diff --git a/BaseClasses.py b/BaseClasses.py index 1e50bfa9..7a850d00 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -26,9 +26,10 @@ class World(object): self.teams = 1 self.owShuffle = owShuffle.copy() self.owTerrain = {} - self.owCrossed = owCrossed.copy() self.owKeepSimilar = {} self.owMixed = owMixed.copy() + self.owCrossed = owCrossed.copy() + self.owCrossed = self.owCrossed if self.owCrossed != 'polar' or self.owMixed else 'none' self.owWhirlpoolShuffle = {} self.owFluteShuffle = {} self.shuffle = shuffle.copy() @@ -95,6 +96,7 @@ class World(object): self.rooms = [] self._room_cache = {} self.dungeon_layouts = {} + self.dungeon_pool = {} self.inaccessible_regions = {} self.enabled_entrances = {} self.key_logic = {} @@ -105,7 +107,6 @@ class World(object): self.sanc_portal = {} self.fish = BabelFish() self.pot_contents = {} - self.trolls = {} for player in range(1, players + 1): def set_player_attr(attr, val): @@ -132,7 +133,7 @@ class World(object): set_player_attr('can_access_trock_big_chest', None) set_player_attr('can_access_trock_middle', None) set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] - or shuffle[player] in ['lean', 'crossed', 'insanity']) + or shuffle[player] in ['lean', 'swapped', 'crossed', 'insanity']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) set_player_attr('keyshuffle', 'none') @@ -163,9 +164,10 @@ class World(object): set_player_attr('pot_contents', None) set_player_attr('pseudoboots', False) set_player_attr('collection_rate', False) - set_player_attr('colorizepots', False) + set_player_attr('colorizepots', True) set_player_attr('pot_pool', {}) set_player_attr('decoupledoors', False) + set_player_attr('door_self_loops', False) set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') set_player_attr('key_logic_algorithm', 'default') @@ -178,7 +180,6 @@ class World(object): set_player_attr('exp_cache', defaultdict(dict)) set_player_attr('enabled_entrances', {}) - set_player_attr('trolls', False) def finish_init(self): for player in range(1, self.players + 1): @@ -186,8 +187,6 @@ class World(object): self.mode[player] = 'open' if self.goal[player] == 'completionist': self.accessibility[player] = 'locations' - if self.trolls[player]: - self.can_take_damage = False def get_name_string_for_object(self, obj): return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' @@ -352,7 +351,7 @@ class World(object): else: if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: return False - elif self.goal[player] in ['crystals', 'trinity', 'z1']: + elif self.goal[player] in ['crystals', 'trinity', 'z1', 'ganonhunt']: return True else: return False @@ -1329,6 +1328,9 @@ class CollectionState(object): return self.has('Golden Sword', player) or self.has('Tempered Sword', player) or self.has('Master Sword', player) or self.has('Fighter Sword', player) return False + def can_use_medallions(self, player): + return self.has_sword(player) or self.world.swords[player] in ['pseudo', 'assured_pseudo'] + def has_real_sword(self, player, level=1): if self.world.swords[player] in ['pseudo', 'assured_pseudo']: return False; @@ -1431,11 +1433,7 @@ class CollectionState(object): def can_flute(self, player): if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player): return False # can't flute in rain state - if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)): - return True - lw = self.world.get_region('Kakariko Area', player) - return self.has('Ocarina (Activated)', player) or (self.has('Ocarina', player) and lw.can_reach(self) - and self.is_not_bunny(lw, player)) + return self.has('Ocarina (Activated)', player) def can_melt_things(self, player): return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.can_use_medallions(player)) @@ -2236,6 +2234,7 @@ class Door(object): self.dest = None self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed) self.blocked_orig = False + self.trapped = False self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue) self.smallKey = False # There's a small key door on this side self.bigKey = False # There's a big key door on this side @@ -2346,7 +2345,7 @@ class Door(object): return self def no_exit(self): - self.blocked = self.blocked_orig = True + self.blocked = self.blocked_orig = self.trapped = True return self def no_entrance(self): @@ -3082,9 +3081,11 @@ class Spoiler(object): 'trap_door_mode': self.world.trap_door_mode, 'key_logic': self.world.key_logic_algorithm, 'decoupledoors': self.world.decoupledoors, + 'door_self_loops': self.world.door_self_loops, 'dungeon_counters': self.world.dungeon_counters, 'item_pool': self.world.difficulty, 'item_functionality': self.world.difficulty_adjustments, + 'beemizer': self.world.beemizer, 'gt_crystals': self.world.crystals_needed_for_gt, 'ganon_crystals': self.world.crystals_needed_for_ganon, 'ganon_vulnerability_item': self.world.ganon_item, @@ -3110,7 +3111,10 @@ class Spoiler(object): 'pseudoboots': self.world.pseudoboots, 'triforcegoal': self.world.treasure_hunt_count, 'triforcepool': self.world.treasure_hunt_total, - 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)} + 'race': self.world.settings.world_rep['meta']['race'], + 'user_notes': self.world.settings.world_rep['meta']['user_notes'], + 'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}, + 'seed': self.world.seed } for p in range(1, self.world.players + 1): @@ -3258,6 +3262,8 @@ class Spoiler(object): outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed)) for k,v in self.metadata["versions"].items(): outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v) + if self.metadata['user_notes']: + outfile.write('User Notes:'.ljust(line_width) + '%s\n' % self.metadata['user_notes']) outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm) outfile.write('Players:'.ljust(line_width) + '%d\n' % self.world.players) outfile.write('Teams:'.ljust(line_width) + '%d\n' % self.world.teams) @@ -3265,9 +3271,9 @@ class Spoiler(object): if self.world.players > 1: outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player))) outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player]) - outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player]) + outfile.write('\n') outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player]) - outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) + outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player]) outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player]) if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'z1', 'ganonhunt']: outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player]) @@ -3275,16 +3281,24 @@ class Spoiler(object): outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player])) outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player])) outfile.write('Ganon Vulnerability Item:'.ljust(line_width) + '%s\n' % str(self.metadata['ganon_vulnerability_item'][player])) + outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player]) + outfile.write('\n') outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player]) outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player]) - outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player]) + outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player]) outfile.write('Flute Mode:'.ljust(line_width) + '%s\n' % self.metadata['flute_mode'][player]) outfile.write('Bow Mode:'.ljust(line_width) + '%s\n' % self.metadata['bow_mode'][player]) - outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player]) - outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player])) + outfile.write('Beemizer:'.ljust(line_width) + '%s\n' % self.metadata['beemizer'][player]) outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player])) - outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player])) + outfile.write('\n') + outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player])) + outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player])) + outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player]) + outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player])) + outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player])) + outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player]) + outfile.write('\n') outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla': outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player])) @@ -3294,35 +3308,38 @@ class Spoiler(object): outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player])) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) - outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player])) + outfile.write('\n') outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) if self.metadata['shuffle'][player] != 'vanilla': outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player])) - outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player])) + outfile.write('Shuffle Link\'s House:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player])) outfile.write('Shuffle Tavern:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffletavern'][player])) + outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player]) if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]: outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player]) - outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player]) + outfile.write('\n') + outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player])) + 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('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]) if self.metadata['door_shuffle'][player] != 'vanilla': outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player]) outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player]) outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player]) - outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player]) outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player])) - outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) + outfile.write('Spiral Stairs Self-Loop:'.ljust(line_width) + '%s\n' % yn(self.metadata['door_self_loops'][player])) + outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player]) - outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player])) - outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player]) - outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player])) - outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player])) - 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('\n') outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player]) outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player]) outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player]) + outfile.write('\n') + outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player])) outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player])) outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race'])) @@ -3584,6 +3601,9 @@ class Pot(object): item = self.item if not self.indicator else self.standing_item_code return [self.x, high_byte, item] + def get_region(self, world, player): + return world.get_region(self.room, 1) + def __eq__(self, other): return self.x == other.x and self.y == other.y and self.room == other.room @@ -3592,9 +3612,9 @@ class Pot(object): # byte 0: DDDE EEEE (DR, ER) -dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3} +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} + 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10} # byte 1: LLLW WSSS (logic, mode, sword) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} @@ -3617,10 +3637,10 @@ mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2} pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7, 'clustered': 8, 'nonempty': 9} -# byte 5: CCCC CTTX (crystals gt, ctr2, experimental) +# byte 5: SCCC CTTX (self-loop doors, crystals gt, ctr2, experimental) counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} -# byte 6: CCCC CPAA (crystals ganon, pyramid, access +# byte 6: ?CCC CPAA (crystals ganon, pyramid, access access_mode = {"items": 0, "locations": 1, "none": 2} # byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies) @@ -3649,7 +3669,7 @@ 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, 'wild': 1, 'universal': 2} # reserved 8 modes? +keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # reserved 8 modes? take_any_mode = {'none': 0, 'random': 1, 'fixed': 2} bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} @@ -3684,11 +3704,12 @@ class Settings(object): (0x80 if w.shuffletavern[p] else 0) | (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]), - ((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3) + (0x80 if w.door_self_loops[p] else 0) + | ((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) - | (0x4 if w.open_pyramid[p] else 0) | access_mode[w.accessibility[p]], + | (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) @@ -3710,8 +3731,8 @@ class Settings(object): (flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4 | take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]), - ((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 6 - | trap_door_mode[w.trap_door_mode[p]] << 4 | key_logic_algo[w.key_logic_algorithm[p]]), + ((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 5 + | trap_door_mode[w.trap_door_mode[p]] << 3 | key_logic_algo[w.key_logic_algorithm[p]]), ]) return base64.b64encode(code, "+-".encode()).decode() @@ -3749,12 +3770,13 @@ class Settings(object): args.dropshuffle[p] = True if settings[4] & 0x10 else False args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F] + args.door_self_loops[p] = True if settings[5] & 0x80 else False args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1] - cgt = (settings[5] & 0xf8) >> 3 + cgt = (settings[5] & 0x78) >> 3 args.crystals_gt[p] = "random" if cgt == 8 else cgt args.experimental[p] = True if settings[5] & 0x1 else False - cgan = (settings[6] & 0xf8) >> 3 + 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 @@ -3794,8 +3816,8 @@ class Settings(object): if len(settings) > 14: args.pseudoboots[p] = True if settings[14] & 0x80 else False - args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 6] - args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x14) >> 4] + args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 5] + args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x18) >> 3] args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07] diff --git a/Bosses.py b/Bosses.py index f12e92a8..f003565d 100644 --- a/Bosses.py +++ b/Bosses.py @@ -38,8 +38,6 @@ def LanmolasDefeatRule(state, player): state.has_special_weapon_level(player, 1))) def MoldormDefeatRule(state, player): - if state.world.trolls[player] and not (state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or state.has('Hookshot', player)): - return False return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or state.has_special_weapon_level(player, 1))) @@ -76,8 +74,6 @@ def MothulaDefeatRule(state, player): state.has_special_weapon_level(player, 1))) def BlindDefeatRule(state, player): - if state.world.trolls[player]: - return state.has('Shovel', player) return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) or state.has_special_weapon_level(player, 1))) @@ -111,14 +107,6 @@ def VitreousDefeatRule(state, player): state.has_special_weapon_level(player, 2))) def TrinexxDefeatRule(state, player): - if state.world.trolls[player]: - if not (state.has('Bombos', player) and state.has('Ether', player)): - return False - return (state.has('Hammer', player) or - state.has_real_sword(player, 3) or - state.has_special_weapon_level(player, 4) or - ((state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) - and state.can_extend_magic(player, 24))) if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): return False if not state.special_weapon_check(player, 2): diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a0a29b..5f1c8fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,96 @@ # Changelog +## 0.3.2.2 +- Added Customizer support for Flute Shuffle (thanks Catobat) +- Fixed bad Old Man rescue possibility in Swapped ER +- Fixed vanilla placement issue in Swapped ER +- Removed entrance hints in Swapped ER +- Fixed various generation/validation errors + +## 0.3.2.1 +- \~Merged in DR v1.2.0.20~ +- Some minor Swapped ER improvements +- Fixed generation error with Flute Activation + +## 0.3.2.0 +- New Swapped ER mode option +- Fixed issue with flipper rules not properly getting pearl requirement added +- Fixed minor issues in generating non-crossed ER on subsequent attempts + +## 0.3.1.2 +- Retro now gives a universal key from bottle vendor fish prize +- Fixed issue with Aga Door preventing Murahduhla Cutscene +- Fixed issue with Ice Cave/Shopping Mall water transitions not flipping in Mixed OWR +- Minor improvements to item GFX draw routine + +## 0.3.1.1 +- \~Merged in DR v1.2.0.17~ +- Various renames/reorganizations of region/rule definition to match upcoming DR world remodel +- Minor improvements to item GFX draw routine +- Minor change to terrain/logic of Spiral/Mimic Ledge in Inverted 2.0 + +## 0.3.1.0 +- Added new Triforce Cutscene when getting Triforce item (turning in TF Pieces) +- Fixed issue with enemy key drops displaying the wrong GFX +- Fixed issue with boss music playing in Hera after boss is defeated +- Fixed issue that limited the Bonk count to cap at 99 +- Made Murahdahla interactable on Pyramid in Rainstate + +## 0.3.0.8 +- All Bonk prize GFX are no longer using the Power Star placeholder GFX +- Fixed issue with dislaying Key GFX during tablet animations +- Fixed issue with DPad input being disabled in Pause Menu if an item GFX is on screen + +## 0.3.0.7 +- \~Merged in DR v1.2.0.16~ +- Major overhaul of how item GFX are drawn on screen + - Bonk prize GFX can now all be displayed simultaneously + - Rupee items that are in-plain-sight are now animated + - Narrow items are now centered within their tile space + - Fixed issue with pottery items showing bad GFX after map check or medallion use +- Fixed issue with bonk drop items causing duplicate sprite spawns + +## 0.3.0.6 +- \~Merged in DR v1.2.0.15~ +- Fixed Tower of Hera music silence issue +- Improved symmetrical GT crystal cutscene +- Changed bonk prizes to not mark as collected unless it is visible on screen + +## 0.3.0.5 +- Major reorganization to GUI Options +- Corrected various fake world behavior in glitched modes +- Fixed various issues with glitched mode logic +- Various minor logic fixes + +## 0.3.0.4 +- \~Merged in DR v1.2.0.14~ + - Fixed issue with enemy drops on OW enemies + - Fixed issue with magically opened doors +- Inverted + Vanilla ER can now generate +- Fixed issue with Multiworld bonk items not sending to correct player +- Minor improvements to OW Palettes on screen transition + +## 0.3.0.3 +- Fixed issue with new Cold Fairy Statue location not dropping correct item +- Fixed issue with Multiworld due to new Cold Fairy Statue location +- Improved water collision to only target Heart Piece sprites (rando items) + +## 0.3.0.2 +- \~Merged in DR v1.2.0.12~ + - Fixed some door landing issues +- Added Cold Fairy Statue as a new Bonk Location in Bonk Drop Shuffle +- Added Customizer support for enabling OWR Options +- Removed `Arrows (5)` item from Item Table, replaces with `Arrows (10)` + +## 0.3.0.1 +- \~Merged in DR v1.2.0.10~ + - Fixed some door landing issues +- Added Customizer support for OWR Tile Flips + - Added 'Standard+Inverted' template customizer yaml +- Fixed some ER issues with placing Sanc Drop in non-crossed ER + ## 0.3.0.0 -- Merged in all DR Customizer features since its initial release up to v1.2.0.9 +- \~Merged in all DR Customizer features since its initial release up to v1.2.0.9~ - Major revamp of Aerinon's ER 2.0 to better support OWR modes - Fixed various incorrect logic issues (Inverted flute spots, Bomb Shop start, etc) - Flute is disabled in rain state except glitched modes @@ -321,7 +410,7 @@ - Removed sortedcontainers dependency ### 0.1.6.7 -- Mountain Entry and West Death Mountain are now Swapped independently (Old Man rescue is always in your starting world) +- Mountain Pass and West Death Mountain are now Swapped independently (Old Man rescue is always in your starting world) - Fixed issue with AT/GT access logic - Improved spoiler log playthru accuracy - Fixed Boss Music when boss room is entered thru straight stairs @@ -347,7 +436,7 @@ ### 0.1.6.2 - Added Balanced option for Flute Shuffle -- Fixed issue with Flute Spot to Mountain Entry softlocking +- Fixed issue with Flute Spot to Mountain Pass softlocking - Fixed logic bug with Inverted Kakariko Portal ### 0.1.6.1 diff --git a/CLI.py b/CLI.py index 4dcdd571..1b2b86c7 100644 --- a/CLI.py +++ b/CLI.py @@ -139,7 +139,7 @@ def parse_cli(argv, no_defaults=False): 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', - 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', + 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', @@ -147,7 +147,7 @@ def parse_cli(argv, no_defaults=False): 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', - 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'trolls']: + 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -220,7 +220,7 @@ def parse_settings(): "keydropshuffle": False, "dropshuffle": False, "pottery": "none", - "colorizepots": False, + "colorizepots": True, "shufflepots": False, "mapshuffle": False, "compassshuffle": False, @@ -233,6 +233,7 @@ def parse_settings(): "trap_door_mode": "optional", "key_logic_algorithm": "default", "decoupledoors": False, + "door_self_loops": False, "experimental": False, "dungeon_counters": "default", "mixed_travel": "prevent", @@ -245,6 +246,7 @@ def parse_settings(): "triforce_goal_min": 0, "triforce_goal_max": 0, "triforce_min_difference": 0, + "triforce_max_difference": 10000, "code": "", "multi": 1, @@ -361,7 +363,7 @@ def parse_settings(): "saveonexit": "ask", "outputname": "", "startinventoryarray": {}, - "trolls": False, + "notes": "" } if sys.platform.lower().find("windows"): diff --git a/DoorShuffle.py b/DoorShuffle.py index 8e65a4cc..3eb02a6f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -88,7 +88,8 @@ def link_doors_prep(world, player): find_inaccessible_regions(world, player) - if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla': + create_dungeon_pool(world, player) + if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla': choose_portals(world, player) else: if world.shuffle[player] == 'vanilla': @@ -133,6 +134,20 @@ def create_dungeon_pool(world, player): pool = None if world.doorShuffle[player] == 'basic': pool = [([name], regions) for name, regions in dungeon_regions.items()] + elif world.doorShuffle[player] == 'paired': + dungeon_pool = list(dungeon_regions.keys()) + groups = [] + while dungeon_pool: + if len(dungeon_pool) == 3: + groups.append(list(dungeon_pool)) + dungeon_pool.clear() + else: + choice_a = random.choice(dungeon_pool) + dungeon_pool.remove(choice_a) + choice_b = random.choice(dungeon_pool) + dungeon_pool.remove(choice_b) + groups.append([choice_a, choice_b]) + pool = [(group, list(chain.from_iterable([dungeon_regions[d] for d in group]))) for group in groups] elif world.doorShuffle[player] == 'partitioned': groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'], ['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'], @@ -143,38 +158,17 @@ def create_dungeon_pool(world, player): elif world.doorShuffle[player] != 'vanilla': logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player]) raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player]) - return pool + world.dungeon_pool[player] = pool def link_doors_main(world, player): - pool = create_dungeon_pool(world, player) + pool = world.dungeon_pool[player] if pool: main_dungeon_pool(pool, world, player) if world.doorShuffle[player] != 'vanilla': create_door_spoiler(world, player) -# todo: I think this function is not necessary -def mark_regions(world, player): - # traverse dungeons and make sure dungeon property is assigned - player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player] - for dungeon in player_dungeons: - queue = deque(dungeon.regions) - while len(queue) > 0: - region = world.get_region(queue.popleft(), player) - if region.name not in dungeon.regions: - dungeon.regions.append(region.name) - region.dungeon = dungeon - for ext in region.exits: - d = world.check_for_door(ext.name, player) - connected = ext.connected_region - if d is not None and connected is not None: - if d.dest is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue: - queue.append(connected) # needs to be added - elif connected is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue: - queue.append(connected) # needs to be added - - def create_door_spoiler(world, player): logger = logging.getLogger('') shuffled_door_types = [DoorType.Normal, DoorType.SpiralStairs] @@ -323,6 +317,11 @@ def connect_simple_door(world, exit_name, region_name, player): d.dest = region +def connect_simple_door_to_region(exit_door, region): + exit_door.entrance.connect(region) + exit_door.dest = region + + def connect_door_only(world, exit_name, region, player): d = world.check_for_door(exit_name, player) if d is not None: @@ -355,6 +354,12 @@ def connect_two_way(world, entrancename, exitname, player): x.dest = y if y is not None: y.dest = x + if x.dependents: + for dep in x.dependents: + connect_simple_door_to_region(dep, ext.parent_region) + if y.dependents: + for dep in y.dependents: + connect_simple_door_to_region(dep, entrance.parent_region) def connect_one_way(world, entrancename, exitname, player): @@ -374,6 +379,9 @@ def connect_one_way(world, entrancename, exitname, player): y = world.check_for_door(exitname, player) if x is not None: x.dest = y + if x.dependents: + for dep in x.dependents: + connect_simple_door_to_region(dep, ext.parent_region) def unmark_ugly_smalls(world, player): for d in ['Eastern Hint Tile Blocked Path SE', 'Eastern Darkness S', 'Thieves Hallway SE', 'Mire Left Bridge S', @@ -422,20 +430,9 @@ def pair_existing_key_doors(world, player, door_a, door_b): def choose_portals(world, player): - if world.doorShuffle[player] != ['vanilla']: shuffle_flag = world.doorShuffle[player] != 'basic' - allowed = {} - if world.doorShuffle[player] == 'basic': - allowed = {name: {name} for name in dungeon_regions} - elif world.doorShuffle[player] == 'partitioned': - groups = [['Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower'], - ['Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town'], - ['Ice Palace', 'Misery Mire', 'Turtle Rock', 'Ganons Tower']] - allowed = {name: set(group) for group in groups for name in group} - elif world.doorShuffle[player] == 'crossed': - all_dungeons = set(dungeon_regions.keys()) - allowed = {name: all_dungeons for name in dungeon_regions} + allowed = {name: set(group[0]) for group in world.dungeon_pool[player] for name in group[0]} # key drops allow the big key in the right place in Desert Tiles 2 bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave'] @@ -460,7 +457,7 @@ def choose_portals(world, player): if portal_region.type == RegionType.LightWorld: world.get_portal(portal, player).light_world = True if name in world.inaccessible_regions[player] or (hc_flag and portal != 'Hyrule Castle South'): - name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name + name_key = 'Desert Ledge' if name == 'Desert Ledge Keep' else name region_map[name_key].append(portal) inaccessible_portals.append(portal) else: @@ -580,7 +577,7 @@ def customizer_portals(master_door_list, world, player): assigned_doors.add(door) # restricts connected doors to the customized portals if assigned_doors: - pool = create_dungeon_pool(world, player) + pool = world.dungeon_pool[player] if pool: pool_map = {} for pool, region_list in pool: @@ -613,7 +610,7 @@ def analyze_portals(world, player): if portal_region.type == RegionType.LightWorld: world.get_portal(portal, player).light_world = True if name in world.inaccessible_regions[player]: - name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name + name_key = 'Desert Ledge' if name == 'Desert Ledge Keep' else name region_map[name_key].append(portal) inaccessible_portals.append(portal) else: @@ -1814,7 +1811,8 @@ def shuffle_door_types(door_type_pools, paths, world, player): for dungeon, doors in custom_dict.items(): all_custom[dungeon].extend(doors) - world.paired_doors[player].clear() + for pd in world.paired_doors[player]: + pd.pair = False used_doors = shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, world, player) # big keys used_doors = shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player) @@ -1846,12 +1844,12 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, wo builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon]) remaining -= len(custom_trap_doors[dungeon]) ttl += len(builder.candidates.trap) - if ttl == 0: + if ttl == 0 and all(len(custom_trap_doors[dungeon]) == 0 for dungeon in pool): continue for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] proportion = len(builder.candidates.trap) - calc = int(round(proportion * door_type_pool.traps/ttl)) + calc = 0 if ttl == 0 else int(round(proportion * door_type_pool.traps/ttl)) suggested = min(proportion, calc) remaining -= suggested suggestion_map[dungeon] = suggested @@ -1949,7 +1947,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_cu if flex_map[dungeon] > 0: queue.append(dungeon) # time to re-assign - reassign_big_key_doors(bk_map, world, player) + reassign_big_key_doors(bk_map, used_doors, world, player) for name, big_list in bk_map.items(): used_doors.update(flatten_pair_list(big_list)) return used_doors @@ -1983,7 +1981,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_ remaining = max(0, remaining) for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] - calculated = int(round(builder.key_doors_num*total_keys/ttl)) + if ttl == 0: + calculated = 0 + else: + calculated = int(round(builder.key_doors_num*total_keys/ttl)) max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player)) cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt) limit = min(max_keys, cand_len, max_computation) @@ -2034,7 +2035,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_ else: builder.key_doors_num -= 1 # time to re-assign - reassign_key_doors(small_map, world, player) + reassign_key_doors(small_map, used_doors, world, player) for dungeon_name in pool: if world.keyshuffle[player] != 'universal': builder = world.dungeon_layouts[player][dungeon_name] @@ -2116,7 +2117,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_ suggestion_map[dungeon] = pair queue.append(dungeon) # time to re-assign - reassign_bd_doors(bd_map, world, player) + reassign_bd_doors(bd_map, used_doors, world, player) for name, pair in bd_map.items(): used_doors.update(flatten_pair_list(pair[0])) used_doors.update(flatten_pair_list(pair[1])) @@ -2213,9 +2214,10 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, sample_list = build_sample_list(combinations, 1000) proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed) proposal.extend(custom_trap_doors) + filtered_proposal = [x for x in proposal if x.name not in trap_door_exceptions] start_regions, event_starts = filter_start_regions(builder, start_regions, world, player) - while not validate_trap_layout(proposal, builder, start_regions, paths, world, player): + while not validate_trap_layout(filtered_proposal, builder, start_regions, paths, world, player): itr += 1 if itr >= len(sample_list): if not drop: @@ -2228,6 +2230,7 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, itr = 0 proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed) proposal.extend(custom_trap_doors) + filtered_proposal = [x for x in proposal if x.name not in trap_door_exceptions] builder.trap_door_proposal = proposal return proposal, trap_doors_needed @@ -2250,6 +2253,12 @@ def filter_start_regions(builder, start_regions, world, player): portal_entrance_region = portal.door.entrance.parent_region.name if portal_entrance_region not in builder.path_entrances: excluded[region] = None + if not portal: + drop_region = next((x.parent_region for x in region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] + or x.parent_region.name == 'Sewer Drop'), None) + if drop_region and drop_region.name in world.inaccessible_regions[player]: + excluded[region] = None if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'): excluded[region] = None if portal is None: @@ -2345,10 +2354,12 @@ def reassign_trap_doors(trap_map, world, player): elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]: room.change(d.doorListPos, DoorKind.Normal) d.blocked = False + d.trapped = False # connect_one_way(world, d.name, d.dest.name, player) elif d.type is DoorType.Normal and d not in traps: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.blocked = False + d.trapped = False for d in traps: change_door_to_trap(d, world, player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player) @@ -2386,24 +2397,45 @@ def change_door_to_trap(d, world, player): elif d.direction in [Direction.North, Direction.West]: new_kind = DoorKind.TrapTriggerable if new_kind: - d.blocked = True + d.blocked = is_trap_door_blocked(d) + d.trapped = True pos = 3 if d.type == DoorType.Normal else 4 verify_door_list_pos(d, room, world, player, pos) d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos] room.change(d.doorListPos, new_kind) - if d.entrance.connected_region is not None: + if d.entrance.connected_region is not None and d.blocked: d.entrance.connected_region.entrances.remove(d.entrance) d.entrance.connected_region = None elif d.type is DoorType.Normal: - d.blocked = True + d.blocked = is_trap_door_blocked(d) + d.trapped = True verify_door_list_pos(d, room, world, player, pos=3) d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos] room.change(d.doorListPos, DoorKind.Trap) - if d.entrance.connected_region is not None: + if d.entrance.connected_region is not None and d.blocked: d.entrance.connected_region.entrances.remove(d.entrance) d.entrance.connected_region = None +trap_door_exceptions = { + 'PoD Mimics 2 SW', 'TR Twin Pokeys NW', 'Thieves Blocked Entry SW', 'Hyrule Dungeon Armory Interior Key Door N', + 'Desert Compass Key Door WN', 'TR Tile Room SE', 'Mire Cross SW', 'Tower Circle of Pots ES', + 'Eastern Single Eyegore ES', 'Eastern Duo Eyegores SE', 'Swamp Push Statue S', + 'Skull 2 East Lobby WS', 'GT Hope Room WN', 'Eastern Courtyard Ledge S', 'Ice Lobby SE', 'GT Speed Torch WN', + 'Ice Switch Room ES', 'Ice Switch Room NE', 'Skull Torch Room WS', 'GT Speed Torch NE', 'GT Speed Torch WS', + 'GT Torch Cross WN', 'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Torches WN', 'PoD Lobby N', 'PoD Middle Cage S', + 'Ice Bomb Jump NW', 'GT Hidden Spikes SE', 'Ice Tall Hint EN', 'GT Conveyor Cross EN', 'Eastern Pot Switch WN', + 'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Eastern Dark Square Key Door WN', 'Eastern Lobby NW', + 'Eastern Lobby NE', 'Ice Cross Bottom SE', 'Desert Back Lobby S', 'Desert West S', + 'Desert West Lobby ES', 'Mire Hidden Shooters SE', 'Mire Hidden Shooters ES', 'Mire Hidden Shooters WS', + 'Tower Dark Pits EN', 'Tower Dark Maze ES', 'TR Tongue Pull WS', +} + + +def is_trap_door_blocked(door): + return door.name not in trap_door_exceptions + + def find_big_key_candidates(builder, start_regions, used, world, player): if world.door_type_mode[player] != 'original': # big, all, chaos # traverse dungeon and find candidates @@ -2526,7 +2558,7 @@ def find_current_bk_doors(builder): return current_doors -def reassign_big_key_doors(bk_map, world, player): +def reassign_big_key_doors(bk_map, used_doors, world, player): logger = logging.getLogger('') for name, big_doors in bk_map.items(): flat_proposal = flatten_pair_list(big_doors) @@ -2535,11 +2567,11 @@ def reassign_big_key_doors(bk_map, world, player): while len(queue) > 0: d = queue.pop() if d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: - if not d.entranceFlag: + if not d.entranceFlag and d not in used_doors and d.dest not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.bigKey = False - elif d.type is DoorType.Normal and d not in flat_proposal: - if not d.entranceFlag: + elif d.type is DoorType.Normal and d not in flat_proposal : + if not d.entranceFlag and d not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.bigKey = False for obj in big_doors: @@ -2782,7 +2814,7 @@ def find_valid_bd_combination(builder, suggested, world, player): return bomb_proposal, dash_proposal, ttl_needed -def reassign_bd_doors(bd_map, world, player): +def reassign_bd_doors(bd_map, used_doors, world, player): for name, pair in bd_map.items(): flat_bomb_proposal = flatten_pair_list(pair[0]) flat_dash_proposal = flatten_pair_list(pair[1]) @@ -2795,10 +2827,10 @@ def reassign_bd_doors(bd_map, world, player): queue = deque(find_current_bd_doors(builder, world)) while len(queue) > 0: d = queue.pop() - if d.type is DoorType.Interior and not_in_proposal(d): + if d.type is DoorType.Interior and not_in_proposal(d) and d not in used_doors and d.dest not in used_doors: if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) - elif d.type is DoorType.Normal and not_in_proposal(d): + elif d.type is DoorType.Normal and not_in_proposal(d) and d not in used_doors: if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) do_bombable_dashable(pair[0], DoorKind.Bombable, world, player) @@ -2990,7 +3022,7 @@ def valid_key_door_pair(door1, door2): return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1 -def reassign_key_doors(small_map, world, player): +def reassign_key_doors(small_map, used_doors, world, player): logger = logging.getLogger('') for name, small_doors in small_map.items(): logger.debug(f'Key doors for {name}') @@ -3011,13 +3043,13 @@ def reassign_key_doors(small_map, world, player): room.delete(d.doorListPos) d.smallKey = False elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: - if not d.entranceFlag: + if not d.entranceFlag and d not in used_doors and d.dest not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False d.dest.smallKey = False queue.remove(d.dest) elif d.type is DoorType.Normal and d not in flat_proposal: - if not d.entranceFlag: + if not d.entranceFlag and d not in used_doors: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False for dp in world.paired_doors[player]: @@ -3271,7 +3303,8 @@ 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', 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint'] + 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') 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 @@ -3313,9 +3346,10 @@ def find_accessible_entrances(world, player, builder): hc_std = True start_regions = ['Hyrule Castle Courtyard'] else: - start_regions = ['Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop', 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint'] - if world.is_tile_swapped(0x1b, player): - start_regions.append('Hyrule Castle Ledge') + 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('Pyramid Area' if not world.is_tile_swapped(0x1b, player) else 'Hyrule Castle Ledge') + regs = convert_regions(start_regions, world, player) visited_regions = set() visited_entrances = [] @@ -3335,12 +3369,12 @@ def find_accessible_entrances(world, player, builder): if connect not in queue and connect not in visited_regions: queue.append(connect) for ext in next_region.exits: - if hc_std and ext.name == 'Hyrule Castle Main Gate (North)': # just skip it + if hc_std and ext.name in ['Hyrule Castle Main Gate (North)', 'Castle Gate Teleporter (Inner)', 'Hyrule Castle Ledge Drop']: # just skip it continue connect = ext.connected_region if connect is None or ext.door and ext.door.blocked: continue - if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' and (ext.name.startswith('Flute From') or ext.name in ['Hyrule Castle Main Gate (North)', 'Top of Pyramid (Inner)', 'Inverted Pyramid Entrance']): + if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' and (ext.name.startswith('Flute From') or ext.name in ['Hyrule Castle Main Gate (North)', 'Castle Gate Teleporter (Inner)', 'Inverted Pyramid Entrance']): continue if connect.name in entrances and connect not in visited_entrances: visited_entrances.append(connect.name) @@ -3373,10 +3407,7 @@ def create_doors_for_inaccessible_region(inaccessible_region, world, player): region = world.get_region(inaccessible_region, player) for ext in region.exits: create_door(world, player, ext.name, region.name) - if ext.connected_region is None: - # TODO: Since Open/Inverted regions are merged into one world model, some exits are left disconnected intentionally - logging.getLogger('').debug('Exit not connected to any region: %s', ext.name) - elif ext.connected_region.name.endswith(' Portal'): + if ext.connected_region and ext.connected_region.name.endswith(' Portal'): for more_exts in ext.connected_region.exits: create_door(world, player, more_exts.name, ext.connected_region.name) @@ -3386,7 +3417,7 @@ def create_door(world, player, entName, region_name): connect = entrance.connected_region if connect is not None: for ext in connect.exits: - if ext.connected_region is not None and ext.connected_region.name == region_name: + if ext.connected_region and ext.connected_region.name == region_name: d = Door(player, ext.name, DoorType.Logical, ext), world.doors += d connect_door_only(world, ext.name, ext.connected_region, player) @@ -3799,6 +3830,8 @@ logical_connections = [ ('GT Blocked Stairs Block Path', 'GT Big Chest'), ('GT Speed Torch South Path', 'GT Speed Torch'), ('GT Speed Torch North Path', 'GT Speed Torch Upper'), + ('GT Conveyor Cross Hammer Path', 'GT Conveyor Cross Across Pits'), + ('GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross'), ('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'), ('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'), ('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'), @@ -4147,7 +4180,7 @@ interior_doors = [ ('Mire Neglected Room SE', 'Mire Chest View NE'), ('Mire BK Chest Ledge WS', 'Mire Warping Pool ES'), # technically one-way ('Mire Torches Top SW', 'Mire Torches Bottom NW'), - ('Mire Torches Bottom WS', 'Mire Attic Hint ES'), + ('Mire Torches Bottom ES', 'Mire Attic Hint WS'), ('Mire Dark Shooters SE', 'Mire Key Rupees NE'), ('Mire Dark Shooters SW', 'Mire Block X NW'), ('Mire Tall Dark and Roomy WS', 'Mire Crystal Right ES'), diff --git a/Doors.py b/Doors.py index 0bd13742..8ba20e40 100644 --- a/Doors.py +++ b/Doors.py @@ -924,9 +924,9 @@ def create_doors(world, player): create_door(player, 'Mire Torches Top SW', Intr).dir(So, 0x97, Left, High).pos(1), create_door(player, 'Mire Torches Bottom Holes', Hole), create_door(player, 'Mire Torches Bottom NW', Intr).dir(No, 0x97, Left, High).pos(1), - create_door(player, 'Mire Torches Bottom WS', Intr).dir(We, 0x97, Bot, High).pos(0), + create_door(player, 'Mire Torches Bottom ES', Intr).dir(Ea, 0x97, Bot, High).pos(0), create_door(player, 'Mire Torches Top Holes', Hole), - create_door(player, 'Mire Attic Hint ES', Intr).dir(Ea, 0x97, Bot, High).pos(0), + create_door(player, 'Mire Attic Hint WS', Intr).dir(We, 0x97, Bot, High).pos(0), create_door(player, 'Mire Attic Hint Hole', Hole), create_door(player, 'Mire Dark Shooters Up Stairs', Sprl).dir(Up, 0x93, 0, LTH).ss(A, 0x32, 0xec), create_door(player, 'Mire Dark Shooters SW', Intr).dir(So, 0x93, Left, High).pos(0), @@ -1115,6 +1115,8 @@ def create_doors(world, player): create_door(player, 'GT Invisible Catwalk NE', Nrml).dir(No, 0x9c, Right, High).pos(2), create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2), create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0), + create_door(player, 'GT Conveyor Cross Hammer Path', Lgcl), + create_door(player, 'GT Conveyor Cross Hookshot Path', Lgcl), create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0), create_door(player, 'GT Hookshot East-Mid Path', Lgcl), create_door(player, 'GT Hookshot Mid-East Path', Lgcl), @@ -1497,7 +1499,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 ['crossed', 'insanity']: + if world.mode[player] == 'open' 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 f97f8ecf..5f34fbf2 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 ['crossed', 'insanity']: + if world.mode[player] == 'open' 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 = [] @@ -1538,8 +1538,8 @@ def calc_allowance_and_dead_ends(builder, connections_tuple, world, player): if entrance in connections.keys(): enabling_region = connections[entrance] check_list = list(potentials[enabling_region]) - if enabling_region.name in ['Desert Ledge', 'Desert Palace Entrance (North) Spot']: - alternate = 'Desert Palace Entrance (North) Spot' if enabling_region.name == 'Desert Ledge' else 'Desert Ledge' + if enabling_region.name in ['Desert Ledge', 'Desert Ledge Keep']: + alternate = 'Desert Ledge Keep' if enabling_region.name == 'Desert Ledge' else 'Desert Ledge' if world.get_region(alternate, player) in potentials: check_list.extend(potentials[world.get_region(alternate, player)]) connecting_entrances = [x for x in check_list if x != entrance and x not in dead_entrances and x not in drop_entrances_allowance] @@ -1677,10 +1677,24 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p random.shuffle(sector_list) orig_location_set = build_orig_location_set(dungeon_map) num_dungeon_items = requested_dungeon_items(world, player) + locations_to_distribute = sum(sector.chest_locations for sector in free_location_sectors.keys()) + reserved_per_dungeon = {d_name: count_reserved_locations(world, player, orig_location_set[d_name]) + for d_name in dungeon_map.keys()} + base_free, found_enough = 2, False + while not found_enough: + needed = sum(max(0, max(base_free, reserved_per_dungeon[d]) + num_dungeon_items - len(orig_location_set[d])) + for d in dungeon_map.keys()) + if needed > locations_to_distribute: + if base_free == 0: + raise Exception('Unable to meet minimum requirements, check for customizer problems') + base_free -= 1 + else: + found_enough = True d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())} next_sector = sector_list.pop() while not valid: - choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player) + choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, + base_free, world, player) if not choice: break choices[choice].append(next_sector) @@ -1691,7 +1705,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p valid = True for d_name, idx in d_idx.items(): free_items = count_reserved_locations(world, player, location_set[d_name]) - target = max(free_items, 2) + num_dungeon_items + target = max(free_items, base_free) + num_dungeon_items if totals[idx] < target: valid = False break @@ -1699,8 +1713,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p if len(sector_list) == 0: choices = defaultdict(list) sector_list = list(free_location_sectors) - else: - next_sector = sector_list.pop() + next_sector = sector_list.pop() else: choices[choice].remove(next_sector) for builder, choice_list in choices.items(): @@ -1709,7 +1722,7 @@ def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_p return free_location_sectors -def weighted_random_location(dungeon_map, choices, orig_location_set, world, player): +def weighted_random_location(dungeon_map, choices, orig_location_set, base_free, world, player): population = [] totals = [] location_set = {x: set(y) for x, y in orig_location_set.items()} @@ -1720,7 +1733,7 @@ def weighted_random_location(dungeon_map, choices, orig_location_set, world, pla builder_set = location_set[dungeon_builder.name] builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder]))) free_items = count_reserved_locations(world, player, builder_set) - target = max(free_items, 2) + num_dungeon_items + target = max(free_items, base_free) + num_dungeon_items if ttl < target: population.append(dungeon_builder) choice = random.choice(population) if len(population) > 0 else None @@ -1775,7 +1788,7 @@ def count_reserved_locations(world, player, proposed_set): return 2 -def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False): +def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole): population = [] some_c_switches_present = False for name, builder in dungeon_map.items(): @@ -1784,7 +1797,7 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier if builder.c_switch_present and not builder.c_locked: some_c_switches_present = True if len(population) == 0: # nothing needs a switch - if assign_one and not some_c_switches_present: # something should have one + if len(crystal_barriers) > 0 and not some_c_switches_present: # something should have one if len(crystal_switches) == 0: raise GenerationException('No crystal switches to assign. Ref %s' % next(iter(dungeon_map.keys()))) valid, builder_choice, switch_choice = False, None, None @@ -3139,8 +3152,7 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info): check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole) check_for_forced_crystal(dungeon_map, candidate_sectors, global_pole) crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors) - leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, - global_pole, len(crystal_barriers) > 0) + leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole) ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole) for sector in leftover: if sector.polarity().is_neutral(): diff --git a/Dungeons.py b/Dungeons.py index 9d862b05..3835ef62 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -190,10 +190,11 @@ gt_regions = [ 'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor', 'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', - 'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform', - 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', - 'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', - 'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', + 'GT Conveyor Cross', 'GT Conveyor Cross Across Pits', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', + 'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', + 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', 'GT Double Switch Entry', + 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', 'GT Double Switch Left', + 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left', 'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section', 'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot', @@ -204,8 +205,9 @@ gt_regions = [ 'GT Dash Hall', 'GT Hidden Spikes', 'GT Cannonball Bridge', 'GT Refill', 'GT Gauntlet 1', 'GT Gauntlet 2', 'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1', 'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches', - 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', 'GT Crystal Circles - Ranged Crystal', - 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', + 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle', + 'GT Crystal Circles - Ranged Crystal', 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm', + 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', 'GT Frozen Over', 'GT Brightly Lit Hall', 'GT Agahnim 2', 'Ganons Tower Portal' ] diff --git a/ER_hint_reference.txt b/ER_hint_reference.txt index a4972fba..c86ca335 100644 --- a/ER_hint_reference.txt +++ b/ER_hint_reference.txt @@ -98,7 +98,7 @@ Ice Palace: Ice Palace Skull Woods Final Section: The back of Skull Woods Death Mountain Return Cave (West): The SW DM Foothills Cave Mimic Cave: Mimic Ledge -Dark World Hammer Peg Cave: The rows of pegs +Hammer Peg Cave: The rows of pegs Pyramid Fairy: The crack on the pyramid Eastern Palace: Eastern Palace Elder House (East): Elder House @@ -145,7 +145,7 @@ Chicken House: The chicken lady's house Tavern North: A backdoor Aginahs Cave: The open desert cave Sahasrahlas Hut: The house near armos -Cave Shop (Lake Hylia): The cave NW Lake Hylia +Lake Hylia Shop: The cave NW Lake Hylia Blacksmiths Hut: The old smithery Sick Kids House: The central house in Kakariko Lost Woods Gamble: A tree trunk door @@ -188,19 +188,19 @@ Dark World Shop: The hammer sealed building Red Shield Shop: The fenced in building Mire Shed: The western hut in the mire East Dark World Hint: The dark cave near the eastmost portal -Dark Desert Hint: The cave east of the mire +Mire Hint: The cave east of the mire Spike Cave: The ledge cave on west dark DM Palace of Darkness Hint: The building south of Kiki Dark Lake Hylia Ledge Spike Cave: The rock SE dark Lake Hylia -Cave Shop (Dark Death Mountain): The base of east dark DM -Dark World Potion Shop: The building near the catfish +Dark Death Mountain Shop: The base of east dark DM +Dark Potion Shop: The building near the catfish Archery Game: The old archery game -Dark World Lumberjack Shop: The northmost Dark World building +Dark Lumberjack Shop: The northmost Dark World building Hype Cave: The cave south of the old bomb shop Brewery: The Village of Outcasts building with no door Dark Lake Hylia Ledge Hint: The open cave SE dark Lake Hylia Chest Game: The westmost building in the Village of Outcasts -Dark Desert Fairy: The eastern hut in the mire +Mire Fairy: The eastern hut in the mire Dark Lake Hylia Ledge Fairy: The sealed cave SE dark Lake Hylia Fortune Teller (Dark): The building NE the Village of Outcasts Sanctuary: Sanctuary diff --git a/EntranceShuffle.py b/EntranceShuffle.py index dc6e6fd4..ff875c59 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -671,7 +671,7 @@ def link_entrances(world, player): 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' if not invFlag else 'GT Lobby': + 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 @@ -1298,7 +1298,7 @@ def full_shuffle_dungeons(world, Dungeon_Exits, player): dw_must_exit.append(ledge.pop()) dw_related.extend(ledge) if not world.is_tile_swapped(0x30, player): - if 'Desert Palace Mouth' in world.inaccessible_regions[player]: + if 'Desert Mouth' in world.inaccessible_regions[player]: lw_entrances.remove('Desert Palace Entrance (East)') lw_must_exit.append('Desert Palace Entrance (East)') else: @@ -1501,8 +1501,8 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe inaccessible_regions = list(world.inaccessible_regions[player]) # find OW regions that don't have a multi-entrance dungeon exit connected - glitch_regions = ['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs', 'Hyrule Castle Water', - 'Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs', 'Pyramid Water'] + glitch_regions = ['Central Cliffs', 'Eastern Cliff', 'Desert Northern Cliffs', 'Hyrule Castle Water', + 'Dark Central Cliffs', 'Darkness Cliff', 'Mire Northern Cliffs', 'Pyramid Water'] multi_dungeon_exits = { 'Hyrule Castle South Portal', 'Hyrule Castle West Portal', 'Hyrule Castle East Portal', 'Sanctuary Portal', 'Desert South Portal', 'Desert West Portal', @@ -1943,7 +1943,7 @@ Entrance_Pool_Base = ['Links House', 'Desert Fairy', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Ledge Fairy', - 'Dark Desert Fairy', + 'Mire Fairy', 'Dark Death Mountain Fairy', 'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', @@ -1951,8 +1951,8 @@ Entrance_Pool_Base = ['Links House', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', - 'Cave Shop (Lake Hylia)', - 'Cave Shop (Dark Death Mountain)', + 'Lake Hylia Shop', + 'Dark Death Mountain Shop', 'Capacity Upgrade', 'Blacksmiths Hut', 'Sick Kids House', @@ -1983,21 +1983,21 @@ Entrance_Pool_Base = ['Links House', 'Big Bomb Shop', 'Dark World Shop', 'Dark Lake Hylia Shop', - 'Dark World Lumberjack Shop', - 'Dark World Potion Shop', + 'Dark Lumberjack Shop', + 'Dark Potion Shop', 'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Hype Cave', 'Brewery', 'C-Shaped House', 'Chest Game', - 'Dark World Hammer Peg Cave', + 'Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Archery Game', 'Mire Shed', - 'Dark Desert Hint', + 'Mire Hint', 'Spike Cave', 'Mimic Cave', 'Kakariko Well Drop', @@ -2079,11 +2079,11 @@ Exit_Pool_Base = ['Links House Exit', 'Bonk Fairy (Light)', 'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy', - 'Swamp Healer Fairy', + 'Light Hype Fairy', 'Desert Healer Fairy', 'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', - 'Dark Desert Healer Fairy', + 'Mire Healer Fairy', 'Dark Death Mountain Healer Fairy', 'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', @@ -2091,8 +2091,8 @@ Exit_Pool_Base = ['Links House Exit', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', - 'Cave Shop (Lake Hylia)', - 'Cave Shop (Dark Death Mountain)', + 'Lake Hylia Shop', + 'Dark Death Mountain Shop', 'Capacity Upgrade', 'Blacksmiths Hut', 'Sick Kids House', @@ -2123,21 +2123,21 @@ Exit_Pool_Base = ['Links House Exit', 'Big Bomb Shop', 'Village of Outcasts Shop', 'Dark Lake Hylia Shop', - 'Dark World Lumberjack Shop', - 'Dark World Potion Shop', + 'Dark Lumberjack Shop', + 'Dark Potion Shop', 'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Hype Cave', 'Brewery', 'C-Shaped House', 'Chest Game', - 'Dark World Hammer Peg Cave', + 'Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Archery Game', 'Mire Shed', - 'Dark Desert Hint', + 'Mire Hint', 'Spike Cave', 'Mimic Cave', 'Kakariko Well (top)', @@ -2170,7 +2170,7 @@ mandatory_connections = [('Lost Woods Hideout (top to bottom)', 'Lost Woods Hide ('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'), ('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'), ('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'), - ('Light World Death Mountain Shop', 'Light World Death Mountain Shop'), + ('Paradox Shop', 'Paradox Shop'), ('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'), @@ -2209,10 +2209,10 @@ default_connections = [('Bonk Fairy (Light)', 'Bonk Fairy (Light)'), ('Dark Sanctuary Hint', 'Dark Sanctuary Hint'), ('Fortune Teller (Dark)', 'Fortune Teller (Dark)'), ('Archery Game', 'Archery Game'), - ('Dark Desert Fairy', 'Dark Desert Healer Fairy') + ('Mire Fairy', 'Mire Healer Fairy') ] -default_takeany_connections = [('Light Hype Fairy', 'Swamp Healer Fairy'), +default_takeany_connections = [('Light Hype Fairy', 'Light Hype Fairy'), ('Desert Fairy', 'Desert Healer Fairy'), ('Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Healer Fairy'), ('Bonk Fairy (Dark)', 'Bonk Fairy (Dark)'), @@ -2229,7 +2229,7 @@ default_pot_connections = [('Lumberjack House', 'Lumberjack House'), ('Hookshot Fairy', 'Hookshot Fairy'), ('Palace of Darkness Hint', 'Palace of Darkness Hint'), ('Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Spike Cave'), - ('Dark Desert Hint', 'Dark Desert Hint') + ('Mire Hint', 'Mire Hint') ] default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Exit (West)'), @@ -2284,18 +2284,18 @@ default_item_connections = [('Links House', 'Links House Exit'), ('C-Shaped House', 'C-Shaped House'), ('Brewery', 'Brewery'), ('Pyramid Fairy', 'Pyramid Fairy'), - ('Dark World Hammer Peg Cave', 'Dark World Hammer Peg Cave'), + ('Hammer Peg Cave', 'Hammer Peg Cave'), ('Big Bomb Shop', 'Big Bomb Shop'), ('Mire Shed', 'Mire Shed'), ('Hype Cave', 'Hype Cave') ] default_shop_connections = [('Kakariko Shop', 'Kakariko Shop'), - ('Cave Shop (Lake Hylia)', 'Cave Shop (Lake Hylia)'), + ('Lake Hylia Shop', 'Lake Hylia Shop'), ('Capacity Upgrade', 'Capacity Upgrade'), - ('Dark World Lumberjack Shop', 'Dark World Lumberjack Shop'), - ('Cave Shop (Dark Death Mountain)', 'Cave Shop (Dark Death Mountain)'), - ('Dark World Potion Shop', 'Dark World Potion Shop'), + ('Dark Lumberjack Shop', 'Dark Lumberjack Shop'), + ('Dark Death Mountain Shop', 'Dark Death Mountain Shop'), + ('Dark Potion Shop', 'Dark Potion Shop'), ('Dark World Shop', 'Village of Outcasts Shop'), ('Red Shield Shop', 'Red Shield Shop'), ('Dark Lake Hylia Shop', 'Dark Lake Hylia Shop') @@ -2449,7 +2449,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c '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), - 'Cave Shop (Lake Hylia)': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 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), @@ -2492,19 +2492,19 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c '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), - 'Dark World Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000), 0x20), + '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 World Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), - 'Dark World Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 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), - 'Dark Desert Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000), 0x00), - 'Dark Desert Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 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), - 'Cave Shop (Dark Death Mountain)': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 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), @@ -2584,11 +2584,11 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Bonk Fairy (Light)': 0x71, 'Bonk Fairy (Dark)': 0x71, 'Lake Hylia Healer Fairy': 0x5E, - 'Swamp Healer Fairy': 0x5E, + 'Light Hype Fairy': 0x5E, 'Desert Healer Fairy': 0x5E, 'Dark Lake Hylia Healer Fairy': 0x5E, 'Dark Lake Hylia Ledge Healer Fairy': 0x5E, - 'Dark Desert Healer Fairy': 0x5E, + 'Mire Healer Fairy': 0x5E, 'Dark Death Mountain Healer Fairy': 0x5E, 'Fortune Teller (Light)': 0x65, 'Lake Hylia Fortune Teller': 0x65, @@ -2597,8 +2597,8 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Chicken House': 0x4B, 'Aginahs Cave': 0x4D, 'Sahasrahlas Hut': 0x45, - 'Cave Shop (Lake Hylia)': 0x58, - 'Cave Shop (Dark Death Mountain)': 0x58, + 'Lake Hylia Shop': 0x58, + 'Dark Death Mountain Shop': 0x58, 'Capacity Upgrade': 0x5D, 'Blacksmiths Hut': 0x64, 'Sick Kids House': 0x40, @@ -2629,21 +2629,21 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Big Bomb Shop': 0x53, 'Village of Outcasts Shop': 0x60, 'Dark Lake Hylia Shop': 0x60, - 'Dark World Lumberjack Shop': 0x60, - 'Dark World Potion 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, - 'Dark World Hammer Peg Cave': 0x83, + 'Hammer Peg Cave': 0x83, 'Red Shield Shop': 0x57, 'Dark Sanctuary Hint': 0x5A, 'Fortune Teller (Dark)': 0x66, 'Archery Game': 0x59, 'Mire Shed': 0x5F, - 'Dark Desert Hint': 0x62, + 'Mire Hint': 0x62, 'Spike Cave': 0x41, 'Mimic Cave': 0x4F, 'Kakariko Well (top)': 0x80, @@ -2730,7 +2730,7 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Chicken House': (0x120, 0x880), 'Aginahs Cave': (0x2e0, 0xd00), 'Sahasrahlas Hut': (0xcf0, 0x6c0), - 'Cave Shop (Lake Hylia)': (0xbc0, 0xc00), + 'Lake Hylia Shop': (0xbc0, 0xc00), 'Capacity Upgrade': (0xca0, 0xda0), 'Kakariko Well Drop': None, 'Blacksmiths Hut': (0x4a0, 0x880), @@ -2740,7 +2740,7 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Lost Woods Gamble': (0x240, 0x080), 'Fortune Teller (Light)': (0x2c0, 0x4c0), 'Snitch Lady (East)': (0x310, 0x7a0), - 'Snitch Lady (West)': (0x800, 0x7a0), + 'Snitch Lady (West)': (0x080, 0x7a0), 'Bush Covered House': (0x2e0, 0x880), 'Tavern (Front)': (0x270, 0x980), 'Light World Bomb Hut': (0x070, 0x980), @@ -2769,20 +2769,20 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d), 'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00), 'Hype Cave': (0x940, 0xc80), 'Bonk Fairy (Dark)': (0x740, 0xa80), - 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0), - 'Dark World Hammer Peg Cave': (0x4c0, 0x940), + 'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x080, 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 World Lumberjack Shop': (0x4e0, 0x0d0), - 'Dark World Potion Shop': (0xc80, 0x4c0), + 'Dark Lumberjack Shop': (0x4e0, 0x0d0), + 'Dark Potion Shop': (0xc80, 0x4c0), 'Archery Game': (0x2f0, 0xaf0), 'Mire Shed': (0x060, 0xc90), - 'Dark Desert Hint': (0x2e0, 0xd00), - 'Dark Desert Fairy': (0x1c0, 0xc90), + 'Mire Hint': (0x2e0, 0xd00), + 'Mire Fairy': (0x1c0, 0xc90), 'Spike Cave': (0x860, 0x180), - 'Cave Shop (Dark Death Mountain)': (0xd80, 0x180), + 'Dark Death Mountain Shop': (0xd80, 0x180), 'Dark Death Mountain Fairy': (0x620, 0x2c0), 'Mimic Cave': (0xc80, 0x180), 'Big Bomb Shop': (0x8b1, 0xb2d), diff --git a/Fill.py b/Fill.py index a53df2a8..3121e291 100644 --- a/Fill.py +++ b/Fill.py @@ -279,6 +279,10 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp if spot_to_fill: return spot_to_fill return None + # explicitly fail these cases + elif world.algorithm in ['dungeon_only', 'major_only']: + raise FillError(f'Rare placement for {world.algorithm} detected. {item_to_place} unable to be placed.' + f' Try a different seed') else: other_locations = [x for x in locations if x not in attempted] for location in other_locations: @@ -425,7 +429,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None else: max_trash = gt_count scaled_trash = math.floor(max_trash * scale_factor) - if world.goal[player] in ['triforcehunt', 'trinity', 'z1', 'ganonhunt']: + if world.goal[player] in ['triforcehunt', 'trinity', 'z1', 'ganonhunt'] or world.algorithm == 'dungeon_only': gftower_trash_count = random.randint(scaled_trash, max_trash) else: gftower_trash_count = random.randint(0, scaled_trash) @@ -538,7 +542,7 @@ def ensure_good_pots(world, write_skips=False): and (loc.type != LocationType.Pot or loc.item.player != loc.player)): loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player) if (loc.item.name in {'Arrows (5)'} - and (loc.type not in [LocationType.Pot, LocationType.Bonk] or loc.item.player != loc.player)): + and (loc.type != LocationType.Pot or loc.item.player != loc.player)): loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player) # # can be placed here by multiworld balancing or shop balancing # # change it to something normal for the player it got swapped to @@ -670,9 +674,10 @@ def sell_potions(world, player): for potion in ['Green Potion', 'Blue Potion', 'Red Potion', 'Bee']: location = random.choice(filter_locations(ItemFactory(potion, player), locations, world, potion=True)) locations.remove(location) - p_item = next(item for item in world.itempool if item.name == potion and item.player == player) - world.push_item(location, p_item, collect=False) - world.itempool.remove(p_item) + p_item = next((item for item in world.itempool if item.name == potion and item.player == player), None) + if p_item: + world.push_item(location, p_item, collect=False) + world.itempool.remove(p_item) def sell_keys(world, player): diff --git a/Gui.py b/Gui.py index 5ca88ac1..a0f4726f 100755 --- a/Gui.py +++ b/Gui.py @@ -144,14 +144,14 @@ def guiMain(args=None): self.pages["randomizer"].pages["entrance"] = entrando_page(self.pages["randomizer"].notebook) self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["entrance"], text="Entrances") + # Dungeons + self.pages["randomizer"].pages["dungeon"] = dungeon_page(self.pages["randomizer"].notebook) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["dungeon"], text="Dungeons") + # Enemizer self.pages["randomizer"].pages["enemizer"],self.settings = enemizer_page(self.pages["randomizer"].notebook,self.settings) self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["enemizer"], text="Enemizer") - # Dungeon Shuffle - self.pages["randomizer"].pages["dungeon"] = dungeon_page(self.pages["randomizer"].notebook) - self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["dungeon"], text="Dungeon Shuffle") - # Multiworld # self.pages["randomizer"].pages["multiworld"],self.settings = multiworld_page(self.pages["randomizer"].notebook,self.settings) # self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["multiworld"], text="Multiworld") diff --git a/ItemList.py b/ItemList.py index 8e2570ea..e8e1b3bc 100644 --- a/ItemList.py +++ b/ItemList.py @@ -226,6 +226,21 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player) + and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))): + region = world.get_region('Kakariko Village',player) + + loc = Location(player, "Flute Activation", parent=region) + region.locations.append(loc) + world.dynamic_locations.append(loc) + + world.clear_location_cache() + + world.push_item(loc, ItemFactory('Ocarina (Activated)', player), False) + loc.event = True + loc.locked = True + loc.forced_item = loc.item + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) @@ -432,21 +447,7 @@ def generate_itempool(world, player): for i in range(4): next(adv_heart_pieces).advancement = True - beeweights = {'0': {None: 100}, - '1': {None: 75, 'trap': 25}, - '2': {None: 40, 'trap': 40, 'bee': 20}, - '3': {'trap': 50, 'bee': 50}, - '4': {'trap': 100}} - def beemizer(item): - if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type: - choice = random.choices(list(beeweights[world.beemizer[item.player]].keys()), weights=list(beeweights[world.beemizer[item.player]].values()))[0] - return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", player) - return item - - if not skip_pool_adjustments: - world.itempool += [beemizer(item) for item in items] - else: - world.itempool += items + world.itempool += items # shuffle medallions mm_medallion, tr_medallion = None, None @@ -503,6 +504,20 @@ def generate_itempool(world, player): # modfiy based on start inventory, if any modify_pool_for_start_inventory(start_inventory, world, player) + beeweights = {'0': {None: 100}, + '1': {None: 75, 'trap': 25}, + '2': {None: 40, 'trap': 40, 'bee': 20}, + '3': {'trap': 50, 'bee': 50}, + '4': {'trap': 100}} + def beemizer(item): + if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type: + choice = random.choices(list(beeweights[world.beemizer[item.player]].keys()), weights=list(beeweights[world.beemizer[item.player]].values()))[0] + return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", player) + return item + + if not skip_pool_adjustments: + 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) pool_size = count_player_dungeon_item_pool(world, player) @@ -520,15 +535,15 @@ def generate_itempool(world, player): take_any_locations = [ 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', 'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)', - 'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy', 'Swamp Healer Fairy', 'Desert Healer Fairy', - 'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Dark Desert Healer Fairy', + 'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy', 'Light Hype Fairy', 'Desert Healer Fairy', + 'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Mire Healer Fairy', 'Dark Death Mountain Healer Fairy', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Kakariko Gamble Game', '50 Rupee Cave', 'Lost Woods Gamble', 'Hookshot Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint', - 'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint'] + 'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Mire Hint'] fixed_take_anys = [ - 'Desert Healer Fairy', 'Swamp Healer Fairy', 'Dark Death Mountain Healer Fairy', + 'Desert Healer Fairy', 'Light Hype Fairy', 'Dark Death Mountain Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Bonk Fairy (Dark)'] @@ -615,10 +630,10 @@ def create_dynamic_shop_locations(world, player): def create_farm_locations(world, player): bush_bombs = ['Flute Boy Approach Area', - 'Kakariko Area', - 'Village of Outcasts Area', + 'Kakariko Village', + 'Village of Outcasts', 'Forgotten Forest Area', - 'Bat Cave Ledge', + 'Blacksmith Ledge', 'East Dark Death Mountain (Bottom)'] rock_bombs = ['Links House Area', 'Dark Chapel Area', @@ -632,7 +647,7 @@ def create_farm_locations(world, player): 'Dark Fortune Area', 'Qirn Jump Area', 'Shield Shop Area', - 'Palace of Darkness Nook Area', + 'Darkness Nook Area', 'Swamp Nook Area', 'Dark South Pass Area'] bonk_bombs = ['Kakariko Fortune Area', 'Dark Graveyard Area'] #TODO: Flute Boy Approach Area and Bonk Rock Ledge are available post-Aga @@ -648,10 +663,10 @@ def create_farm_locations(world, player): 'Hype Cave Area', 'Dark South Pass Area', 'Bumper Cave Area'] - pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] + pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Pass Area'] post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] - bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] + bush_crabs = ['Lost Woods East Area', 'Mountain Pass Area'] pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] rock_crabs = ['Desert Pass Area'] @@ -1006,9 +1021,9 @@ def balance_prices(world, player): def check_hints(world, player): - if world.shuffle[player] in ['simple', 'restricted', 'full', 'lite', 'lean', 'crossed', 'insanity']: + if world.shuffle[player] in ['simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity']: for shop, location_list in shop_to_location_table.items(): - if shop in ['Capacity Upgrade', 'Light World Death Mountain Shop', 'Potion Shop']: + if shop in ['Capacity Upgrade', 'Paradox Shop', 'Potion Shop']: continue # near the queen, near potions, and near 7 chests are fine for loc_name in location_list: # other shops are indistinguishable in ER world.get_location(loc_name, player).hint_text = f'for sale' @@ -1126,9 +1141,6 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt pool.remove('Piece of Heart') pool.extend(['Rupees (20)'] * 12) - if world.trolls[player]: - pool.extend(['Magic Upgrade (1/2)']) - if bombbag: pool = [item.replace('Bomb Upgrade (+5)','Rupees (5)') for item in pool] pool = [item.replace('Bomb Upgrade (+10)','Rupees (5)') for item in pool] @@ -1222,10 +1234,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt if world.keyshuffle[player] == 'universal': pool.extend(diff.retro) if door_shuffle != 'vanilla': # door shuffle needs more keys for universal keys - replace = 'Rupees (20)' if difficulty == 'normal' else 'Rupees (5)' - indices = [i for i, x in enumerate(pool) if x == replace] - for i in range(0, min(10, len(indices))): - pool[indices[i]] = 'Small Key (Universal)' + pool.extend(['Small Key (Universal)'] * 5) # reduce to 5 for now if mode == 'standard': if door_shuffle == 'vanilla': key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) @@ -1275,8 +1284,19 @@ item_alternates = { def modify_pool_for_start_inventory(start_inventory, world, player): - # skips custom item pools - these shouldn't be adjusted if (world.customizer and world.customizer.get_item_pool()) or world.custom: + # custom item pools only adjust in dungeon items + for item in start_inventory: + if item.dungeon: + d = world.get_dungeon(item.dungeon, item.player) + match = next((i for i in d.all_items if i.name == item.name), None) + if match: + if match.map or match.compass: + d.dungeon_items.remove(match) + elif match.smallkey: + d.small_keys.remove(match) + elif match.bigkey and d.big_key == match: + d.big_key = None return for item in start_inventory: if item.player == player: @@ -1302,8 +1322,8 @@ def modify_pool_for_start_inventory(start_inventory, world, player): d.dungeon_items.remove(match) elif match.smallkey: d.small_keys.remove(match) - elif match.bigkey: - d.big_key.remove(match) + elif match.bigkey and d.big_key == match: + d.big_key = None def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer, goal, mode, swords, bombbag, customitemarray): @@ -1346,7 +1366,21 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer itemtotal = itemtotal + customitemarray["generickeys"] customitems = [ - "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "L1 Bombs", "L2 Bombs", "L3 Bombs", "L4 Bombs", "L5 Bombs", "Progressive Bombs", "L1 Cane", "L2 Cane", "L3 Cane", "L4 Cane", "L5 Cane", "Progressive Cane", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce" + "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", + "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", + "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", + "Cane of Somaria", "Cane of Byrna", "Cape", + "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", + "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", + "Master Sword", "Tempered Sword", "Golden Sword", + "Progressive Bombs", "Progressive Cane", + "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", + "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", + "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", + "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Bombs (10)", + "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", + "Blue Clock", "Green Clock", "Red Clock", + "Progressive Bow", "Triforce Piece", "Triforce" ] for customitem in customitems: pool.extend([customitem] * customitemarray[get_custom_array_key(customitem)]) @@ -1524,6 +1558,9 @@ def make_customizer_pool(world, player): place_item('Master Sword Pedestal', 'Triforce') guaranteed_items = alwaysitems + ['Magic Mirror', 'Moon Pearl'] + if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active': + guaranteed_items.remove('Ocarina') + guaranteed_items.append('Ocarina (Activated)') missing_items = [] if world.shopsanity[player]: guaranteed_items.extend(['Blue Potion', 'Green Potion', 'Red Potion']) @@ -1552,7 +1589,8 @@ def make_customizer_pool(world, player): bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None) if not bow_found: missing_items.append('Progressive Bow') - logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') + if missing_items: + logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], world.treasure_hunt_total[player]) @@ -1561,20 +1599,23 @@ def make_customizer_pool(world, player): if pieces < t: pool.extend(['Triforce Piece'] * (t - pieces)) - if not world.customizer.get_start_inventory(): - if world.logic[player] in ['owglitches', 'nologic']: - precollected_items.append('Pegasus Boots') - if 'Pegasus Boots' in pool: - pool.remove('Pegasus Boots') - pool.append('Rupees (20)') - if world.swords[player] == 'assured': - precollected_items.append('Progressive Sword') - if 'Progressive Sword' in pool: - pool.remove('Progressive Sword') - pool.append('Rupees (50)') - elif 'Fighter Sword' in pool: - pool.remove('Fighter Sword') - pool.append('Rupees (50)') + sphere_0 = world.customizer.get_start_inventory() + no_start_inventory = not sphere_0 or not sphere_0[player] + init_equip = [] if no_start_inventory else sphere_0[player] + if (world.logic[player] in ['owglitches', 'nologic'] + and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))): + precollected_items.append('Pegasus Boots') + if 'Pegasus Boots' in pool: + pool.remove('Pegasus Boots') + pool.append('Rupees (20)') + if world.swords[player] == 'assured' and (no_start_inventory or all(' Sword' not in x for x in init_equip)): + precollected_items.append('Progressive Sword') + if 'Progressive Sword' in pool: + pool.remove('Progressive Sword') + pool.append('Rupees (50)') + elif 'Fighter Sword' in pool: + pool.remove('Fighter Sword') + pool.append('Rupees (50)') return pool, placed_items, precollected_items, clock_mode, 1 @@ -1648,10 +1689,13 @@ def fill_specific_items(world): dungeon_pool, prize_set, prize_pool) if item_to_place: world.push_item(loc, item_to_place, False) + loc.locked = True track_outside_keys(item_to_place, loc, world) track_dungeon_items(item_to_place, loc, world) loc.event = (event_flag or item_to_place.advancement or item_to_place.bigkey or item_to_place.smallkey) + else: + raise Exception(f'Did not find "{item}" in item pool to place at "{location}"') advanced_placements = world.customizer.get_advanced_placements() if advanced_placements: for player, placement_list in advanced_placements.items(): @@ -1661,7 +1705,7 @@ def fill_specific_items(world): item_to_place, event_flag = get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool) if not item_to_place: - continue + raise Exception(f'Did not find "{item}" in item pool to place for a LocationGroup"') locations = placement['locations'] handled = False while not handled: @@ -1681,6 +1725,7 @@ def fill_specific_items(world): if loc.item: continue world.push_item(loc, item_to_place, False) + loc.locked = True track_outside_keys(item_to_place, loc, world) track_dungeon_items(item_to_place, loc, world) loc.event = (event_flag or item_to_place.advancement diff --git a/Items.py b/Items.py index 1cab8b55..f11b7c6b 100644 --- a/Items.py +++ b/Items.py @@ -80,7 +80,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), - 'Arrows (5)': (False, False, None, 0xB5, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'), + 'Arrows (5)': (False, False, None, 0x5A, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'), 'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'), 'Big Magic': (False, False, None, 0xB4, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'), 'Chicken': (False, False, None, 0xB3, 5, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'), @@ -171,12 +171,13 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key'), 'Nothing': (False, False, None, 0x5A, 1, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'), 'Bee Trap': (False, False, None, 0xB0, 50, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'friendship'), - 'Progressive Bombs': (True, False, 'SwordBomb', 0xB6, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), - 'Progressive Cane': (True, False, 'SwordCane', 0xB7, 200, 'a better\nstick\nrests here!', 'the unknown stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), + 'Progressive Bombs': (True, False, 'SwordBomb', 0xC0, 200, 'throw more\npowerful\nexplosives', 'the unknown grenades', 'the bomb-holding kid', 'better booms for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'fancy bombs'), + 'Progressive Cane': (True, False, 'SwordCane', 0xC1, 200, 'a better\nstick\nrests here!', 'the unknown stick', 'the stick-holding kid', 'better stick for sale', 'fungus into stick', 'cane boy improves again', 'fancy cane'), 'Red Potion': (False, False, None, 0x2E, 150, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'), 'Green Potion': (False, False, None, 0x2F, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'), 'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'), 'Bee': (False, False, None, 0x0E, 10, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'), + 'Good Bee': (False, False, None, 0xB5, 10, 'I will sting your foes a lot', 'and the cold buddy', 'the beekeeper kid', 'cold insect for sale', 'shroom pollenation', 'bottle boy has cold bee again', 'a good bee'), '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, 0xB1, 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, 0xB2, 50, 'Just a pixie!', 'and the pixie', 'the pixie kid', 'pixie for sale', 'pixie fungus', 'bottle boy has pixie again', 'a pixie'), diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 2cbda4be..3f154ade 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -459,7 +459,7 @@ def refine_placement_rules(key_layout, max_ctr): changed = True while changed: changed = False - rules_to_remove = [] + rules_to_remove = {} for rule in key_logic.placement_rules: if rule.check_locations_w_bk: rule.check_locations_w_bk.difference_update(key_logic.sm_restricted) @@ -468,7 +468,7 @@ def refine_placement_rules(key_layout, max_ctr): rule.check_locations_w_bk.difference_update(key_onlys) rule.needed_keys_w_bk -= len(key_onlys) if rule.needed_keys_w_bk == 0: - rules_to_remove.append(rule) + rules_to_remove[rule] = None # todo: evaluate this usage # if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1: # new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk @@ -481,13 +481,13 @@ def refine_placement_rules(key_layout, max_ctr): # changed = True if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk: logging.getLogger('').warning('Invalid rule - what went wrong here??') - rules_to_remove.append(rule) + rules_to_remove[rule] = None changed = True if rule.bk_conditional_set is not None: rule.bk_conditional_set.difference_update(key_logic.bk_restricted) rule.bk_conditional_set.difference_update(max_ctr.key_only_locations) if len(rule.bk_conditional_set) == 0: - rules_to_remove.append(rule) + rules_to_remove[rule] = None if rule.check_locations_wo_bk: rule.check_locations_wo_bk.difference_update(key_logic.sm_restricted) key_onlys = rule.check_locations_wo_bk.intersection(max_ctr.key_only_locations) @@ -495,11 +495,11 @@ def refine_placement_rules(key_layout, max_ctr): rule.check_locations_wo_bk.difference_update(key_onlys) rule.needed_keys_wo_bk -= len(key_onlys) if rule.needed_keys_wo_bk == 0: - rules_to_remove.append(rule) + rules_to_remove[rule] = None if len(rule.check_locations_wo_bk) < rule.needed_keys_wo_bk or rule.needed_keys_wo_bk > key_layout.max_chests: if not rule.prize_relevance and len(rule.bk_conditional_set) > 0: key_logic.bk_restricted.update(rule.bk_conditional_set) - rules_to_remove.append(rule) + rules_to_remove[rule] = None changed = True # impossible for bk to be here, I think for rule_a, rule_b in itertools.combinations([x for x in key_logic.placement_rules if x not in rules_to_remove], 2): if rule_b.bk_conditional_set and rule_a.check_locations_w_bk: @@ -511,25 +511,25 @@ def refine_placement_rules(key_layout, max_ctr): common_locs = len(rule_b.check_locations_w_bk & rule_a.check_locations_wo_bk) if (common_needed - common_locs) * 2 > key_layout.max_chests: key_logic.bk_restricted.update(rule_a.bk_conditional_set) - rules_to_remove.append(rule_a) + rules_to_remove[rule_a] = None changed = True break equivalent_rules = [] for rule in key_logic.placement_rules: for rule2 in key_logic.placement_rules: - if rule != rule2: + if rule != rule2 and rule not in rules_to_remove and rule2 not in rules_to_remove: if rule.check_locations_w_bk and rule2.check_locations_w_bk: if rule2.check_locations_w_bk == rule.check_locations_w_bk and rule2.needed_keys_w_bk > rule.needed_keys_w_bk: - rules_to_remove.append(rule) + rules_to_remove[rule] = None elif rule2.needed_keys_w_bk == rule.needed_keys_w_bk and rule2.check_locations_w_bk < rule.check_locations_w_bk: - rules_to_remove.append(rule) + rules_to_remove[rule] = None elif rule2.check_locations_w_bk == rule.check_locations_w_bk and rule2.needed_keys_w_bk == rule.needed_keys_w_bk: equivalent_rules.append((rule, rule2)) if rule.check_locations_wo_bk and rule2.check_locations_wo_bk and rule.bk_conditional_set == rule2.bk_conditional_set: if rule2.check_locations_wo_bk == rule.check_locations_wo_bk and rule2.needed_keys_wo_bk > rule.needed_keys_wo_bk: - rules_to_remove.append(rule) + rules_to_remove[rule] = None elif rule2.needed_keys_wo_bk == rule.needed_keys_wo_bk and rule2.check_locations_wo_bk < rule.check_locations_wo_bk: - rules_to_remove.append(rule) + rules_to_remove[rule] = None elif rule2.check_locations_wo_bk == rule.check_locations_wo_bk and rule2.needed_keys_wo_bk == rule.needed_keys_wo_bk: equivalent_rules.append((rule, rule2)) if len(rules_to_remove) > 0: @@ -1421,7 +1421,7 @@ def prize_relevance(key_layout, dungeon_entrance, is_atgt_swapped): def prize_relevance_sig2(start_regions, d_name, dungeon_entrance, is_atgt_swapped): if len(start_regions) > 1 and dungeon_entrance and dungeon_table[d_name].prize: - if dungeon_entrance.name == ('Agahmins Tower' if is_atgt_swapped else 'Ganons Tower'): + if dungeon_entrance.name == ('Agahnims Tower' if is_atgt_swapped else 'Ganons Tower'): return 'GT' elif dungeon_entrance.name == 'Pyramid Fairy': return 'BigBomb' @@ -1561,13 +1561,18 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa def invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player): if prev_state is None or state.used_smalls == prev_state.used_smalls: return False - new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors)) - state_copy = state.copy() - while len(new_bk_doors) > 0: - for door in new_bk_doors: - open_a_door(door.door, state_copy, key_layout.flat_prop, world, player) - new_bk_doors = set(state_copy.big_doors).difference(set(prev_state.big_doors)) - expand_key_state(state_copy, key_layout.flat_prop, world, player) + if state.found_forced_bk() and not prev_state.found_forced_bk(): + return False + if state.big_key_opened: + new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors)) + state_copy = state.copy() + while len(new_bk_doors) > 0: + for door in new_bk_doors: + open_a_door(door.door, state_copy, key_layout.flat_prop, world, player) + new_bk_doors = set(state_copy.big_doors).difference(set(prev_state.big_doors)) + expand_key_state(state_copy, key_layout.flat_prop, world, player) + else: + state_copy = state new_locations = set(state_copy.found_locations).difference(set(prev_state.found_locations)) important_found = False for loc in new_locations: @@ -2094,6 +2099,12 @@ def validate_key_placement(key_layout, world, player): if world.bigkeyshuffle[player]: max_counter = find_max_counter(key_layout) big_key_outside = bigkey_name not in (l.item.name for l in max_counter.free_locations if l.item) + for i in world.precollected_items: + if i.player == player and i.name == bigkey_name: + big_key_outside = True + break + if i.player == player and i.name == smallkey_name: + keys_outside += 1 for code, counter in key_layout.key_counters.items(): if len(counter.child_doors) == 0: diff --git a/Main.py b/Main.py index d814e8e1..6f417765 100644 --- a/Main.py +++ b/Main.py @@ -36,7 +36,9 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -__version__ = '1.2.0.9-u' +version_number = '1.2.0.20' +version_branch = '-u' +__version__ = f'{version_number}{version_branch}' from source.classes.BabelFish import BabelFish @@ -147,6 +149,7 @@ def main(args, seed=None, fish=None): world.trap_door_mode = args.trap_door_mode.copy() world.key_logic_algorithm = args.key_logic_algorithm.copy() world.decoupledoors = args.decoupledoors.copy() + world.door_self_loops = args.door_self_loops.copy() world.experimental = args.experimental.copy() world.dungeon_counters = args.dungeon_counters.copy() world.fish = fish @@ -156,8 +159,6 @@ def main(args, seed=None, fish=None): world.potshuffle = args.shufflepots.copy() world.mixed_travel = args.mixed_travel.copy() world.standardize_palettes = args.standardize_palettes.copy() - world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()} - world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()} world.shufflelinks = args.shufflelinks.copy() world.shuffletavern = args.shuffletavern.copy() world.pseudoboots = args.pseudoboots.copy() @@ -166,7 +167,26 @@ def main(args, seed=None, fish=None): world.restrict_boss_items = args.restrict_boss_items.copy() world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() - world.trolls = args.trolls.copy() + + world.treasure_hunt_count = {} + world.treasure_hunt_total = {} + for p in args.triforce_goal: + if int(args.triforce_goal[p]) != 0 or int(args.triforce_pool[p]) != 0 or int(args.triforce_goal_min[p]) != 0 or int(args.triforce_goal_max[p]) != 0 or int(args.triforce_pool_min[p]) != 0 or int(args.triforce_pool_max[p]) != 0: + if int(args.triforce_goal[p]) != 0: + world.treasure_hunt_count[p] = int(args.triforce_goal[p]) + elif int(args.triforce_goal_min[p]) != 0 and int(args.triforce_goal_max[p]) != 0: + world.treasure_hunt_count[p] = random.randint(int(args.triforce_goal_min[p]), int(args.triforce_goal_max[p])) + else: + world.treasure_hunt_count[p] = 8 if world.goal[p] == 'trinity' else 20 + if int(args.triforce_pool[p]) != 0: + world.treasure_hunt_total[p] = int(args.triforce_pool[p]) + elif int(args.triforce_pool_min[p]) != 0 and int(args.triforce_pool_max[p]) != 0: + world.treasure_hunt_total[p] = random.randint(max(int(args.triforce_pool_min[p]), world.treasure_hunt_count[p] + int(args.triforce_min_difference[p])), min(int(args.triforce_pool_max[p]), world.treasure_hunt_count[p] + int(args.triforce_max_difference[p]))) + else: + world.treasure_hunt_total[p] = 10 if world.goal[p] == 'trinity' else 30 + else: + # this will be handled in ItemList.py and custom item pool is used to determine the numbers + world.treasure_hunt_count[p], world.treasure_hunt_total[p] = 0, 0 world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.finish_init() @@ -191,7 +211,7 @@ def main(args, seed=None, fish=None): world.player_names[player].append(name) logger.info('') world.settings = CustomSettings() - world.settings.create_from_world(world, args.race) + world.settings.create_from_world(world, args) outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' @@ -253,6 +273,8 @@ def main(args, seed=None, fish=None): update_world_regions(world, player) mark_light_dark_world_regions(world, player) create_dynamic_exits(world, player) + if args.print_custom_yaml: + world.settings.record_overworld(world) init_districts(world) @@ -293,6 +315,8 @@ def main(args, seed=None, fish=None): set_rules(world, player) district_item_pool_config(world) + dungeon_tracking(world) + fill_specific_items(world) for player in range(1, world.players + 1): if world.shopsanity[player]: sell_potions(world, player) @@ -304,9 +328,6 @@ def main(args, seed=None, fish=None): massage_item_pool(world) if args.print_custom_yaml: world.settings.record_item_pool(world) - dungeon_tracking(world) - fill_specific_items(world) - logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) fill_prizes(world) @@ -528,6 +549,7 @@ def copy_world(world): ret.beemizer = world.beemizer.copy() ret.intensity = world.intensity.copy() ret.decoupledoors = world.decoupledoors.copy() + ret.door_self_loops = world.door_self_loops.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() @@ -540,7 +562,6 @@ def copy_world(world): ret.prizes = world.prizes.copy() ret.restrict_boss_items = world.restrict_boss_items.copy() ret.inaccessible_regions = world.inaccessible_regions.copy() - ret.trolls = world.trolls.copy() for player in range(1, world.players + 1): create_regions(ret, player) @@ -717,7 +738,6 @@ def copy_world_premature(world, player): ret.prizes = world.prizes.copy() ret.restrict_boss_items = world.restrict_boss_items.copy() ret.key_logic = world.key_logic.copy() - ret.trolls = world.trolls.copy() ret.is_copied_world = True @@ -816,8 +836,8 @@ def create_playthrough(world): # get locations containing progress items prog_locations = [location for location in world.get_filled_locations() if location.item.advancement or world.goal[location.player] == 'completionist'] - optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] - optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls + optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile', 'Flute Activation'] + optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Pass Area Tree Pull']) # adding pre-aga tree pulls optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs state_cache = [None] collection_spheres = [] @@ -843,7 +863,9 @@ def create_playthrough(world): logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations)) if not sphere: - logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) + if world.accessibility[location.item.player] != 'none': + logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), + [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) if any([location.name not in optional_locations and world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression")) else: @@ -906,6 +928,7 @@ def create_playthrough(world): if world.has_beaten_game(state): required_locations.clear() else: + logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (loc.item.name, loc.item.player, loc.name, loc.player) for loc in required_locations]) raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required")) # store the required locations for statistical analysis diff --git a/MultiClient.py b/MultiClient.py index 71ac6dff..72f05f34 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -67,6 +67,8 @@ class Context: self.lookup_name_to_id = {} self.lookup_id_to_name = {} + self.pottery_locations_enabled = None + def color_code(*args): codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43, @@ -97,6 +99,8 @@ SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes? ITEM_SRAM_SIZE = 0x250 SHOP_SRAM_LEN = 0x29 # 41 tracked items +POT_LOCATION_TABLE = 0x142A60 + RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte @@ -145,6 +149,7 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), 'Mini Moldorm Cave - Far Right': (0x123, 0x80), 'Mini Moldorm Cave - Generous Guy': (0x123, 0x400), 'Ice Rod Cave': (0x120, 0x10), + 'Cold Fairy Statue': (0x120, 0x200), 'Bonk Rock Cave': (0x124, 0x10), 'Desert Palace - Big Chest': (0x73, 0x10), 'Desert Palace - Torch': (0x73, 0x400), @@ -828,12 +833,14 @@ def get_location_name_from_address(ctx, address): def filter_location(ctx, location): + if location in location_table_pot_items: + tile_idx, mask = location_table_pot_items[location] + tracking_data = ctx.pottery_locations_enabled + tile_pots = tracking_data[tile_idx] | (tracking_data[tile_idx+1] << 8) + return (mask & tile_pots) == 0 if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data and PotShuffle.key_drop_data[location][0] == 'Drop'): return True - if (not ctx.pottery_mode and location in PotShuffle.key_drop_data - and PotShuffle.key_drop_data[location][0] == 'Pot'): - return True if not ctx.shop_mode and location in Regions.flat_normal_shops: return True if not ctx.retro_mode and location in Regions.flat_retro_shops: @@ -938,10 +945,11 @@ async def track_locations(ctx : Context, roomid, roomdata): from OWEdges import OWTileRegions for location, (_, flag, _, _, region_name, _) in bonk_prize_table.items(): if location not in ctx.locations_checked: - screenid = OWTileRegions[region_name] - ow_unchecked[location] = (screenid, flag) - ow_begin = min(ow_begin, screenid) - ow_end = max(ow_end, screenid + 1) + if region_name in OWTileRegions: + screenid = OWTileRegions[region_name] + ow_unchecked[location] = (screenid, flag) + ow_begin = min(ow_begin, screenid) + ow_end = max(ow_end, screenid + 1) if ow_begin < ow_end: ow_data = await snes_read(ctx, SAVEDATA_START + 0x280 + ow_begin, ow_end - ow_begin) if ow_data is not None: @@ -1006,6 +1014,9 @@ async def game_watcher(ctx : Context): logging.warning("ROM change detected, please reconnect to the multiworld server") await disconnect(ctx) + if ctx.pottery_locations_enabled is None: + ctx.pottery_locations_enabled = await snes_read(ctx, POT_LOCATION_TABLE, 0x250) + gamemode = await snes_read(ctx, WRAM_START + 0x10, 1) if gamemode is None or gamemode[0] not in INGAME_MODES: continue diff --git a/MultiServer.py b/MultiServer.py index 7921f02e..0ca32c25 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -46,9 +46,12 @@ class Context: self.lookup_name_to_id = {} self.lookup_id_to_name = {} + self.disable_client_forfeit = False + async def process_command(self, input): await process_command(self, input) + async def send_msgs(websocket, msgs): if not websocket or not websocket.open or websocket.closed: return @@ -284,7 +287,10 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args): if args.startswith('!players'): notify_all(ctx, get_connected_players_string(ctx)) if args.startswith('!forfeit'): - forfeit_player(ctx, client.team, client.slot) + if ctx.disable_client_forfeit: + notify_client(client, 'Client-initiated forfeits are disabled. Please ask the host of this game to forfeit on your behalf.') + else: + forfeit_player(ctx, client.team, client.slot) if args.startswith('!countdown'): try: timer = int(args.split()[1]) diff --git a/OWEdges.py b/OWEdges.py index 1e40a0c4..f4da280e 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -51,8 +51,8 @@ def create_owedges(world, player): create_owedge(player, 'East Death Mountain WS', 0x05, We, Ld, 0x03, 0x0d).coordInfo(0x0340, 0x1660), create_owedge(player, 'East Death Mountain EN', 0x05, Ea, Ld, 0x02, 0x06).coordInfo(0x0078, 0x0180), create_owedge(player, 'Death Mountain TR Pegs WN', 0x07, We, Ld, 0x02) .coordInfo(0x0078, 0x00e0), - create_owedge(player, 'Mountain Entry NW', 0x0a, No, Ld, 0x01) .coordInfo(0x04cc, 0x180a), - create_owedge(player, 'Mountain Entry SE', 0x0a, So, Ld, 0x04) .coordInfo(0x0518, 0x1012), + create_owedge(player, 'Mountain Pass NW', 0x0a, No, Ld, 0x01) .coordInfo(0x04cc, 0x180a), + create_owedge(player, 'Mountain Pass SE', 0x0a, So, Ld, 0x04) .coordInfo(0x0518, 0x1012), create_owedge(player, 'Zora Waterfall NE', 0x0f, No, Ld, 0x02) .coordInfo(0x0f80, 0x009a).special_entrance(0x82), create_owedge(player, 'Zora Waterfall SE', 0x0f, So, Ld, 0x05) .coordInfo(0x0f80, 0x1020), create_owedge(player, 'Lost Woods Pass NW', 0x10, No, Ld, 0x03) .coordInfo(0x0058, 0x1800), @@ -143,7 +143,7 @@ def create_owedges(world, player): create_owedge(player, 'Tree Line WC', 0x2e, We, Wr, 0x1a) .coordInfo(0x0b3c, 0x0660), create_owedge(player, 'Eastern Nook NE', 0x2f, No, Ld, 0x16) .coordInfo(0x0f78, 0x1820), create_owedge(player, 'Desert EC', 0x30, Ea, Ld, 0x1e, 0x39).coordInfo(0x0ee4, 0x1480), - create_owedge(player, 'Desert ES', 0x30, Ea, Ld, 0x1f, 0x39).coordInfo(0x0f8c, 0x1980), + create_owedge(player, 'Desert ES', 0x30, Ea, Ld, 0x1f, 0x39).coordInfo(0x0f90, 0x1980), create_owedge(player, 'Flute Boy Approach NW', 0x32, No, Ld, 0x17) .coordInfo(0x044c, 0x1800), create_owedge(player, 'Flute Boy Approach NC', 0x32, No, Ld, 0x18) .coordInfo(0x04e8, 0x180c), create_owedge(player, 'Flute Boy Approach EC', 0x32, Ea, Ld, 0x1a) .coordInfo(0x0d04, 0x05c0), @@ -167,7 +167,7 @@ def create_owedges(world, player): create_owedge(player, 'Ice Cave SW', 0x37, So, Wr, 0x1e) .coordInfo(0x0e80, 0x1002), create_owedge(player, 'Ice Cave SE', 0x37, So, Ld, 0x1f) .coordInfo(0x0f50, 0x101c), create_owedge(player, 'Desert Pass WC', 0x3a, We, Ld, 0x1f) .coordInfo(0x0ee4, 0x03e0), - create_owedge(player, 'Desert Pass WS', 0x3a, We, Ld, 0x20) .coordInfo(0x0f8c, 0x0860), + create_owedge(player, 'Desert Pass WS', 0x3a, We, Ld, 0x20) .coordInfo(0x0f90, 0x0860), create_owedge(player, 'Desert Pass EC', 0x3a, Ea, Ld, 0x20) .coordInfo(0x0f18, 0x0640), create_owedge(player, 'Desert Pass ES', 0x3a, Ea, Ld, 0x21) .coordInfo(0x0fcb, 0x08c0), create_owedge(player, 'Dam NC', 0x3b, No, Ld, 0x1e) .coordInfo(0x0728, 0x1816), @@ -402,7 +402,7 @@ OWEdgeGroups = { (Op, LW, Vt, Ld, PL, 1): ( [ ['Lumberjack SW'], - ['Mountain Entry SE'], + ['Mountain Pass SE'], ['Lost Woods SE'], ['Zora Waterfall SE'], ['Kakariko Fortune SC'], @@ -420,7 +420,7 @@ OWEdgeGroups = { ['Ice Cave SE'] ], [ - ['Mountain Entry NW'], + ['Mountain Pass NW'], ['Kakariko Pond NE'], ['Kakariko Fortune NE'], ['Zora Approach NE'], @@ -764,7 +764,7 @@ OWEdgeGroupsTerrain = { (Op, LW, Vt, None, PL, 1): ( [ ['Lumberjack SW'], - ['Mountain Entry SE'], + ['Mountain Pass SE'], ['Lost Woods SE'], ['Zora Waterfall SE'], ['Kakariko Fortune SC'], @@ -780,7 +780,7 @@ OWEdgeGroupsTerrain = { ['Statues SC'] ], [ - ['Mountain Entry NW'], + ['Mountain Pass NW'], ['Kakariko Pond NE'], ['Kakariko Fortune NE'], ['Zora Approach NE'], @@ -1037,12 +1037,12 @@ OWTileRegions = bidict({ 'East Death Mountain (Bottom)': 0x05, 'Death Mountain Floating Island': 0x05, - 'Death Mountain TR Pegs': 0x07, + 'Death Mountain TR Pegs Area': 0x07, 'Death Mountain TR Pegs Ledge': 0x07, - 'Mountain Entry Area': 0x0a, - 'Mountain Entry Entrance': 0x0a, - 'Mountain Entry Ledge': 0x0a, + 'Mountain Pass Area': 0x0a, + 'Mountain Pass Entry': 0x0a, + 'Mountain Pass Ledge': 0x0a, 'Zora Waterfall Area': 0x0f, 'Zora Waterfall Water': 0x0f, @@ -1076,9 +1076,9 @@ OWTileRegions = bidict({ 'Zora Approach Ledge': 0x17, 'Zora Approach Water': 0x17, - 'Kakariko Area': 0x18, + 'Kakariko Village': 0x18, 'Kakariko Southwest': 0x18, - 'Kakariko Grass Yard': 0x18, + 'Kakariko Bush Yard': 0x18, 'Forgotten Forest Area': 0x1a, @@ -1097,7 +1097,7 @@ OWTileRegions = bidict({ 'Eastern Palace Area': 0x1e, 'Blacksmith Area': 0x22, - 'Bat Cave Ledge': 0x22, + 'Blacksmith Ledge': 0x22, 'Sand Dunes Area': 0x25, @@ -1125,11 +1125,11 @@ OWTileRegions = bidict({ 'Desert Area': 0x30, 'Desert Ledge': 0x30, - 'Desert Palace Entrance (North) Spot': 0x30, + 'Desert Ledge Keep': 0x30, 'Desert Checkerboard Ledge': 0x30, - 'Desert Palace Stairs': 0x30, - 'Desert Palace Mouth': 0x30, - 'Desert Palace Teleporter Ledge': 0x30, + 'Desert Stairs': 0x30, + 'Desert Mouth': 0x30, + 'Desert Teleporter Ledge': 0x30, 'Bombos Tablet Ledge': 0x30, 'Flute Boy Approach Area': 0x32, @@ -1144,15 +1144,16 @@ OWTileRegions = bidict({ 'Statues Area': 0x34, 'Statues Water': 0x34, - 'Lake Hylia Area': 0x35, - 'Lake Hylia South Shore': 0x35, + 'Lake Hylia Northwest Bank': 0x35, 'Lake Hylia Northeast Bank': 0x35, + 'Lake Hylia South Shore': 0x35, 'Lake Hylia Central Island': 0x35, 'Lake Hylia Island': 0x35, 'Lake Hylia Water': 0x35, 'Lake Hylia Water D': 0x35, 'Ice Cave Area': 0x37, + 'Ice Cave Water': 0x37, 'Desert Pass Area': 0x3a, 'Middle Aged Man': 0x3a, @@ -1176,12 +1177,13 @@ OWTileRegions = bidict({ 'Dark Lumberjack Area': 0x42, 'West Dark Death Mountain (Top)': 0x43, - 'GT Approach': 0x43, + 'GT Stairs': 0x43, 'West Dark Death Mountain (Bottom)': 0x43, 'East Dark Death Mountain (Top)': 0x45, 'East Dark Death Mountain (Bottom Left)': 0x45, 'East Dark Death Mountain (Bottom)': 0x45, + 'East Dark Death Mountain (Bushes)': 0x45, 'Dark Death Mountain Ledge': 0x45, 'Dark Death Mountain Isolated Ledge': 0x45, 'Dark Death Mountain Floating Island': 0x45, @@ -1190,7 +1192,7 @@ OWTileRegions = bidict({ 'Turtle Rock Ledge': 0x47, 'Bumper Cave Area': 0x4a, - 'Bumper Cave Entrance': 0x4a, + 'Bumper Cave Entry': 0x4a, 'Bumper Cave Ledge': 0x4a, 'Catfish Area': 0x4f, @@ -1221,8 +1223,8 @@ OWTileRegions = bidict({ 'Catfish Approach Ledge': 0x57, 'Catfish Approach Water': 0x57, - 'Village of Outcasts Area': 0x58, - 'Dark Grassy Lawn': 0x58, + 'Village of Outcasts': 0x58, + 'Village of Outcasts Bush Yard': 0x58, 'Shield Shop Area': 0x5a, 'Shield Shop Fence': 0x5a, @@ -1266,10 +1268,10 @@ OWTileRegions = bidict({ 'Dark Tree Line Area': 0x6e, 'Dark Tree Line Water': 0x6e, - 'Palace of Darkness Nook Area': 0x6f, + 'Darkness Nook Area': 0x6f, - 'Misery Mire Area': 0x70, - 'Misery Mire Teleporter Ledge': 0x70, + 'Mire Area': 0x70, + 'Mire Teleporter Ledge': 0x70, 'Stumpy Approach Area': 0x72, 'Stumpy Approach Bush Entry': 0x72, @@ -1282,15 +1284,16 @@ OWTileRegions = bidict({ 'Hype Cave Area': 0x74, 'Hype Cave Water': 0x74, - 'Ice Lake Area': 0x75, + 'Ice Lake Northwest Bank': 0x75, 'Ice Lake Northeast Bank': 0x75, - 'Ice Lake Ledge (West)': 0x75, - 'Ice Lake Ledge (East)': 0x75, + 'Ice Lake Southwest Ledge': 0x75, + 'Ice Lake Southeast Ledge': 0x75, 'Ice Lake Water': 0x75, - 'Ice Lake Moat': 0x75, + 'Ice Lake Iceberg': 0x75, 'Ice Palace Area': 0x75, 'Shopping Mall Area': 0x77, + 'Shopping Mall Water': 0x77, 'Swamp Nook Area': 0x7a, @@ -1320,8 +1323,8 @@ parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW', 'East Death Mountain WS': 'East Dark Death Mountain WS', 'East Death Mountain EN': 'East Dark Death Mountain EN', 'Death Mountain TR Pegs WN': 'Turtle Rock WN', - 'Mountain Entry NW': 'Bumper Cave NW', - 'Mountain Entry SE': 'Bumper Cave SE', + 'Mountain Pass NW': 'Bumper Cave NW', + 'Mountain Pass SE': 'Bumper Cave SE', 'Zora Waterfall SE': 'Catfish SE', 'Lost Woods Pass NW': 'Skull Woods Pass NW', 'Lost Woods Pass NE': 'Skull Woods Pass NE', @@ -1447,18 +1450,18 @@ parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW', OWExitTypes = { 'OWEdge': [], 'Ledge': ['West Death Mountain Drop', - 'Spectacle Rock Drop', - 'East Death Mountain Spiral Ledge Drop', - 'East Death Mountain Fairy Ledge Drop', - 'East Death Mountain Mimic Ledge Drop', + 'Spectacle Rock Ledge Drop', + 'EDM To Spiral Ledge Drop', + 'EDM To Fairy Ledge Drop', + 'EDM To Mimic Ledge Drop', + 'EDM Ledge Drop', 'Spiral Ledge Drop', - 'Mimic Ledge Drop', 'Spiral Mimic Ledge Drop', 'Fairy Ascension Ledge Drop', 'Fairy Ascension Plateau Ledge Drop', 'TR Pegs Ledge Drop', - 'Mountain Entry Entrance Ledge Drop', - 'Mountain Entry Ledge Drop', + 'Mountain Pass Entry Ledge Drop', + 'Mountain Pass Ledge Drop', 'Zora Waterfall Water Drop', 'Bonk Rock Ledge Drop', 'Graveyard Ledge Drop', @@ -1471,15 +1474,15 @@ OWExitTypes = { 'Hyrule Castle Ledge Courtyard Drop', 'Wooden Bridge Water Drop', 'Wooden Bridge Northeast Water Drop', - 'Sand Dunes Ledge Drop', - 'Stone Bridge East Ledge Drop', - 'Tree Line Ledge Drop', - 'Eastern Palace Ledge Drop', + 'Sand Dunes Cliff Ledge Drop', + 'Stone Bridge East Cliff Ledge Drop', + 'Tree Line Cliff Ledge Drop', + 'Eastern Palace Cliff Ledge Drop', 'Maze Race Ledge Drop', 'Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', - 'Lake Hylia Area Cliff Ledge Drop', + 'Lake Hylia Northwest Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', @@ -1491,7 +1494,6 @@ OWExitTypes = { 'Checkerboard Ledge Drop', 'Desert Mouth Drop', 'Desert Teleporter Drop', - 'Desert Boss Cliff Ledge Drop', 'Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', @@ -1504,29 +1506,29 @@ OWExitTypes = { 'Lake Hylia Island Water Drop', 'Desert Pass Ledge Drop', 'Octoballoon Waterfall Water Drop', - 'Dark Death Mountain Drop (West)', - 'Dark Death Mountain Drop (East)', + 'West Dark Death Mountain Drop', + 'East Dark Death Mountain Drop', 'Floating Island Drop', 'Turtle Rock Tail Ledge Drop', 'Turtle Rock Ledge Drop', 'Bumper Cave Ledge Drop', - 'Bumper Cave Entrance Drop', + 'Bumper Cave Entry Drop', 'Qirn Jump Water Drop', 'Dark Witch Water Drop', 'Dark Witch Northeast Water Drop', 'Catfish Approach Bottom Ledge Drop', 'Catfish Approach Water Drop', 'Catfish Approach Ledge Drop', - 'Shield Shop Fence (Outer) Ledge Drop', - 'Shield Shop Fence (Inner) Ledge Drop', + 'Shield Shop Fence Drop (Outer)', + 'Shield Shop Fence Drop (Inner)', 'Pyramid Exit Ledge Drop', 'Broken Bridge Water Drop', 'Broken Bridge Northeast Water Drop', 'Broken Bridge West Water Drop', - 'Dark Dunes Ledge Drop', - 'Hammer Bridge North Ledge Drop', - 'Dark Tree Line Ledge Drop', - 'Palace of Darkness Ledge Drop', + 'Dark Dunes Cliff Ledge Drop', + 'Hammer Bridge North Cliff Ledge Drop', + 'Dark Tree Line Cliff Ledge Drop', + 'Palace of Darkness Cliff Ledge Drop', 'Dig Game To Ledge Drop', 'Dig Game Ledge Drop', 'Frog Ledge Drop', @@ -1534,8 +1536,8 @@ OWExitTypes = { 'Dark Bonk Rocks Cliff Ledge Drop', 'Bomb Shop Cliff Ledge Drop', 'Hammer Bridge South Cliff Ledge Drop', - 'Ice Lake Moat Bomb Jump', - 'Ice Lake Area Cliff Ledge Drop', + 'Ice Lake Iceberg Bomb Jump', + 'Ice Lake Northwest Cliff Ledge Drop', 'Ice Palace Island FAWT Ledge Drop', 'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', @@ -1543,7 +1545,7 @@ OWExitTypes = { 'Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Portal Cliff Ledge Drop', 'Hype Cliff Ledge Drop', - 'Misery Mire Teleporter Ledge Drop', + 'Mire Teleporter Ledge Drop', 'Mire Cliff Ledge Drop', 'Dark Checkerboard Cliff Ledge Drop', 'Archery Game Cliff Ledge Drop', @@ -1564,30 +1566,30 @@ OWExitTypes = { 'DM Hammer Bridge (West)', 'DM Hammer Bridge (East)', 'Floating Island Bridge (East)', - 'Fairy Ascension Rocks (North)', + 'Fairy Ascension Rocks (Inner)', 'DM Broken Bridge (West)', 'DM Broken Bridge (East)', 'Spiral Mimic Bridge (West)', 'Spiral Mimic Bridge (East)', 'Spiral Ledge Approach', 'Mimic Ledge Approach', - 'Fairy Ascension Rocks (South)', + 'Fairy Ascension Rocks (Outer)', 'Floating Island Bridge (West)', 'TR Pegs Ledge Entry', 'TR Pegs Ledge Leave', - 'Mountain Entry Entrance Rock (West)', - 'Mountain Entry Entrance Rock (East)', + 'Mountain Pass Rock (Outer)', + 'Mountain Pass Rock (Inner)', 'Zora Waterfall Water Entry', - 'Zora Waterfall Water Approach', + 'Zora Waterfall Approach', 'Zora Waterfall Landing', 'Lost Woods Pass Hammer (North)', 'Lost Woods Pass Hammer (South)', 'Lost Woods Pass Rock (North)', 'Lost Woods Pass Rock (South)', - 'Kings Grave Outer Rocks', + 'Kings Grave Rocks (Outer)', 'Graveyard Ladder (Bottom)', 'Graveyard Ladder (Top)', - 'Kings Grave Inner Rocks', + 'Kings Grave Rocks (Inner)', 'River Bend Water Drop', 'River Bend West Pier', 'River Bend East Water Drop', @@ -1601,33 +1603,33 @@ OWExitTypes = { 'Kakariko Southwest Bush (South)', 'Kakariko Yard Bush (North)', 'Hyrule Castle Main Gate (South)', - 'Hyrule Castle Inner East Rock', + 'Hyrule Castle East Rock (Inner)', 'Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Courtyard Bush (North)', - 'Hyrule Castle Outer East Rock', + 'Hyrule Castle East Rock (Outer)', 'Wooden Bridge Bush (South)', 'Wooden Bridge Bush (North)', - 'Bat Cave Ledge Peg', - 'Bat Cave Ledge Peg (East)', + 'Blacksmith Ledge Peg (West)', + 'Blacksmith Ledge Peg (East)', 'Maze Race Game', - 'Stone Bridge Northbound', - 'Stone Bridge Southbound', - 'Desert Palace Statue Move', + 'Stone Bridge (Northbound)', + 'Stone Bridge (Southbound)', + 'Desert Statue Move', 'Checkerboard Ledge Approach', - 'Desert Ledge Outer Rocks', - 'Desert Ledge Inner Rocks', + 'Desert Ledge Rocks (Outer)', + 'Desert Ledge Rocks (Inner)', 'Checkerboard Ledge Leave', 'Flute Boy Bush (South)', - 'Cave 45 Inverted Approach', + 'Cave 45 Approach', 'Flute Boy Bush (North)', - 'Cave 45 Inverted Leave', + 'Cave 45 Leave', 'C Whirlpool Rock (Bottom)', 'C Whirlpool Rock (Top)', - 'C Whirlpool Pegs (Right)', - 'C Whirlpool Pegs (Left)', + 'C Whirlpool Pegs (Outer)', + 'C Whirlpool Pegs (Inner)', 'C Whirlpool Water Entry', 'C Whirlpool Landing', 'Statues Water Entry', @@ -1648,14 +1650,15 @@ OWExitTypes = { 'Middle Aged Man', 'Octoballoon Water Drop', 'Octoballoon Pier', - 'Skull Woods Bush Rock (East)', - 'Skull Woods Bush Rock (West)', + 'Skull Woods Rock (East)', + 'Skull Woods Rock (West)', 'Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Bush (East)', - 'GT Entry Approach', - 'GT Entry Leave', + 'GT Approach', + 'GT Leave', 'East Dark Death Mountain Bushes', - 'Bumper Cave Entrance Rock', + 'Bumper Cave Rock (Outer)', + 'Bumper Cave Rock (Inner)', 'Skull Woods Pass Bush Row (West)', 'Skull Woods Pass Bush Row (East)', 'Skull Woods Pass Bush (North)', @@ -1670,8 +1673,8 @@ OWExitTypes = { 'Dark Witch Rock (North)', 'Catfish Approach Rocks (West)', 'Catfish Approach Rocks (East)', - 'Village of Outcasts Pegs', - 'Grassy Lawn Pegs', + 'Bush Yard Pegs (Outer)', + 'Bush Yard Pegs (Inner)', 'Pyramid Crack', 'Broken Bridge Hammer Rock (South)', 'Broken Bridge Hammer Rock (North)', @@ -1689,8 +1692,8 @@ OWExitTypes = { 'Stumpy Approach Bush (North)', 'Dark C Whirlpool Rock (Bottom)', 'Dark C Whirlpool Rock (Top)', - 'Dark C Whirlpool Pegs (Right)', - 'Dark C Whirlpool Pegs (Left)', + 'Dark C Whirlpool Pegs (Outer)', + 'Dark C Whirlpool Pegs (Inner)', 'Dark C Whirlpool Water Entry', 'Dark C Whirlpool Landing', 'Hype Cave Water Entry', @@ -1698,7 +1701,7 @@ OWExitTypes = { 'Ice Lake Northeast Water Drop', 'Ice Lake Northeast Pier', 'Ice Lake Northeast Pier Hop', - 'Ice Lake Moat Water Entry', + 'Ice Lake Iceberg Water Entry', 'Bomber Corner Water Drop', 'Bomber Corner Pier' ], @@ -1706,21 +1709,21 @@ OWExitTypes = { 'East Death Mountain Teleporter', 'TR Pegs Teleporter', 'Kakariko Teleporter', - 'Top of Pyramid', - 'Top of Pyramid (Inner)', + 'Castle Gate Teleporter', + 'Castle Gate Teleporter (Inner)', 'East Hyrule Teleporter', 'Desert Teleporter', 'South Hyrule Teleporter', 'Lake Hylia Teleporter', 'Dark Death Mountain Teleporter (West)', - 'Dark Death Mountain Teleporter (East)', + 'East Dark Death Mountain Teleporter', 'Turtle Rock Teleporter', 'West Dark World Teleporter', - 'Post Aga Inverted Teleporter', + 'Post Aga Teleporter', 'East Dark World Teleporter', - 'Misery Mire Teleporter', + 'Mire Teleporter', 'South Dark World Teleporter', - 'Ice Palace Teleporter' + 'Ice Lake Teleporter' ], 'Whirlpool': ['Zora Whirlpool', 'Kakariko Pond Whirlpool', diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 2372993f..0e05246d 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -37,7 +37,7 @@ def get_invalid_mirror_bunny_entrances(): yield 'Hype Cave' yield 'Bonk Fairy (Dark)' yield 'Thieves Town' - yield 'Dark World Hammer Peg Cave' + yield 'Hammer Peg Cave' yield 'Brewery' yield 'Hookshot Cave' yield 'Dark Lake Hylia Ledge Fairy' @@ -148,7 +148,7 @@ def get_boots_clip_exits_lw(world, player): for names, parent_regions, target_regions in boots_clips: region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions), True) if region_pair and region_pair[2]: - assert(region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}') + assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}' yield(region_pair[0], region_pair[1], region_pair[2]) @@ -164,7 +164,7 @@ def get_boots_clip_exits_dw(world, player): for names, parent_regions, target_regions in boots_clips: region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions), False) if region_pair and region_pair[2]: - assert(region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}') + assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}' yield(region_pair[0], region_pair[1], region_pair[2]) @@ -191,7 +191,7 @@ def get_mirror_clip_spots(world, player): for names, parent_regions, target_regions in mirror_clips: region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions), False) if region_pair and region_pair[2] and not world.is_tile_lw_like(OWTileRegions[region_pair[1]], player): - assert(region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}') + assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}' yield(region_pair[0], region_pair[1], region_pair[2]) @@ -205,7 +205,7 @@ def get_mirror_offset_spots(world, player): for names, parent_regions, target_regions, path_to in mirror_offsets: region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions, path_to), False) if region_pair and region_pair[2] and not world.is_tile_lw_like(OWTileRegions[region_pair[1]], player): - assert(region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}') + assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}' yield(region_pair[0], region_pair[1], region_pair[2], region_pair[3]) @@ -347,19 +347,19 @@ def set_owg_rules(player, world, connections, default_rule): connection.access_rule = rule -glitch_regions = (['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs'], - ['Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs']) +glitch_regions = (['Central Cliffs', 'Eastern Cliff', 'Desert Northern Cliffs'], + ['Dark Central Cliffs', 'Darkness Cliff', 'Mire Northern Cliffs']) # same screen clips, no Tile Flip OWR implications boots_clips_local = [ # (name, from_region, to_region) ('Hera Ascent Clip', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists ('WDDM Bomb Clip', 'West Dark Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists - ('Ganons Tower Screen Wrap Clip', 'West Dark Death Mountain (Bottom)', 'GT Approach'), # This only gets you to the GT entrance + ('Ganons Tower Screen Wrap Clip', 'West Dark Death Mountain (Bottom)', 'GT Stairs'), # This only gets you to the GT entrance ('Spectacle Rock Ledge Clip', 'West Death Mountain (Top)', 'Spectacle Rock Ledge'), ('Floating Island Clip', 'East Death Mountain (Top East)', 'Death Mountain Floating Island'), ('Floating Island Return Clip', 'Death Mountain Floating Island', 'East Death Mountain (Top East)'), - #('DW Floating Island Clip', 'East Dark Death Mountain (Bottom)', 'Dark Death Mountain Floating Island'), #cannot guarantee camera correction + #('DW Floating Island Clip', 'East Dark Death Mountain (Bottom)', 'Death Mountain Floating Island'), #cannot guarantee camera correction ('EDM East Dropdown Clip', 'East Death Mountain (Top East)', 'East Death Mountain (Bottom Left)'), ('EDM Hammer Bypass Teleport', 'East Death Mountain (Top West)', 'East Death Mountain (Top East)'), ('EDDM West Dropdown Clip', 'East Dark Death Mountain (Top)', 'East Dark Death Mountain (Bottom Left)'), @@ -367,14 +367,14 @@ boots_clips_local = [ # (name, from_region, to_region) ('WDDM To EDDM Bottom Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'), ('TR Bridge Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge'), - ('TR Pegs Ledge Clip', 'Death Mountain TR Pegs', 'Death Mountain TR Pegs Ledge'), - ('TR Pegs Ledge Descent Clip', 'Death Mountain TR Pegs Ledge', 'Death Mountain TR Pegs'), # inverted only, but doesn't hurt to exist always + ('TR Pegs Ledge Clip', 'Death Mountain TR Pegs Area', 'Death Mountain TR Pegs Ledge'), + ('TR Pegs Ledge Descent Clip', 'Death Mountain TR Pegs Ledge', 'Death Mountain TR Pegs Area'), # inverted only, but doesn't hurt to exist always ('Turtle Rock Ledge Clip', 'Turtle Rock Area', 'Turtle Rock Ledge'), - ('Mountain Entry To Ledge Clip', 'Mountain Entry Area', 'Mountain Entry Ledge'), + ('Mountain Pass To Ledge Clip', 'Mountain Pass Area', 'Mountain Pass Ledge'), ('Bumper Cave Ledge Clip', 'Bumper Cave Area', 'Bumper Cave Ledge'), - ('Mountain Ledge Drop Clip', 'Mountain Entry Ledge', 'Mountain Entry Entrance'), - ('Bumper Cave Ledge Drop Clip', 'Bumper Cave Ledge', 'Bumper Cave Entrance'), + ('Mountain Ledge Drop Clip', 'Mountain Pass Ledge', 'Mountain Pass Entry'), + ('Bumper Cave Ledge Drop Clip', 'Bumper Cave Ledge', 'Bumper Cave Entry'), ('Potion Shop Northbound Rock Bypass Clip', 'Potion Shop Area', 'Potion Shop Northeast'), ('Potion Shop Southbound Rock Bypass Clip', 'Potion Shop Northeast', 'Potion Shop Area'), @@ -383,19 +383,19 @@ boots_clips_local = [ # (name, from_region, to_region) ('Hyrule Castle To Water Clip', 'Hyrule Castle Area', 'Hyrule Castle Water'), #fake flipper - #('Bat Cave River Clip Spot', 'Blacksmith Area', 'Bat Cave Ledge'), #cannot guarantee camera correction + #('Bat Cave River Clip Spot', 'Blacksmith Area', 'Blacksmith Ledge'), #TODO: This should be added in MG (screenwrap transition) ('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize'), ('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water'), #requires flippers ('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water'), #requires flippers - ('Desert To Teleporter Clip', 'Desert Area', 'Desert Palace Teleporter Ledge'), - ('Mire To Teleporter Clip', 'Misery Mire Area', 'Misery Mire Teleporter Ledge'), + ('Desert To Teleporter Clip', 'Desert Area', 'Desert Teleporter Ledge'), + ('Mire To Teleporter Clip', 'Mire Area', 'Mire Teleporter Ledge'), ('Desert To Bombos Tablet Clip', 'Desert Area', 'Bombos Tablet Ledge'), - ('Lake Hylia To Shore Clip', 'Lake Hylia Area', 'Lake Hylia South Shore'), - ('Ice Lake To Shore Clip', 'Ice Lake Area', 'Ice Lake Ledge (West)') + ('Lake Hylia To Shore Clip', 'Lake Hylia Northwest Bank', 'Lake Hylia South Shore'), + ('Ice Lake To Shore Clip', 'Ice Lake Northwest Bank', 'Ice Lake Southwest Ledge') #('Desert Pass To Zora Clip', 'Desert Pass Area', 'Zoras Domain', ) #revisit when Zora is shuffled ] @@ -416,20 +416,20 @@ boots_clips = [ (['Kings Grave DMD Clip', 'Dark Kings Grave DMD Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Kings Grave Area', None]), (['EDM to WDM Top Clip', 'EDDM To WDDM Clip'], ['East Death Mountain (Top West)', 'East Dark Death Mountain (Top)'], ['West Death Mountain (Top)', 'West Dark Death Mountain (Top)']), - (['EDM To TR Pegs Clip', 'EDDM To TR Clip'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)'], ['Death Mountain TR Pegs', None]), + (['EDM To TR Pegs Clip', 'EDDM To TR Clip'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)'], ['Death Mountain TR Pegs Area', None]), (['EDM DMD FAWT Clip', 'Dark Witch DMD FAWT Clip'], ['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], ['Potion Shop Area', 'Dark Witch Area']), (['WDM DMD To River Bend Clip', 'WDDM DMD To Qirn Jump Clip'], ['East Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom Left)'], ['River Bend Area', 'Qirn Jump Area']), (['EDM DMD To River Bend Clip', 'EDDM DMD To Qirn Jump Clip'], ['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], ['River Bend Area', 'Qirn Jump Area']), - (['TR Pegs To EDM Clip', 'TR To EDDM Clip'], ['Death Mountain TR Pegs', 'Turtle Rock Area'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)']), - (['Zora DMD Clip', 'Catfish DMD Clip'], ['Death Mountain TR Pegs', 'Turtle Rock Area'], ['Zora Waterfall Area', 'Catfish Area']), + (['TR Pegs To EDM Clip', 'TR To EDDM Clip'], ['Death Mountain TR Pegs Area', 'Turtle Rock Area'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)']), + (['Zora DMD Clip', 'Catfish DMD Clip'], ['Death Mountain TR Pegs Area', 'Turtle Rock Area'], ['Zora Waterfall Area', 'Catfish Area']), - (['Mountain Entry To Pond Clip', 'Bumper Cave To Pond Clip'], ['Mountain Entry Area', 'Bumper Cave Area'], ['Kakariko Pond Area', 'Outcast Pond Area']), + (['Mountain Pass To Pond Clip', 'Bumper Cave To Pond Clip'], ['Mountain Pass Area', 'Bumper Cave Area'], ['Kakariko Pond Area', 'Outcast Pond Area']), (['Zora Waterfall Ledge Clip', 'Catfish Ledge Clip'], ['Zora Waterfall Area', 'Catfish Area'], ['Zora Approach Area', 'Catfish Approach Area']), #(['Pond DMA Clip', 'Dark Pond DMA Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)']), #cannot guarantee camera correction - (['Pond To Mountain Entry Clip', 'Pond To Bumper Cave Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Mountain Entry Area', 'Bumper Cave Area']), + (['Pond To Mountain Pass Clip', 'Pond To Bumper Cave Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Mountain Pass Area', 'Bumper Cave Area']), (['Pond To Bonk Rocks Clip', 'Pond To Chapel Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Bonk Rock Ledge', 'Dark Chapel Area']), (['River Bend To Potion Shop Clip', 'Qirn Jump To Dark Witch Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], ['Potion Shop Area', 'Dark Witch Area']), @@ -443,8 +443,8 @@ boots_clips = [ (['Zora Approach To Potion Shop Clip', 'Catfish Approach To Dark Witch Clip'], ['Zora Approach Area', 'Catfish Approach Area'], ['Potion Shop Area', 'Dark Witch Area']), (['Zora Approach To PoD Clip', 'Catfish Approach To PoD Clip'], ['Zora Approach Area', 'Catfish Approach Area'], [None, 'Palace of Darkness Area']), - (['Kakariko Bomb Hut Clip', 'VoO To Dig Game Clip'], ['Kakariko Southwest', 'Village of Outcasts Area'], ['Maze Race Area', 'Dig Game Area']), - (['Kakariko To Dig Game Hook Clip', 'VoO To Dig Game Hook Clip'], ['Kakariko Southwest', 'Village of Outcasts Area'], [None, 'Dig Game Ledge']), #requires hookshot + (['Kakariko Bomb Hut Clip', 'VoO To Dig Game Clip'], ['Kakariko Southwest', 'Village of Outcasts'], ['Maze Race Area', 'Dig Game Area']), + (['Kakariko To Dig Game Hook Clip', 'VoO To Dig Game Hook Clip'], ['Kakariko Southwest', 'Village of Outcasts'], [None, 'Dig Game Ledge']), #requires hookshot (['Forgotten Forest To Blacksmith Clip', None], ['Forgotten Forest Area', None], ['Hyrule Castle Water', 'Pyramid Water']), #fake flipper @@ -452,30 +452,30 @@ boots_clips = [ (['Wooden Bridge To Water Clip', 'Broken Bridge To Water Clip'], ['Wooden Bridge Area', 'Broken Bridge West'], [None, 'Pyramid Water']), #fake flipper (['Eastern Palace To Zora Approach Clip', None], ['Eastern Palace Area', None], ['Zora Approach Area', 'Catfish Approach Area']), - (['Eastern Palace To Nook Clip', None], ['Eastern Palace Area', None], ['Eastern Nook Area', 'Palace of Darkness Nook Area']), + (['Eastern Palace To Nook Clip', None], ['Eastern Palace Area', None], ['Eastern Nook Area', 'Darkness Nook Area']), (['Eastern Palace To Cliff Clip', 'PoD To Cliff Clip'], ['Eastern Palace Area', 'Palace of Darkness Area'], ['Eastern Cliff', 'Darkness Cliff']), (['Sand Dunes To Cliff Clip', 'Dark Dunes To Cliff Clip'], ['Sand Dunes Area', 'Dark Dunes Area'], ['Eastern Cliff', 'Darkness Cliff']), (['Sand Dunes To Water Clip', 'Dark Dunes To Water Clip'], ['Sand Dunes Area', 'Dark Dunes Area'], [None, 'Pyramid Water']), #fake flipper - (['Maze Race To Desert Ledge Clip', 'Dig Game To Mire Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Ledge', 'Misery Mire Area']), - (['Maze Race To Desert Boss Clip', 'Dig Game To Desert Boss Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Palace Entrance (North) Spot', None]), - (['Suburb To Cliff Clip', 'Archery Game To Cliff Clip'], ['Kakariko Suburb Area', 'Archery Game Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Maze Race To Desert Ledge Clip', 'Dig Game To Mire Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Ledge', 'Mire Area']), + (['Maze Race To Desert Boss Clip', 'Dig Game To Desert Boss Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Ledge Keep', None]), + (['Suburb To Cliff Clip', 'Archery Game To Cliff Clip'], ['Kakariko Suburb Area', 'Archery Game Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), (['Central Bonk Rocks To Cliff Clip', 'Dark Bonk Rocks To Cliff Clip'], ['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['Links House To Cliff Clip', 'Bomb Shop To Cliff Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['Stone Bridge To Cliff Clip', 'Hammer Bridge To Cliff Clip'], ['Stone Bridge South Area', 'Hammer Bridge South Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['Eastern Nook To Eastern Clip', None], ['Eastern Nook Area', None], ['Eastern Palace Area', 'Palace of Darkness Area']), - (['Eastern Nook To Ice Cave FAWT Clip', 'PoD Nook To Shopping Mall FAWT Clip'], ['Eastern Nook Area', 'Palace of Darkness Nook Area'], ['Ice Cave Area', 'Shopping Mall Area']), + (['Eastern Nook To Ice Cave FAWT Clip', 'PoD Nook To Shopping Mall FAWT Clip'], ['Eastern Nook Area', 'Darkness Nook Area'], ['Ice Cave Area', 'Shopping Mall Area']), (['Links To Bridge FAWT Clip', 'Bomb Shop To Hammer Bridge FAWT Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Stone Bridge North Area', 'Hammer Bridge North Area']), #fake flipper (['Stone Bridge To Water Clip', 'Hammer Bridge To Water Clip'], ['Stone Bridge North Area', 'Hammer Bridge North Area'], [None, 'Pyramid Water']), #fake flipper (['Desert To Maze Race Clip', None], ['Desert Ledge', None], ['Maze Race Area', 'Dig Game Area']), - (['Desert To Cliff Clip', 'Mire To Cliff Clip'], ['Desert Area', 'Misery Mire Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Desert To Cliff Clip', 'Mire To Cliff Clip'], ['Desert Area', 'Mire Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), - (['Flute Boy To Cliff Clip', 'Stumpy To Cliff Clip'], ['Flute Boy Approach Area', 'Stumpy Approach Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), - (['Cave 45 To Cliff Clip', None], ['Cave 45 Ledge', None], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Flute Boy To Cliff Clip', 'Stumpy To Cliff Clip'], ['Flute Boy Approach Area', 'Stumpy Approach Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), + (['Cave 45 To Cliff Clip', None], ['Cave 45 Ledge', None], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), (['C Whirlpool To Cliff Clip', 'Dark C Whirlpool To Cliff Clip'], ['C Whirlpool Area', 'Dark C Whirlpool Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['C Whirlpool Outer To Cliff Clip', 'Dark C Whirlpool Outer To Cliff Clip'], ['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], ['Central Cliffs', 'Dark Central Cliffs']), @@ -483,25 +483,25 @@ boots_clips = [ (['Statues To Cliff Clip', 'Hype To Cliff Clip'], ['Statues Area', 'Hype Cave Area'], ['Central Cliffs', 'Dark Central Cliffs']), - (['Lake Hylia To Statues Clip', 'Ice Lake To Hype Clip'], ['Lake Hylia Area', 'Ice Lake Area'], ['Statues Area', 'Hype Cave Area']), - (['Lake Hylia To South Pass Clip', 'Ice Lake To South Pass Clip'], ['Lake Hylia Area', 'Ice Lake Area'], ['South Pass Area', 'Dark South Pass Area']), + (['Lake Hylia To Statues Clip', 'Ice Lake To Hype Clip'], ['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], ['Statues Area', 'Hype Cave Area']), + (['Lake Hylia To South Pass Clip', 'Ice Lake To South Pass Clip'], ['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], ['South Pass Area', 'Dark South Pass Area']), - (['Desert Pass To Cliff Clip', 'Swamp Nook To Cliff Clip'], ['Desert Pass Area', 'Swamp Nook Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), - (['Desert Pass Southeast To Cliff Clip', None], ['Desert Pass Southeast', None], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Desert Pass To Cliff Clip', 'Swamp Nook To Cliff Clip'], ['Desert Pass Area', 'Swamp Nook Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), + (['Desert Pass Southeast To Cliff Clip', None], ['Desert Pass Southeast', None], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), - (['Dam To Cliff Clip', 'Swamp To Cliff Clip'], ['Dam Area', 'Swamp Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Dam To Cliff Clip', 'Swamp To Cliff Clip'], ['Dam Area', 'Swamp Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']), (['Dam To Desert Pass Southeast Clip', 'Swamp To Desert Pass Southeast Clip'], ['Dam Area', 'Swamp Area'], ['Desert Pass Southeast', None]), - (['South Pass To Lake Hylia Clip', 'South Pass To Ice Lake Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia Area', 'Ice Lake Area']), - (['South Pass To Shore Clip', 'South Pass To Dark Shore Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia South Shore', 'Ice Lake Ledge (West)']), - #(['Octoballoon To Shore Clip', 'Bomber Corner To Shore Clip'], ['Octoballoon Area', 'Bomber Corner Area'], ['Lake Hylia South Shore', 'Ice Lake Ledge (East)']), #map wrap hardlock risk + (['South Pass To Lake Hylia Clip', 'South Pass To Ice Lake Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank']), + (['South Pass To Shore Clip', 'South Pass To Dark Shore Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia South Shore', 'Ice Lake Southwest Ledge']), + #(['Octoballoon To Shore Clip', 'Bomber Corner To Shore Clip'], ['Octoballoon Area', 'Bomber Corner Area'], ['Lake Hylia South Shore', 'Ice Lake Southeast Ledge']), #map wrap hardlock risk (['HC Water To Blacksmith Clip', 'Pyramid Water To Hammerpegs Clip'], ['Hyrule Castle Water', 'Pyramid Water'], ['Blacksmith Area', 'Hammer Pegs Area']), #TODO: THIS IS NOT A BOOTS CLIP, this is a normal connection that needs to occur somewhere ([None, 'Pyramid Water To Bomb Shop Clip'], [None, 'Pyramid Water'], ['Links House Area', 'Big Bomb Shop Area']) #TODO: THIS IS NOT A BOOTS CLIP, this is a normal connection that needs to occur somewhere ] mirror_clips_local = [ - ('Desert East Mirror Clip', 'Misery Mire Area', 'Desert Palace Mouth'), + ('Desert East Mirror Clip', 'Mire Area', 'Desert Mouth'), ('EDDM Mirror Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'), ('EDDM Mirror Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge') ] @@ -512,5 +512,5 @@ mirror_clips = [ mirror_offsets = [ (['DM Offset Mirror', 'DDM Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Courtyard Northeast', 'Pyramid Crack'], ['Pyramid Area', 'Hyrule Castle Courtyard']), - (['DM To HC Ledge Offset Mirror', 'DDM To HC Ledge Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', None], ['Pyramid Area', None]) + (['DM To HC Ledge Offset Mirror', 'DDM To Pyramid Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', 'Pyramid Area'], ['Pyramid Area', 'Hyrule Castle Area']) ] \ No newline at end of file diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 61fe3668..d46ddac1 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.0.0' +version_number = '0.3.2.2' # branch indicator is intentionally different across branches version_branch = '' @@ -407,9 +407,9 @@ def link_overworld(world, player): logging.getLogger('').debug('Shuffling flute spots') def connect_flutes(flute_destinations): for o in range(0, len(flute_destinations)): - owslot = flute_destinations[o] - regions = flute_data[owslot][0] - if not world.is_tile_swapped(flute_data[owslot][1], player): + owid = flute_destinations[o] + regions = flute_data[owid][0] + if not world.is_tile_swapped(owid, player): connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player) else: connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player) @@ -417,11 +417,15 @@ def link_overworld(world, player): if world.owFluteShuffle[player] == 'vanilla': connect_flutes(default_flute_connections) else: + flute_spots = 8 flute_pool = list(flute_data.keys()) new_spots = list() ignored_regions = set() + used_flute_regions = [] + forbidden_spots = [] + forbidden_regions = [] - def addSpot(owid, ignore_proximity): + def addSpot(owid, ignore_proximity, forced): if world.owFluteShuffle[player] == 'balanced': def getIgnored(regionname, base_owid, owid): region = world.get_region(regionname, player) @@ -430,23 +434,28 @@ def link_overworld(world, player): if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid): new_ignored.add(exit.connected_region.name) getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name]) + if regionname in one_way_ledges: + for ledge_region in one_way_ledges[regionname]: + if ledge_region not in new_ignored: + new_ignored.add(ledge_region) + getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region]) - if not world.is_tile_swapped(flute_data[owid][1], player): + if not world.is_tile_swapped(owid, player): new_region = flute_data[owid][0][0] else: new_region = flute_data[owid][0][1] - if new_region in ignored_regions: + if new_region in ignored_regions and not forced: return False new_ignored = {new_region} getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region]) - if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): + if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): return False ignored_regions.update(new_ignored) if owid in flute_pool: flute_pool.remove(owid) - if ignore_proximity: + if ignore_proximity and not forced: logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') new_spots.append(owid) @@ -455,23 +464,65 @@ def link_overworld(world, player): logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}') return True + if world.customizer: + custom_spots = world.customizer.get_owflutespots() + if custom_spots and player in custom_spots: + if 'force' in custom_spots[player]: + for id in custom_spots[player]['force']: + owid = id & 0xBF + addSpot(owid, True, True) + flute_spots -= 1 + if not world.is_tile_swapped(owid, player): + used_flute_regions.append(flute_data[owid][0][0]) + else: + used_flute_regions.append(flute_data[owid][0][1]) + if 'forbid' in custom_spots[player]: + for id in custom_spots[player]['forbid']: + owid = id & 0xBF + if owid not in new_spots: + forbidden_spots.append(owid) + if not world.is_tile_swapped(owid, player): + forbidden_regions.append(flute_data[owid][0][0]) + else: + forbidden_regions.append(flute_data[owid][0][1]) + # determine sectors (isolated groups of regions) to place flute spots - flute_regions = {(f[0][0] if (f[1] not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items()} + flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots} flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]] flute_sectors = [s for s in flute_sectors if len(s[1]) > 0] region_total = sum([c for c,_ in flute_sectors]) sector_total = len(flute_sectors) + empty_sector_total = 0 + sector_has_spot = [] - # reserve a number of flute spots for each sector - flute_spots = 8 + # determine which sectors still need a flute spot for sector in flute_sectors: + already_has_spot = any(region in sector for region in used_flute_regions) + sector_has_spot.append(already_has_spot) + if not already_has_spot: + empty_sector_total += 1 + if flute_spots < empty_sector_total: + logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail') + # pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable + for i in range(len(flute_sectors)): + if not sector_has_spot[i]: + sector_has_spot[i] = True + empty_sector_total -= 1 + if flute_spots == empty_sector_total: + break + + # distribute flute spots for each sector + for i in range(len(flute_sectors)): + sector = flute_sectors[i] sector_total -= 1 - spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) + if not sector_has_spot[i]: + empty_sector_total -= 1 + spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) target_spots = len(new_spots) + spots_to_place logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)') - if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]: - addSpot(0x38, False) # guarantee desert/mire access + if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]): + addSpot(0x30, True, True) # guarantee desert/mire access random.shuffle(sector[1]) f = 0 @@ -482,8 +533,9 @@ def link_overworld(world, player): t += 1 if t > 5: raise GenerationException('Infinite loop detected in flute shuffle') - if sector[1][f] not in new_spots: - addSpot(flute_regions[sector[1][f]], t > 0) + owid = flute_regions[sector[1][f]] + if owid not in new_spots and owid not in forbidden_spots: + addSpot(owid, t > 0, False) f += 1 region_total -= sector[0] @@ -495,7 +547,6 @@ def link_overworld(world, player): connect_flutes(new_spots) # update spoiler - new_spots = list(map(lambda o: flute_data[o][1], new_spots)) s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)])) text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], s[0x00], s[0x03], s[0x05], @@ -632,17 +683,56 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): parity[5] -= 1 group_parity[group[0]] = parity - attempts = 1000 + # customizer adjustments + undefined_chance = 50 + flipped_groups = list() + always_removed = list() + if world.customizer: + if not do_grouped: + custom_flips = world.customizer.get_owtileflips() + if custom_flips and player in custom_flips: + custom_flips = custom_flips[player] + nonflipped_groups = list() + forced_flips = list() + forced_nonflips = list() + if 'undefined_chance' in custom_flips: + undefined_chance = custom_flips['undefined_chance'] + if 'force_flip' in custom_flips: + forced_flips = custom_flips['force_flip'] + if 'force_no_flip' in custom_flips: + forced_nonflips = custom_flips['force_no_flip'] + + for group in groups: + if any(owid in group[0] for owid in forced_nonflips): + nonflipped_groups.append(group) + if any(owid in group[0] for owid in forced_flips): + flipped_groups.append(group) + + # 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') + + for g in nonflipped_groups: + always_removed.append(g) + if undefined_chance == 0: + for g in [g for g in groups if g not in flipped_groups + always_removed]: + always_removed.append(g) + + attempts = 1 + if 0 < undefined_chance < 100: + # do roughly 1000 attempts at a full list + attempts = len(groups) - len(always_removed) + attempts = (attempts ** 1.9) + (attempts * 10) + 1 while True: if attempts == 0: # expected to only occur with custom flips raise GenerationException('Could not find valid tile flips') # tile shuffle happens here - removed = list() - for group in groups: - #if 0x1b in group[0] or 0x13 in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted - if random.randint(0, 1): - removed.append(group) + removed = copy.deepcopy(always_removed) + if 0 < undefined_chance < 100: + for group in [g for g in groups if g not in always_removed]: + if group not in flipped_groups and random.randint(1, 100) > undefined_chance: + removed.append(group) # save shuffled tiles to list new_results = [[],[],[]] @@ -666,9 +756,9 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): attempts -= 1 continue # ensure sanc can be placed in LW in certain modes - if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' 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 else 0) - free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0) + if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'swapped', 'crossed', 'insanity'] and world.mode[player] != 'inverted' 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: attempts -= 1 continue @@ -848,7 +938,6 @@ def create_flute_exits(world, player): exitname = 'Flute From ' + region.name exit = Entrance(region.player, exitname, region) exit.spot_type = 'Flute' - exit.access_rule = lambda state: state.can_flute(player) exit.connect(world.get_region('Flute Sky', player)) region.exits.append(exit) @@ -934,27 +1023,26 @@ def can_reach_smith(world, player): region = world.get_region(region_name, player) for exit in region.exits: if not found and exit.connected_region is not None: - if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], world.precollected_items)) and exit.spot_type == 'Flute': - fluteregion = exit.connected_region - for flutespot in fluteregion.exits: - if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: - explore_region(flutespot.connected_region.name, flutespot.connected_region) - elif exit.connected_region.name not in explored_regions \ - and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ - and exit.access_rule(blank_state): - explore_region(exit.connected_region.name, exit.connected_region) - elif exit.name == 'Sanctuary S': - sanc_region = exit.connected_region - if len(sanc_region.exits) and sanc_region.exits[0].name == 'Sanctuary Exit': - explore_region(sanc_region.exits[0].connected_region.name, sanc_region.exits[0].connected_region) + if exit.spot_type == 'Flute': + if any(map(lambda i: i.name == 'Ocarina (Activated)', world.precollected_items)): + for flutespot in exit.connected_region.exits: + if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: + explore_region(flutespot.connected_region.name, flutespot.connected_region) elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state): found = True + return + elif exit.connected_region.name not in explored_regions: + if (region.type == RegionType.Dungeon and exit.connected_region.name.endswith(' Portal')) \ + or (exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ + and exit.access_rule(blank_state)): + explore_region(exit.connected_region.name, exit.connected_region) blank_state = CollectionState(world) if world.mode[player] == 'standard': blank_state.collect(ItemFactory('Zelda Delivered', player), True) - if world.logic[player] in ['noglitches', 'minorglitches'] and world.get_region('Frog Prison', player).type == (RegionType.DarkWorld if not invFlag else RegionType.LightWorld): + if world.logic[player] in ['noglitches', 'minorglitches'] and not world.is_tile_swapped(0x29, player): blank_state.collect(ItemFactory('Titans Mitts', player), True) + blank_state.collect(ItemFactory('Moon Pearl', player), True) found = False explored_regions = list() @@ -965,7 +1053,11 @@ def can_reach_smith(world, player): explore_region(start_region) if not found: if not invFlag: - explore_region('Sanctuary') + 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) + else: + explore_region('Sanctuary') else: explore_region('Dark Sanctuary Hint') return found @@ -1050,7 +1142,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F region = base_world.get_region(region_name, player) for exit in region.exits: if exit.connected_region is not None: - if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], base_world.precollected_items)) and exit.spot_type == 'Flute': + if any(map(lambda i: i.name == 'Ocarina (Activated)', base_world.precollected_items)) and exit.spot_type == 'Flute': fluteregion = exit.connected_region for flutespot in fluteregion.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: @@ -1086,17 +1178,17 @@ def validate_layout(world, player): 'East Death Mountain (Bottom)': ['East Death Mountain (Top East)'], 'Kakariko Suburb Area': ['Maze Race Ledge'], 'Maze Race Ledge': ['Kakariko Suburb Area'], - 'Desert Area': ['Desert Ledge', 'Desert Palace Mouth'], + 'Desert Area': ['Desert Ledge', 'Desert Mouth'], 'East Dark Death Mountain (Top)': ['Dark Death Mountain Floating Island'], 'East Dark Death Mountain (Bottom)': ['East Dark Death Mountain (Top)'], 'Turtle Rock Area': ['Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge'], 'Dark Death Mountain Ledge': ['Turtle Rock Area'], 'Dark Death Mountain Isolated Ledge': ['Turtle Rock Area'], - 'Mountain Entry Entrance': ['West Death Mountain (Bottom)'], - 'Mountain Entry Ledge': ['West Death Mountain (Bottom)'], - 'West Death Mountain (Bottom)': ['Mountain Entry Ledge'], - 'Bumper Cave Entrance': ['Bumper Cave Ledge'] + 'Mountain Pass Entry': ['West Death Mountain (Bottom)'], + 'Mountain Pass Ledge': ['West Death Mountain (Bottom)'], + 'West Death Mountain (Bottom)': ['Mountain Pass Ledge'], + 'Bumper Cave Entry': ['Bumper Cave Ledge'] } sane_connectors = { # guaranteed dungeon access @@ -1150,14 +1242,14 @@ 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 == 'inverted': + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean'] and world.mode[player] == 'inverted': start_region = 'Dark Chapel Area' explore_region(start_region) if not world.is_tile_swapped(0x30, player): - start_region = 'Desert Palace Teleporter Ledge' + start_region = 'Desert Teleporter Ledge' else: - start_region = 'Misery Mire Teleporter Ledge' + start_region = 'Mire Teleporter Ledge' explore_region(start_region) if not world.is_tile_swapped(0x1b, player): @@ -1180,19 +1272,19 @@ def validate_layout(world, player): unreachable_count = len(unreachable_regions) for region_name in reversed(unreachable_regions): # check if can be accessed flute - if unreachable_regions[region_name].type == RegionType.LightWorld: + if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): owid = OWTileRegions[region_name] - if owid < 0x80 and any(f[1] == owid and region_name in f[0] for f in flute_data.values()): - if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in [0x03, 0x16, 0x18, 0x2c, 0x2f, 0x3b, 0x3f]: + if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid][0]: + if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections: unreachable_regions.pop(region_name) explore_region(region_name) break # check if entrances in region could be used to access region if world.shuffle[player] != 'vanilla': for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']: - if (entrance.name == 'Links House' and (world.mode == 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ - or (entrance.name == 'Big Bomb Shop' and (world.mode != 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ - or (entrance.name == 'Ganons Tower' and (world.mode != 'inverted' and not world.shuffle_ganon[player])) \ + if (entrance.name == 'Links House' and (world.mode[player] == 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ + or (entrance.name == 'Big Bomb Shop' and (world.mode[player] != 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ + or (entrance.name == 'Ganons Tower' and (world.mode[player] != 'inverted' and not world.shuffle_ganon[player])) \ or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['insanity']) \ or entrance.name == 'Tavern North': continue # these are fixed entrances and cannot be used for gaining access to region @@ -1217,206 +1309,206 @@ test_connections = [ ] # 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 = [('Old Man S&Q', 'Old Man House'), +mandatory_connections = [ + ('Old Man S&Q', 'Old Man House'), - # Intra-tile OW Connections - ('Lost Woods Bush (West)', 'Lost Woods East Area'), #pearl - ('Lost Woods Bush (East)', 'Lost Woods West Area'), #pearl - ('West Death Mountain Drop', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Drop', 'West Death Mountain (Top)'), - ('Old Man Drop Off', 'Old Man Drop Off'), - ('DM Hammer Bridge (West)', 'East Death Mountain (Top East)'), #hammer - ('DM Hammer Bridge (East)', 'East Death Mountain (Top West)'), #hammer - ('East Death Mountain Spiral Ledge Drop', 'Spiral Cave Ledge'), - ('Spiral Ledge Drop', 'East Death Mountain (Bottom)'), - ('East Death Mountain Fairy Ledge Drop', 'Fairy Ascension Ledge'), - ('Fairy Ascension Ledge Drop', 'Fairy Ascension Plateau'), - ('Fairy Ascension Plateau Ledge Drop', 'East Death Mountain (Bottom)'), - ('Fairy Ascension Rocks (North)', 'East Death Mountain (Bottom)'), #mitts - ('Fairy Ascension Rocks (South)', 'Fairy Ascension Plateau'), #mitts - ('DM Broken Bridge (West)', 'East Death Mountain (Bottom)'), #hookshot - ('DM Broken Bridge (East)', 'East Death Mountain (Bottom Left)'), #hookshot - ('TR Pegs Ledge Entry', 'Death Mountain TR Pegs Ledge'), #mitts - ('TR Pegs Ledge Leave', 'Death Mountain TR Pegs'), #mitts - ('TR Pegs Ledge Drop', 'Death Mountain TR Pegs'), - ('Mountain Entry Entrance Rock (West)', 'Mountain Entry Entrance'), #glove - ('Mountain Entry Entrance Rock (East)', 'Mountain Entry Area'), #glove - ('Mountain Entry Entrance Ledge Drop', 'Mountain Entry Area'), - ('Mountain Entry Ledge Drop', 'Mountain Entry Area'), - ('Zora Waterfall Landing', 'Zora Waterfall Area'), - ('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers - ('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers - ('Zora Waterfall Water Approach', 'Zora Waterfall Entryway'), #flippers - ('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer - ('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer - ('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts - ('Lost Woods Pass Rock (South)', 'Lost Woods Pass Portal Area'), #mitts - ('Bonk Rock Ledge Drop', 'Sanctuary Area'), - ('Graveyard Ledge Drop', 'Graveyard Area'), - ('Kings Grave Outer Rocks', 'Kings Grave Area'), #mitts - ('Kings Grave Inner Rocks', 'Graveyard Area'), #mitts - ('River Bend Water Drop', 'River Bend Water'), #flippers - ('River Bend East Water Drop', 'River Bend Water'), #flippers - ('River Bend West Pier', 'River Bend Area'), - ('River Bend East Pier', 'River Bend East Bank'), - ('Potion Shop Water Drop', 'Potion Shop Water'), #flippers - ('Potion Shop Northeast Water Drop', 'Potion Shop Water'), #flippers - ('Potion Shop Rock (South)', 'Potion Shop Northeast'), #glove - ('Potion Shop Rock (North)', 'Potion Shop Area'), #glove - ('Zora Approach Water Drop', 'Zora Approach Water'), #flippers - ('Zora Approach Rocks (West)', 'Zora Approach Ledge'), #mitts/boots - ('Zora Approach Rocks (East)', 'Zora Approach Area'), #mitts/boots - ('Zora Approach Bottom Ledge Drop', 'Zora Approach Ledge'), - ('Zora Approach Ledge Drop', 'Zora Approach Area'), - ('Kakariko Southwest Bush (North)', 'Kakariko Southwest'), #pearl - ('Kakariko Southwest Bush (South)', 'Kakariko Area'), #pearl - ('Kakariko Yard Bush (South)', 'Kakariko Grass Yard'), #pearl - ('Kakariko Yard Bush (North)', 'Kakariko Area'), #pearl - ('Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest'), #pearl - ('Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Area'), #pearl - ('Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Courtyard'), #pearl - ('Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Courtyard Northeast'), #pearl - ('Hyrule Castle Main Gate (South)', 'Hyrule Castle Courtyard'), #aga+mirror - ('Hyrule Castle Main Gate (North)', 'Hyrule Castle Area'), #aga+mirror - ('Hyrule Castle Ledge Drop', 'Hyrule Castle Area'), - ('Hyrule Castle Ledge Courtyard Drop', 'Hyrule Castle Courtyard'), - ('Hyrule Castle Inner East Rock', 'Hyrule Castle East Entry'), #glove - ('Hyrule Castle Outer East Rock', 'Hyrule Castle Area'), #glove - ('Wooden Bridge Bush (South)', 'Wooden Bridge Northeast'), #pearl - ('Wooden Bridge Bush (North)', 'Wooden Bridge Area'), #pearl - ('Wooden Bridge Water Drop', 'Wooden Bridge Water'), #flippers - ('Wooden Bridge Northeast Water Drop', 'Wooden Bridge Water'), #flippers - ('Bat Cave Ledge Peg', 'Bat Cave Ledge'), #hammer - ('Bat Cave Ledge Peg (East)', 'Blacksmith Area'), #hammer - ('Maze Race Game', 'Maze Race Prize'), #pearl - ('Maze Race Ledge Drop', 'Maze Race Area'), - ('Stone Bridge Southbound', 'Stone Bridge South Area'), - ('Stone Bridge Northbound', 'Stone Bridge North Area'), - ('Desert Palace Statue Move', 'Desert Palace Stairs'), #book - ('Desert Ledge Drop', 'Desert Area'), - ('Desert Ledge Outer Rocks', 'Desert Palace Entrance (North) Spot'), #glove - ('Desert Ledge Inner Rocks', 'Desert Ledge'), #glove - ('Checkerboard Ledge Drop', 'Desert Area'), - ('Desert Mouth Drop', 'Desert Area'), - ('Desert Teleporter Drop', 'Desert Area'), - ('Bombos Tablet Drop', 'Desert Area'), - ('Flute Boy Bush (North)', 'Flute Boy Approach Area'), #pearl - ('Flute Boy Bush (South)', 'Flute Boy Bush Entry'), #pearl - ('C Whirlpool Water Entry', 'C Whirlpool Water'), #flippers - ('C Whirlpool Landing', 'C Whirlpool Area'), - ('C Whirlpool Rock (Bottom)', 'C Whirlpool Outer Area'), #glove - ('C Whirlpool Rock (Top)', 'C Whirlpool Area'), #glove - ('C Whirlpool Pegs (Right)', 'C Whirlpool Portal Area'), #hammer - ('C Whirlpool Pegs (Left)', 'C Whirlpool Area'), #hammer - ('Statues Water Entry', 'Statues Water'), #flippers - ('Statues Landing', 'Statues Area'), - ('Lake Hylia Water Drop', 'Lake Hylia Water'), #flippers - ('Lake Hylia South Water Drop', 'Lake Hylia Water'), #flippers - ('Lake Hylia Northeast Water Drop', 'Lake Hylia Water'), #flippers - ('Lake Hylia Central Water Drop', 'Lake Hylia Water'), #flippers - ('Lake Hylia Island Water Drop', 'Lake Hylia Water'), #flippers - ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), - ('Lake Hylia West Pier', 'Lake Hylia Area'), - ('Lake Hylia East Pier', 'Lake Hylia Northeast Bank'), - ('Lake Hylia Water D Approach', 'Lake Hylia Water D'), - ('Lake Hylia Water D Leave', 'Lake Hylia Water'), #flippers - ('Desert Pass Ledge Drop', 'Desert Pass Area'), - ('Desert Pass Rocks (North)', 'Desert Pass Southeast'), #glove - ('Desert Pass Rocks (South)', 'Desert Pass Area'), #glove - ('Middle Aged Man', 'Middle Aged Man'), - ('Octoballoon Water Drop', 'Octoballoon Water'), #flippers - ('Octoballoon Waterfall Water Drop', 'Octoballoon Water'), #flippers - ('Octoballoon Pier', 'Octoballoon Area'), + # Intra-tile OW Connections + ('Lost Woods Bush (West)', 'Lost Woods East Area'), #pearl + ('Lost Woods Bush (East)', 'Lost Woods West Area'), #pearl + ('West Death Mountain Drop', 'West Death Mountain (Bottom)'), + ('Spectacle Rock Ledge Drop', 'West Death Mountain (Top)'), + ('Old Man Drop Off', 'Old Man Drop Off'), + ('DM Hammer Bridge (West)', 'East Death Mountain (Top East)'), #hammer + ('DM Hammer Bridge (East)', 'East Death Mountain (Top West)'), #hammer + ('EDM To Spiral Ledge Drop', 'Spiral Cave Ledge'), + ('EDM Ledge Drop', 'East Death Mountain (Bottom)'), + ('Spiral Ledge Drop', 'East Death Mountain (Bottom)'), + ('Fairy Ascension Ledge Drop', 'Fairy Ascension Plateau'), + ('Fairy Ascension Plateau Ledge Drop', 'East Death Mountain (Bottom)'), + ('Fairy Ascension Rocks (Inner)', 'East Death Mountain (Bottom)'), #mitts + ('Fairy Ascension Rocks (Outer)', 'Fairy Ascension Plateau'), #mitts + ('DM Broken Bridge (West)', 'East Death Mountain (Bottom)'), #hookshot + ('DM Broken Bridge (East)', 'East Death Mountain (Bottom Left)'), #hookshot + ('TR Pegs Ledge Entry', 'Death Mountain TR Pegs Ledge'), #mitts + ('TR Pegs Ledge Leave', 'Death Mountain TR Pegs Area'), #mitts + ('Mountain Pass Rock (Outer)', 'Mountain Pass Entry'), #glove + ('Mountain Pass Rock (Inner)', 'Mountain Pass Area'), #glove + ('Mountain Pass Entry Ledge Drop', 'Mountain Pass Area'), + ('Mountain Pass Ledge Drop', 'Mountain Pass Area'), + ('Zora Waterfall Landing', 'Zora Waterfall Area'), + ('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers + ('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers + ('Zora Waterfall Approach', 'Zora Waterfall Entryway'), #flippers + ('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer + ('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer + ('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts + ('Lost Woods Pass Rock (South)', 'Lost Woods Pass Portal Area'), #mitts + ('Bonk Rock Ledge Drop', 'Sanctuary Area'), + ('Graveyard Ledge Drop', 'Graveyard Area'), + ('Kings Grave Rocks (Outer)', 'Kings Grave Area'), #mitts + ('Kings Grave Rocks (Inner)', 'Graveyard Area'), #mitts + ('River Bend Water Drop', 'River Bend Water'), #flippers + ('River Bend East Water Drop', 'River Bend Water'), #flippers + ('River Bend West Pier', 'River Bend Area'), + ('River Bend East Pier', 'River Bend East Bank'), + ('Potion Shop Water Drop', 'Potion Shop Water'), #flippers + ('Potion Shop Northeast Water Drop', 'Potion Shop Water'), #flippers + ('Potion Shop Rock (South)', 'Potion Shop Northeast'), #glove + ('Potion Shop Rock (North)', 'Potion Shop Area'), #glove + ('Zora Approach Water Drop', 'Zora Approach Water'), #flippers + ('Zora Approach Rocks (West)', 'Zora Approach Ledge'), #mitts/boots + ('Zora Approach Rocks (East)', 'Zora Approach Area'), #mitts/boots + ('Zora Approach Bottom Ledge Drop', 'Zora Approach Ledge'), + ('Zora Approach Ledge Drop', 'Zora Approach Area'), + ('Kakariko Southwest Bush (North)', 'Kakariko Southwest'), #pearl + ('Kakariko Southwest Bush (South)', 'Kakariko Village'), #pearl + ('Kakariko Yard Bush (South)', 'Kakariko Bush Yard'), #pearl + ('Kakariko Yard Bush (North)', 'Kakariko Village'), #pearl + ('Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest'), #pearl + ('Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Area'), #pearl + ('Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Courtyard'), #pearl + ('Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Courtyard Northeast'), #pearl + ('Hyrule Castle Main Gate (South)', 'Hyrule Castle Courtyard'), #aga+mirror + ('Hyrule Castle Main Gate (North)', 'Hyrule Castle Area'), #aga+mirror + ('Hyrule Castle Ledge Drop', 'Hyrule Castle Area'), + ('Hyrule Castle Ledge Courtyard Drop', 'Hyrule Castle Courtyard'), + ('Hyrule Castle East Rock (Inner)', 'Hyrule Castle East Entry'), #glove + ('Hyrule Castle East Rock (Outer)', 'Hyrule Castle Area'), #glove + ('Wooden Bridge Bush (South)', 'Wooden Bridge Northeast'), #pearl + ('Wooden Bridge Bush (North)', 'Wooden Bridge Area'), #pearl + ('Wooden Bridge Water Drop', 'Wooden Bridge Water'), #flippers + ('Wooden Bridge Northeast Water Drop', 'Wooden Bridge Water'), #flippers + ('Blacksmith Ledge Peg (West)', 'Blacksmith Ledge'), #hammer + ('Blacksmith Ledge Peg (East)', 'Blacksmith Area'), #hammer + ('Maze Race Game', 'Maze Race Prize'), #pearl + ('Maze Race Ledge Drop', 'Maze Race Area'), + ('Stone Bridge (Southbound)', 'Stone Bridge South Area'), + ('Stone Bridge (Northbound)', 'Stone Bridge North Area'), + ('Desert Statue Move', 'Desert Stairs'), #book + ('Desert Ledge Drop', 'Desert Area'), + ('Desert Ledge Rocks (Outer)', 'Desert Ledge Keep'), #glove + ('Desert Ledge Rocks (Inner)', 'Desert Ledge'), #glove + ('Checkerboard Ledge Drop', 'Desert Area'), + ('Desert Mouth Drop', 'Desert Area'), + ('Desert Teleporter Drop', 'Desert Area'), + ('Bombos Tablet Drop', 'Desert Area'), + ('Flute Boy Bush (North)', 'Flute Boy Approach Area'), #pearl + ('Flute Boy Bush (South)', 'Flute Boy Bush Entry'), #pearl + ('C Whirlpool Water Entry', 'C Whirlpool Water'), #flippers + ('C Whirlpool Landing', 'C Whirlpool Area'), + ('C Whirlpool Rock (Bottom)', 'C Whirlpool Outer Area'), #glove + ('C Whirlpool Rock (Top)', 'C Whirlpool Area'), #glove + ('C Whirlpool Pegs (Outer)', 'C Whirlpool Portal Area'), #hammer + ('C Whirlpool Pegs (Inner)', 'C Whirlpool Area'), #hammer + ('Statues Water Entry', 'Statues Water'), #flippers + ('Statues Landing', 'Statues Area'), + ('Lake Hylia Water Drop', 'Lake Hylia Water'), #flippers + ('Lake Hylia South Water Drop', 'Lake Hylia Water'), #flippers + ('Lake Hylia Northeast Water Drop', 'Lake Hylia Water'), #flippers + ('Lake Hylia Central Water Drop', 'Lake Hylia Water'), #flippers + ('Lake Hylia Island Water Drop', 'Lake Hylia Water'), #flippers + ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), + ('Lake Hylia West Pier', 'Lake Hylia Northwest Bank'), + ('Lake Hylia East Pier', 'Lake Hylia Northeast Bank'), + ('Lake Hylia Water D Approach', 'Lake Hylia Water D'), + ('Lake Hylia Water D Leave', 'Lake Hylia Water'), #flippers + ('Ice Cave Water Drop', 'Ice Cave Water'), #flippers + ('Ice Cave Pier', 'Ice Cave Area'), + ('Desert Pass Ledge Drop', 'Desert Pass Area'), + ('Desert Pass Rocks (North)', 'Desert Pass Southeast'), #glove + ('Desert Pass Rocks (South)', 'Desert Pass Area'), #glove + ('Middle Aged Man', 'Middle Aged Man'), + ('Octoballoon Water Drop', 'Octoballoon Water'), #flippers + ('Octoballoon Waterfall Water Drop', 'Octoballoon Water'), #flippers + ('Octoballoon Pier', 'Octoballoon Area'), - ('Skull Woods Bush Rock (West)', 'Skull Woods Forest'), #glove - ('Skull Woods Bush Rock (East)', 'Skull Woods Portal Entry'), #glove - ('Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Path (Northeast)'), #pearl - ('Skull Woods Forgotten Bush (East)', 'Skull Woods Forgotten Path (Southwest)'), #pearl - ('Dark Death Mountain Drop (West)', 'West Dark Death Mountain (Bottom)'), - ('GT Entry Approach', 'GT Approach'), - ('GT Entry Leave', 'West Dark Death Mountain (Top)'), - ('Floating Island Drop', 'East Dark Death Mountain (Top)'), - ('Dark Death Mountain Drop (East)', 'East Dark Death Mountain (Bottom)'), - ('East Dark Death Mountain Bushes', 'East Dark Death Mountain (Bushes)'), - ('Turtle Rock Ledge Drop', 'Turtle Rock Area'), - ('Bumper Cave Entrance Rock', 'Bumper Cave Entrance'), #glove - ('Bumper Cave Ledge Drop', 'Bumper Cave Area'), - ('Bumper Cave Entrance Drop', 'Bumper Cave Area'), - ('Skull Woods Pass Bush Row (West)', 'Skull Woods Pass East Top Area'), #pearl - ('Skull Woods Pass Bush Row (East)', 'Skull Woods Pass West Area'), #pearl - ('Skull Woods Pass Bush (North)', 'Skull Woods Pass Portal Area'), #pearl - ('Skull Woods Pass Bush (South)', 'Skull Woods Pass East Top Area'), #pearl - ('Skull Woods Pass Rock (North)', 'Skull Woods Pass East Bottom Area'), #mitts - ('Skull Woods Pass Rock (South)', 'Skull Woods Pass Portal Area'), #mitts - ('Dark Graveyard Bush (South)', 'Dark Graveyard North'), #pearl - ('Dark Graveyard Bush (North)', 'Dark Graveyard Area'), #pearl - ('Qirn Jump Water Drop', 'Qirn Jump Water'), #flippers - ('Qirn Jump East Water Drop', 'Qirn Jump Water'), #flippers - ('Qirn Jump Pier', 'Qirn Jump East Bank'), - ('Dark Witch Water Drop', 'Dark Witch Water'), #flippers - ('Dark Witch Northeast Water Drop', 'Dark Witch Water'), #flippers - ('Dark Witch Rock (North)', 'Dark Witch Area'), #glove - ('Dark Witch Rock (South)', 'Dark Witch Northeast'), #glove - ('Catfish Approach Rocks (West)', 'Catfish Approach Ledge'), #mitts/boots - ('Catfish Approach Rocks (East)', 'Catfish Approach Area'), #mitts/boots - ('Catfish Approach Bottom Ledge Drop', 'Catfish Approach Ledge'), - ('Catfish Approach Ledge Drop', 'Catfish Approach Area'), - ('Catfish Approach Water Drop', 'Catfish Approach Water'), #flippers - ('Village of Outcasts Pegs', 'Dark Grassy Lawn'), #hammer - ('Grassy Lawn Pegs', 'Village of Outcasts Area'), #hammer - ('Shield Shop Fence (Outer) Ledge Drop', 'Shield Shop Fence'), - ('Shield Shop Fence (Inner) Ledge Drop', 'Shield Shop Area'), - ('Pyramid Exit Ledge Drop', 'Pyramid Area'), - ('Pyramid Crack', 'Pyramid Crack'), - ('Broken Bridge Hammer Rock (South)', 'Broken Bridge Northeast'), #hammer/glove - ('Broken Bridge Hammer Rock (North)', 'Broken Bridge Area'), #hammer/glove - ('Broken Bridge Hookshot Gap', 'Broken Bridge West'), #hookshot - ('Broken Bridge Water Drop', 'Broken Bridge Water'), #flippers - ('Broken Bridge Northeast Water Drop', 'Broken Bridge Water'), #flippers - ('Broken Bridge West Water Drop', 'Broken Bridge Water'), #flippers - ('Peg Area Rocks (West)', 'Hammer Pegs Area'), #mitts - ('Peg Area Rocks (East)', 'Hammer Pegs Entry'), #mitts - ('Dig Game To Ledge Drop', 'Dig Game Ledge'), #mitts - ('Dig Game Ledge Drop', 'Dig Game Area'), - ('Frog Ledge Drop', 'Archery Game Area'), - ('Frog Rock (Inner)', 'Frog Area'), #mitts - ('Frog Rock (Outer)', 'Frog Prison'), #mitts - ('Archery Game Rock (North)', 'Archery Game Area'), #mitts - ('Archery Game Rock (South)', 'Frog Area'), #mitts - ('Hammer Bridge Pegs (North)', 'Hammer Bridge South Area'), #hammer - ('Hammer Bridge Pegs (South)', 'Hammer Bridge North Area'), #hammer - ('Hammer Bridge Water Drop', 'Hammer Bridge Water'), #flippers - ('Hammer Bridge Pier', 'Hammer Bridge North Area'), - ('Misery Mire Teleporter Ledge Drop', 'Misery Mire Area'), - ('Stumpy Approach Bush (North)', 'Stumpy Approach Area'), #pearl - ('Stumpy Approach Bush (South)', 'Stumpy Approach Bush Entry'), #pearl - ('Dark C Whirlpool Water Entry', 'Dark C Whirlpool Water'), #flippers - ('Dark C Whirlpool Landing', 'Dark C Whirlpool Area'), - ('Dark C Whirlpool Rock (Bottom)', 'Dark C Whirlpool Outer Area'), #glove - ('Dark C Whirlpool Rock (Top)', 'Dark C Whirlpool Area'), #glove - ('Dark C Whirlpool Pegs (Right)', 'Dark C Whirlpool Portal Area'), #hammer - ('Dark C Whirlpool Pegs (Left)', 'Dark C Whirlpool Area'), #hammer - ('Hype Cave Water Entry', 'Hype Cave Water'), #flippers - ('Hype Cave Landing', 'Hype Cave Area'), - ('Ice Lake Water Drop', 'Ice Lake Water'), #flippers - ('Ice Lake Northeast Water Drop', 'Ice Lake Water'), #flippers - ('Ice Lake Southwest Water Drop', 'Ice Lake Water'), #flippers - ('Ice Lake Southeast Water Drop', 'Ice Lake Water'), #flippers - ('Ice Lake Moat Water Entry', 'Ice Lake Water'), #flippers - ('Ice Lake Northeast Pier', 'Ice Lake Northeast Bank'), - ('Bomber Corner Water Drop', 'Bomber Corner Water'), #flippers - ('Bomber Corner Waterfall Water Drop', 'Bomber Corner Water'), #flippers - ('Bomber Corner Pier', 'Bomber Corner Area'), + ('Skull Woods Rock (West)', 'Skull Woods Forest'), #glove + ('Skull Woods Rock (East)', 'Skull Woods Portal Entry'), #glove + ('Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Path (Northeast)'), #pearl + ('Skull Woods Forgotten Bush (East)', 'Skull Woods Forgotten Path (Southwest)'), #pearl + ('West Dark Death Mountain Drop', 'West Dark Death Mountain (Bottom)'), + ('GT Approach', 'GT Stairs'), + ('GT Leave', 'West Dark Death Mountain (Top)'), + ('Floating Island Drop', 'East Dark Death Mountain (Top)'), + ('East Dark Death Mountain Drop', 'East Dark Death Mountain (Bottom)'), + ('East Dark Death Mountain Bushes', 'East Dark Death Mountain (Bushes)'), + ('Turtle Rock Ledge Drop', 'Turtle Rock Area'), + ('Bumper Cave Rock (Outer)', 'Bumper Cave Entry'), #glove + ('Bumper Cave Rock (Inner)', 'Bumper Cave Area'), #glove + ('Bumper Cave Ledge Drop', 'Bumper Cave Area'), + ('Bumper Cave Entry Drop', 'Bumper Cave Area'), + ('Skull Woods Pass Bush Row (West)', 'Skull Woods Pass East Top Area'), #pearl + ('Skull Woods Pass Bush Row (East)', 'Skull Woods Pass West Area'), #pearl + ('Skull Woods Pass Bush (North)', 'Skull Woods Pass Portal Area'), #pearl + ('Skull Woods Pass Bush (South)', 'Skull Woods Pass East Top Area'), #pearl + ('Skull Woods Pass Rock (North)', 'Skull Woods Pass East Bottom Area'), #mitts + ('Skull Woods Pass Rock (South)', 'Skull Woods Pass Portal Area'), #mitts + ('Dark Graveyard Bush (South)', 'Dark Graveyard North'), #pearl + ('Dark Graveyard Bush (North)', 'Dark Graveyard Area'), #pearl + ('Qirn Jump Water Drop', 'Qirn Jump Water'), #flippers + ('Qirn Jump East Water Drop', 'Qirn Jump Water'), #flippers + ('Qirn Jump Pier', 'Qirn Jump East Bank'), + ('Dark Witch Water Drop', 'Dark Witch Water'), #flippers + ('Dark Witch Northeast Water Drop', 'Dark Witch Water'), #flippers + ('Dark Witch Rock (North)', 'Dark Witch Area'), #glove + ('Dark Witch Rock (South)', 'Dark Witch Northeast'), #glove + ('Catfish Approach Water Drop', 'Catfish Approach Water'), #flippers + ('Catfish Approach Rocks (West)', 'Catfish Approach Ledge'), #mitts/boots + ('Catfish Approach Rocks (East)', 'Catfish Approach Area'), #mitts/boots + ('Catfish Approach Bottom Ledge Drop', 'Catfish Approach Ledge'), + ('Catfish Approach Ledge Drop', 'Catfish Approach Area'), + ('Bush Yard Pegs (Outer)', 'Village of Outcasts Bush Yard'), #hammer + ('Bush Yard Pegs (Inner)', 'Village of Outcasts'), #hammer + ('Shield Shop Fence Drop (Outer)', 'Shield Shop Fence'), + ('Shield Shop Fence Drop (Inner)', 'Shield Shop Area'), + ('Pyramid Exit Ledge Drop', 'Pyramid Area'), + ('Pyramid Crack', 'Pyramid Crack'), + ('Broken Bridge Hammer Rock (South)', 'Broken Bridge Northeast'), #hammer/glove + ('Broken Bridge Hammer Rock (North)', 'Broken Bridge Area'), #hammer/glove + ('Broken Bridge Hookshot Gap', 'Broken Bridge West'), #hookshot + ('Broken Bridge Water Drop', 'Broken Bridge Water'), #flippers + ('Broken Bridge Northeast Water Drop', 'Broken Bridge Water'), #flippers + ('Broken Bridge West Water Drop', 'Broken Bridge Water'), #flippers + ('Peg Area Rocks (West)', 'Hammer Pegs Area'), #mitts + ('Peg Area Rocks (East)', 'Hammer Pegs Entry'), #mitts + ('Dig Game To Ledge Drop', 'Dig Game Ledge'), #mitts + ('Dig Game Ledge Drop', 'Dig Game Area'), + ('Frog Ledge Drop', 'Archery Game Area'), + ('Frog Rock (Inner)', 'Frog Area'), #mitts + ('Frog Rock (Outer)', 'Frog Prison'), #mitts + ('Archery Game Rock (North)', 'Archery Game Area'), #mitts + ('Archery Game Rock (South)', 'Frog Area'), #mitts + ('Hammer Bridge Pegs (North)', 'Hammer Bridge South Area'), #hammer + ('Hammer Bridge Pegs (South)', 'Hammer Bridge North Area'), #hammer + ('Hammer Bridge Water Drop', 'Hammer Bridge Water'), #flippers + ('Hammer Bridge Pier', 'Hammer Bridge North Area'), + ('Mire Teleporter Ledge Drop', 'Mire Area'), + ('Stumpy Approach Bush (North)', 'Stumpy Approach Area'), #pearl + ('Stumpy Approach Bush (South)', 'Stumpy Approach Bush Entry'), #pearl + ('Dark C Whirlpool Water Entry', 'Dark C Whirlpool Water'), #flippers + ('Dark C Whirlpool Landing', 'Dark C Whirlpool Area'), + ('Dark C Whirlpool Rock (Bottom)', 'Dark C Whirlpool Outer Area'), #glove + ('Dark C Whirlpool Rock (Top)', 'Dark C Whirlpool Area'), #glove + ('Dark C Whirlpool Pegs (Outer)', 'Dark C Whirlpool Portal Area'), #hammer + ('Dark C Whirlpool Pegs (Inner)', 'Dark C Whirlpool Area'), #hammer + ('Hype Cave Water Entry', 'Hype Cave Water'), #flippers + ('Hype Cave Landing', 'Hype Cave Area'), + ('Ice Lake Water Drop', 'Ice Lake Water'), #flippers + ('Ice Lake Northeast Water Drop', 'Ice Lake Water'), #flippers + ('Ice Lake Southwest Water Drop', 'Ice Lake Water'), #flippers + ('Ice Lake Southeast Water Drop', 'Ice Lake Water'), #flippers + ('Ice Lake Iceberg Water Entry', 'Ice Lake Water'), #flippers + ('Ice Lake Northeast Pier', 'Ice Lake Northeast Bank'), + ('Shopping Mall Water Drop', 'Shopping Mall Water'), #flippers + ('Shopping Mall Pier', 'Shopping Mall Area'), + ('Bomber Corner Water Drop', 'Bomber Corner Water'), #flippers + ('Bomber Corner Waterfall Water Drop', 'Bomber Corner Water'), #flippers + ('Bomber Corner Pier', 'Bomber Corner Area'), - # OWG In-Bounds Connections - ('Stone Bridge EC Cliff Water Drop', 'Stone Bridge Water'), #fake flipper - ('Tree Line WC Cliff Water Drop', 'Tree Line Water'), #fake flipper, - - ('Hammer Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), #fake flipper - ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water'), #fake flipper - ('Ice Lake Northeast Pier Hop', 'Ice Lake Northeast Bank'), - ('Ice Lake Moat Bomb Jump', 'Ice Lake Moat') - ] + # OWG In-Bounds Connections + ('Ice Lake Northeast Pier Hop', 'Ice Lake Northeast Bank'), + ('Ice Lake Iceberg Bomb Jump', 'Ice Lake Iceberg') +] default_whirlpool_connections = [ ((0x33, 'C Whirlpool', 'C Whirlpool Water'), (0x15, 'River Bend Whirlpool', 'River Bend Water')), @@ -1426,9 +1518,9 @@ default_whirlpool_connections = [ ] default_flute_connections = [ - 0x0b, 0x16, 0x18, 0x2c, 0x2f, 0x38, 0x3b, 0x3f + 0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f ] - + ow_connections = { 0x03: ([ ('West Death Mountain Teleporter', 'West Dark Death Mountain (Bottom)') @@ -1438,21 +1530,22 @@ ow_connections = { ('Dark Death Mountain Teleporter (West)', 'West Death Mountain (Bottom)') ]), 0x05: ([ + ('EDM To Fairy Ledge Drop', 'Fairy Ascension Ledge'), ('East Death Mountain Teleporter', 'East Dark Death Mountain (Bottom)') ], [ ('Floating Island Bridge (West)', 'East Death Mountain (Top East)'), ('Floating Island Bridge (East)', 'Death Mountain Floating Island'), - ('East Death Mountain Mimic Ledge Drop', 'Mimic Cave Ledge'), - ('Mimic Ledge Drop', 'East Death Mountain (Bottom)'), + ('EDM To Mimic Ledge Drop', 'Mimic Cave Ledge'), ('Spiral Mimic Bridge (West)', 'Spiral Mimic Ledge Extend'), ('Spiral Mimic Bridge (East)', 'Spiral Mimic Ledge Extend'), ('Spiral Ledge Approach', 'Spiral Cave Ledge'), ('Mimic Ledge Approach', 'Mimic Cave Ledge'), ('Spiral Mimic Ledge Drop', 'Fairy Ascension Ledge'), - ('Dark Death Mountain Teleporter (East)', 'East Death Mountain (Bottom)') + ('East Dark Death Mountain Teleporter', 'East Death Mountain (Bottom)') ]), 0x07: ([ - ('TR Pegs Teleporter', 'Turtle Rock Ledge') + ('TR Pegs Teleporter', 'Turtle Rock Ledge'), + ('TR Pegs Ledge Drop', 'Death Mountain TR Pegs Area') ], [ ('Turtle Rock Tail Ledge Drop', 'Turtle Rock Ledge'), ('Turtle Rock Teleporter', 'Death Mountain TR Pegs Ledge') @@ -1469,24 +1562,24 @@ ow_connections = { ('Graveyard Ladder (Bottom)', 'Graveyard Ledge') ]), 0x1b: ([ - ('Top of Pyramid', 'Pyramid Area'), - ('Top of Pyramid (Inner)', 'Pyramid Area') + ('Castle Gate Teleporter', 'Pyramid Area'), + ('Castle Gate Teleporter (Inner)', 'Pyramid Area') ], [ - ('Post Aga Inverted Teleporter', 'Hyrule Castle Area') + ('Post Aga Teleporter', 'Hyrule Castle Area') ]), 0x1e: ([ - ('Eastern Palace Ledge Drop', 'Eastern Palace Area'), # OWG - ('Palace of Darkness Ledge Drop', 'Palace of Darkness Area') # OWG + ('Eastern Palace Cliff Ledge Drop', 'Eastern Palace Area'), # OWG + ('Palace of Darkness Cliff Ledge Drop', 'Palace of Darkness Area') # OWG ], [ - ('Eastern Palace Ledge Drop', 'Palace of Darkness Area'), # OWG - ('Palace of Darkness Ledge Drop', 'Eastern Palace Area') # OWG + ('Eastern Palace Cliff Ledge Drop', 'Palace of Darkness Area'), # OWG + ('Palace of Darkness Cliff Ledge Drop', 'Eastern Palace Area') # OWG ]), 0x25: ([ - ('Sand Dunes Ledge Drop', 'Sand Dunes Area'), # OWG - ('Dark Dunes Ledge Drop', 'Dark Dunes Area') # OWG + ('Sand Dunes Cliff Ledge Drop', 'Sand Dunes Area'), # OWG + ('Dark Dunes Cliff Ledge Drop', 'Dark Dunes Area') # OWG ], [ - ('Sand Dunes Ledge Drop', 'Dark Dunes Area'), # OWG - ('Dark Dunes Ledge Drop', 'Sand Dunes Area') # OWG + ('Sand Dunes Cliff Ledge Drop', 'Dark Dunes Area'), # OWG + ('Dark Dunes Cliff Ledge Drop', 'Sand Dunes Area') # OWG ]), 0x29: ([ ('Suburb Cliff Ledge Drop', 'Kakariko Suburb Area'), # OWG @@ -1510,40 +1603,46 @@ ow_connections = { ('Bomb Shop Cliff Ledge Drop', 'Links House Area') # OWG ]), 0x2d: ([ - ('Stone Bridge East Ledge Drop', 'Stone Bridge North Area'), # OWG - ('Hammer Bridge North Ledge Drop', 'Hammer Bridge North Area'), # OWG + ('Stone Bridge East Cliff Ledge Drop', 'Stone Bridge North Area'), # OWG + ('Hammer Bridge North Cliff Ledge Drop', 'Hammer Bridge North Area'), # OWG ('Stone Bridge Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG - ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area') # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG + ('Stone Bridge EC Cliff Water Drop', 'Stone Bridge Water'), # fake flipper + ('Hammer Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), # fake flipper + ('Tree Line WC Cliff Water Drop', 'Tree Line Water'), # fake flipper + ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') # fake flipper ], [ - ('Stone Bridge East Ledge Drop', 'Hammer Bridge North Area'), # OWG - ('Hammer Bridge North Ledge Drop', 'Stone Bridge North Area'), # OWG + ('Stone Bridge East Cliff Ledge Drop', 'Hammer Bridge North Area'), # OWG + ('Hammer Bridge North Cliff Ledge Drop', 'Stone Bridge North Area'), # OWG ('Stone Bridge Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG - ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge South Area') # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG + ('Stone Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), # fake flipper + ('Hammer Bridge EC Cliff Water Drop', 'Stone Bridge Water'), # fake flipper + ('Tree Line WC Cliff Water Drop', 'Dark Tree Line Water'), # fake flipper + ('Dark Tree Line WC Cliff Water Drop', 'Tree Line Water') # fake flipper ]), 0x2e: ([ - ('Tree Line Ledge Drop', 'Tree Line Area'), # OWG - ('Dark Tree Line Ledge Drop', 'Dark Tree Line Area') # OWG + ('Tree Line Cliff Ledge Drop', 'Tree Line Area'), # OWG + ('Dark Tree Line Cliff Ledge Drop', 'Dark Tree Line Area') # OWG ], [ - ('Tree Line Ledge Drop', 'Dark Tree Line Area'), # OWG - ('Dark Tree Line Ledge Drop', 'Tree Line Area') # OWG + ('Tree Line Cliff Ledge Drop', 'Dark Tree Line Area'), # OWG + ('Dark Tree Line Cliff Ledge Drop', 'Tree Line Area') # OWG ]), 0x2f: ([ - ('East Hyrule Teleporter', 'Palace of Darkness Nook Area') + ('East Hyrule Teleporter', 'Darkness Nook Area') ], [ ('East Dark World Teleporter', 'Eastern Nook Area') ]), 0x30: ([ ('Mirror To Bombos Tablet Ledge', 'Bombos Tablet Ledge'), # OWG - ('Desert Teleporter', 'Misery Mire Teleporter Ledge'), - ('Desert Boss Cliff Ledge Drop', 'Desert Palace Entrance (North) Spot'), # OWG - ('Mire Cliff Ledge Drop', 'Misery Mire Area'), # OWG + ('Desert Teleporter', 'Mire Teleporter Ledge'), + ('Mire Cliff Ledge Drop', 'Mire Area'), # OWG ('Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge') # OWG ], [ ('Checkerboard Ledge Approach', 'Desert Checkerboard Ledge'), ('Checkerboard Ledge Leave', 'Desert Area'), - ('Misery Mire Teleporter', 'Desert Palace Teleporter Ledge'), - ('Desert Boss Cliff Ledge Drop', 'Misery Mire Area'), # OWG - ('Mire Cliff Ledge Drop', 'Desert Palace Entrance (North) Spot'), # OWG + ('Mire Teleporter', 'Desert Teleporter Ledge'), + ('Mire Cliff Ledge Drop', 'Desert Ledge Keep'), # OWG ('Dark Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge') # OWG ]), 0x32: ([ @@ -1551,8 +1650,8 @@ ow_connections = { ('Cave 45 Cliff Ledge Drop', 'Cave 45 Ledge'), # OWG ('Stumpy Approach Cliff Ledge Drop', 'Stumpy Approach Area') # OWG ], [ - ('Cave 45 Inverted Leave', 'Flute Boy Approach Area'), - ('Cave 45 Inverted Approach', 'Cave 45 Ledge'), + ('Cave 45 Leave', 'Flute Boy Approach Area'), + ('Cave 45 Approach', 'Cave 45 Ledge'), ('Cave 45 Cliff Ledge Drop', 'Stumpy Approach Area'), # OWG ('Stumpy Approach Cliff Ledge Drop', 'Cave 45 Ledge') # OWG ]), @@ -1586,17 +1685,16 @@ ow_connections = { ]), 0x35: ([ ('Lake Hylia Teleporter', 'Ice Palace Area'), - #('Ice Palace Ledge Drop', 'Ice Lake Moat'), - ('Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Area'), # OWG - ('Ice Lake Area Cliff Ledge Drop', 'Ice Lake Area'), # OWG + ('Lake Hylia Northwest Cliff Ledge Drop', 'Lake Hylia Northwest Bank'), # OWG + ('Ice Lake Northwest Cliff Ledge Drop', 'Ice Lake Northwest Bank'), # OWG ('Lake Hylia Island FAWT Ledge Drop', 'Lake Hylia Island'), # OWG - ('Ice Palace Island FAWT Ledge Drop', 'Ice Lake Moat') # OWG + ('Ice Palace Island FAWT Ledge Drop', 'Ice Lake Iceberg') # OWG ], [ ('Lake Hylia Island Pier', 'Lake Hylia Island'), - ('Ice Palace Teleporter', 'Lake Hylia Water D'), - ('Lake Hylia Area Cliff Ledge Drop', 'Ice Lake Area'), # OWG - ('Ice Lake Area Cliff Ledge Drop', 'Lake Hylia Area'), # OWG - ('Lake Hylia Island FAWT Ledge Drop', 'Ice Lake Moat'), # OWG + ('Ice Lake Teleporter', 'Lake Hylia Water D'), + ('Lake Hylia Northwest Cliff Ledge Drop', 'Ice Lake Northwest Bank'), # OWG + ('Ice Lake Northwest Cliff Ledge Drop', 'Lake Hylia Northwest Bank'), # OWG + ('Lake Hylia Island FAWT Ledge Drop', 'Ice Lake Iceberg'), # OWG ('Ice Palace Island FAWT Ledge Drop', 'Lake Hylia Island') # OWG ]), 0x3a: ([ @@ -1636,11 +1734,11 @@ mirror_connections = { 'East Dark Death Mountain (Bushes)': ['Fairy Ascension Plateau'], 'East Dark Death Mountain (Bottom Left)': ['East Death Mountain (Bottom Left)'], - 'Turtle Rock Area': ['Death Mountain TR Pegs'], + 'Turtle Rock Area': ['Death Mountain TR Pegs Area'], - 'Bumper Cave Area': ['Mountain Entry Area'], - 'Bumper Cave Entrance': ['Mountain Entry Entrance'], - 'Bumper Cave Ledge': ['Mountain Entry Ledge'], + 'Bumper Cave Area': ['Mountain Pass Area'], + 'Bumper Cave Entry': ['Mountain Pass Entry'], + 'Bumper Cave Ledge': ['Mountain Pass Ledge'], 'Catfish Area': ['Zora Waterfall Area'], @@ -1667,8 +1765,8 @@ mirror_connections = { 'Catfish Approach Area': ['Zora Approach Area'], 'Catfish Approach Ledge': ['Zora Approach Ledge'], - 'Village of Outcasts Area': ['Kakariko Area'], - 'Dark Grassy Lawn': ['Kakariko Area'], + 'Village of Outcasts': ['Kakariko Village'], + 'Village of Outcasts Bush Yard': ['Kakariko Village'], 'Shield Shop Area': ['Forgotten Forest Area'], 'Shield Shop Fence': ['Forgotten Forest Area'], @@ -1683,7 +1781,7 @@ mirror_connections = { 'Palace of Darkness Area': ['Eastern Palace Area'], - 'Hammer Pegs Area': ['Blacksmith Area', 'Bat Cave Ledge'], + 'Hammer Pegs Area': ['Blacksmith Area', 'Blacksmith Ledge'], 'Hammer Pegs Entry': ['Blacksmith Area'], 'Dark Dunes Area': ['Sand Dunes Area'], @@ -1707,9 +1805,9 @@ mirror_connections = { 'Dark Tree Line Area': ['Tree Line Area'], - 'Palace of Darkness Nook Area': ['Eastern Nook Area'], + 'Darkness Nook Area': ['Eastern Nook Area'], - 'Misery Mire Area': ['Desert Area', 'Desert Ledge', 'Desert Checkerboard Ledge', 'Desert Palace Stairs', 'Desert Palace Entrance (North) Spot'], + 'Mire Area': ['Desert Area', 'Desert Ledge', 'Desert Checkerboard Ledge', 'Desert Stairs', 'Desert Ledge Keep'], 'Stumpy Approach Area': ['Cave 45 Ledge'], 'Stumpy Approach Bush Entry': ['Flute Boy Bush Entry'], @@ -1719,13 +1817,13 @@ mirror_connections = { 'Hype Cave Area': ['Statues Area'], - 'Ice Lake Area': ['Lake Hylia Area'], + 'Ice Lake Northwest Bank': ['Lake Hylia Northwest Bank'], 'Ice Lake Northeast Bank': ['Lake Hylia Northeast Bank'], - 'Ice Lake Ledge (West)': ['Lake Hylia South Shore'], - 'Ice Lake Ledge (East)': ['Lake Hylia South Shore'], + 'Ice Lake Southwest Ledge': ['Lake Hylia South Shore'], + 'Ice Lake Southeast Ledge': ['Lake Hylia South Shore'], 'Ice Lake Water': ['Lake Hylia Island'], 'Ice Palace Area': ['Lake Hylia Central Island'], - 'Ice Lake Moat': ['Lake Hylia Water', 'Lake Hylia Water D'], #needs flippers + 'Ice Lake Iceberg': ['Lake Hylia Water', 'Lake Hylia Water D'], #first one needs flippers 'Shopping Mall Area': ['Ice Cave Area'], @@ -1758,12 +1856,12 @@ mirror_connections = { 'East Death Mountain (Bottom)': ['East Dark Death Mountain (Bottom)'], 'Death Mountain Floating Island': ['Dark Death Mountain Floating Island'], - 'Death Mountain TR Pegs': ['Turtle Rock Area'], + 'Death Mountain TR Pegs Area': ['Turtle Rock Area'], 'Death Mountain TR Pegs Ledge': ['Turtle Rock Ledge'], - 'Mountain Entry Area': ['Bumper Cave Area'], - 'Mountain Entry Entrance': ['Bumper Cave Entrance'], - 'Mountain Entry Ledge': ['Bumper Cave Ledge'], + 'Mountain Pass Area': ['Bumper Cave Area'], + 'Mountain Pass Entry': ['Bumper Cave Entry'], + 'Mountain Pass Ledge': ['Bumper Cave Ledge'], 'Zora Waterfall Area': ['Catfish Area'], @@ -1792,9 +1890,9 @@ mirror_connections = { 'Zora Approach Area': ['Catfish Approach Area'], 'Zora Approach Ledge': ['Catfish Approach Ledge'], - 'Kakariko Area': ['Village of Outcasts Area'], - 'Kakariko Southwest': ['Village of Outcasts Area'], - 'Kakariko Grass Yard': ['Dark Grassy Lawn'], + 'Kakariko Village': ['Village of Outcasts'], + 'Kakariko Southwest': ['Village of Outcasts'], + 'Kakariko Bush Yard': ['Village of Outcasts Bush Yard'], 'Forgotten Forest Area': ['Shield Shop Area'], @@ -1832,13 +1930,13 @@ mirror_connections = { 'Tree Line Area': ['Dark Tree Line Area'], - 'Eastern Nook Area': ['Palace of Darkness Nook Area'], + 'Eastern Nook Area': ['Darkness Nook Area'], - 'Desert Area': ['Misery Mire Area'], - 'Desert Ledge': ['Misery Mire Area'], - 'Desert Palace Entrance (North) Spot': ['Misery Mire Area'], - 'Desert Checkerboard Ledge': ['Misery Mire Area'], - 'Desert Palace Stairs': ['Misery Mire Area'], + 'Desert Area': ['Mire Area'], + 'Desert Ledge': ['Mire Area'], + 'Desert Ledge Keep': ['Mire Area'], + 'Desert Checkerboard Ledge': ['Mire Area'], + 'Desert Stairs': ['Mire Area'], 'Flute Boy Approach Area': ['Stumpy Approach Area'], 'Cave 45 Ledge': ['Stumpy Approach Area'], @@ -1849,11 +1947,11 @@ mirror_connections = { 'Statues Area': ['Hype Cave Area'], - 'Lake Hylia Area': ['Ice Lake Area'], - 'Lake Hylia South Shore': ['Ice Lake Ledge (West)', 'Ice Lake Ledge (East)'], + 'Lake Hylia Northwest Bank': ['Ice Lake Northwest Bank'], + 'Lake Hylia South Shore': ['Ice Lake Southwest Ledge', 'Ice Lake Southeast Ledge'], 'Lake Hylia Northeast Bank': ['Ice Lake Northeast Bank'], 'Lake Hylia Central Island': ['Ice Palace Area'], - 'Lake Hylia Water D': ['Ice Lake Moat'], + 'Lake Hylia Water D': ['Ice Lake Iceberg'], 'Ice Cave Area': ['Shopping Mall Area'], @@ -1878,8 +1976,8 @@ default_connections = [('Lost Woods NW', 'Master Sword Meadow SC'), ('Lost Woods SC', 'Lost Woods Pass NE'), ('Lost Woods SE', 'Kakariko Fortune NE'), ('Lost Woods EN', 'Lumberjack WN'), - ('Lumberjack SW', 'Mountain Entry NW'), - ('Mountain Entry SE', 'Kakariko Pond NE'), + ('Lumberjack SW', 'Mountain Pass NW'), + ('Mountain Pass SE', 'Kakariko Pond NE'), ('Zora Waterfall NE', 'Zoras Domain SW'), ('Lost Woods Pass SW', 'Kakariko NW'), ('Lost Woods Pass SE', 'Kakariko NC'), @@ -2023,7 +2121,7 @@ one_way_ledges = { 'East Death Mountain (Bottom)': {'East Death Mountain (Top East)', 'Spiral Cave Ledge'}, 'Fairy Ascension Plateau': {'Fairy Ascension Ledge'}, - 'Mountain Entry Area': {'Mountain Entry Ledge'}, + 'Mountain Pass Area': {'Mountain Pass Ledge'}, 'Sanctuary Area': {'Bonk Rock Ledge'}, 'Graveyard Area': {'Graveyard Ledge'}, 'Potion Shop Water': {'Potion Shop Area', @@ -2038,9 +2136,9 @@ one_way_ledges = { 'Flute Boy Approach Area': {'Cave 45 Ledge'}, 'Desert Area': {'Desert Ledge', 'Desert Checkerboard Ledge', - 'Desert Palace Mouth', + 'Desert Mouth', 'Bombos Tablet Ledge', - 'Desert Palace Teleporter Ledge'}, + 'Desert Teleporter Ledge'}, 'Desert Pass Area': {'Desert Pass Ledge'}, 'Lake Hylia Water': {'Lake Hylia South Shore', 'Lake Hylia Island'}, @@ -2057,22 +2155,22 @@ one_way_ledges = { 'Broken Bridge Water': {'Broken Bridge West', 'Broken Bridge Area', 'Broken Bridge Northeast'}, - 'Misery Mire Area': {'Misery Mire Teleporter Ledge'}, - 'Ice Lake Water': {'Ice Lake Area', - 'Ice Lake Ledge (West)', - 'Ice Lake Ledge (East)'} + 'Mire Area': {'Mire Teleporter Ledge'}, + 'Ice Lake Water': {'Ice Lake Northwest Bank', + 'Ice Lake Southwest Ledge', + 'Ice Lake Southeast Ledge'} } isolated_regions = [ 'Death Mountain Floating Island', 'Mimic Cave Ledge', 'Spiral Mimic Ledge Extend', - 'Mountain Entry Ledge', + 'Mountain Pass Ledge', 'Maze Race Prize', 'Maze Race Ledge', 'Desert Ledge', - 'Desert Palace Entrance (North) Spot', - 'Desert Palace Mouth', + 'Desert Ledge Keep', + 'Desert Mouth', 'Dark Death Mountain Floating Island', 'Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge', @@ -2084,12 +2182,12 @@ isolated_regions = [ flute_data = { #Slot LW Region DW Region OWID VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX - 0x09: (['Lost Woods East Area', 'Skull Woods Forest'], 0x00, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290), + 0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290), 0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550), - 0x0b: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x03, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), - 0x0e: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x05, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8), - 0x07: (['Death Mountain TR Pegs', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20), - 0x0a: (['Mountain Entry Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488), + 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), + 0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8), + 0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20), + 0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488), 0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x02d0, 0x0f38), 0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x0448, 0x0058), 0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x0588, 0x0318), @@ -2099,11 +2197,11 @@ flute_data = { 0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x04f8, 0x0b60), 0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x0598, 0x0ccf), 0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x04e0, 0x0f68), - 0x18: (['Kakariko Area', 'Village of Outcasts Area'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x07c0, 0x0210, 0x07c8, 0x01f8), + 0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x07c0, 0x0210, 0x07c8, 0x01f8), 0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0548), 0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07d8, 0x07f8), 0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0720, 0x0a80), - 0x26: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x1e, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80), + 0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80), 0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0908, 0x04d8), 0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x08b8, 0x0b08), 0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0b88, 0x00b8), @@ -2113,13 +2211,13 @@ flute_data = { 0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0b20, 0x08b8), 0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0), 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58), - 0x2f: (['Eastern Nook Area', 'Palace of Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30), - 0x38: (['Desert Palace Teleporter Ledge', 'Misery Mire Teleporter Ledge'], 0x30, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070), + 0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30), + 0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070), 0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0cd6, 0x0568), 0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0c80, 0x0628), 0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0d60, 0x08d8), - #0x35: (['Lake Hylia Area', 'Ice Lake Area'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88), - 0x3e: (['Lake Hylia South Shore', 'Ice Lake Ledge (East)'], 0x35, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0f90, 0x0da4), + #0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88), + 0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0f90, 0x0da4), 0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d48, 0x0ed0), 0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0e70, 0x0540), 0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0f30, 0x0770), diff --git a/PotShuffle.py b/PotShuffle.py index 152f6756..76d5733c 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -788,12 +788,12 @@ vanilla_pots = { 0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))], 0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))], # note: these addresses got moved thanks to waterfall fairy edit - 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])), - Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])), - Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])), - Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])), - Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])), - Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))], + 0x114: [Pot(92, 4, PotItem.Heart, 'Mire Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])), + Pot(96, 4, PotItem.Heart, 'Mire Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])), + Pot(92, 5, PotItem.Bomb, 'Mire Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])), + Pot(96, 5, PotItem.Bomb, 'Mire Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])), + Pot(92, 10, PotItem.FiveArrows, 'Mire Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])), + Pot(96, 10, PotItem.Heart, 'Mire Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))], 0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])), Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])), @@ -851,10 +851,10 @@ vanilla_pots = { Pot(100, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA])), Pot(88, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA])), Pot(100, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]))], - 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B801A, [0x33, 0xCB, 0xFA])), - Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B801D, [0x3B, 0xCB, 0xFA])), - Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B8020, [0x43, 0xCB, 0xFA])), - Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B8023, [0x4B, 0xCB, 0xFA]))], + 0x127: [Pot(24, 25, PotItem.Nothing, 'Hammer Peg Cave', obj=RoomObject(0x2B801A, [0x33, 0xCB, 0xFA])), + Pot(28, 25, PotItem.Nothing, 'Hammer Peg Cave', obj=RoomObject(0x2B801D, [0x3B, 0xCB, 0xFA])), + Pot(32, 25, PotItem.Nothing, 'Hammer Peg Cave', obj=RoomObject(0x2B8020, [0x43, 0xCB, 0xFA])), + Pot(36, 25, PotItem.Nothing, 'Hammer Peg Cave', obj=RoomObject(0x2B8023, [0x4B, 0xCB, 0xFA]))], } @@ -1022,7 +1022,7 @@ key_drop_data = { 'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'], 'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'], 'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'], - 'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'int a pot in Ice Palace', 'Small Key (Ice Palace)'], + 'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'in a pot in Ice Palace', 'Small Key (Ice Palace)'], 'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'], 'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'], 'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'], diff --git a/README.md b/README.md index c4de4bee..e757958a 100644 --- a/README.md +++ b/README.md @@ -248,38 +248,37 @@ New flute spots are chosen at random with minimum bias. ## Bonk Drop Shuffle (--bonk_drops) -This adds 41 new item locations to the game. These bonk locations are limited to the ones that drop a static item in the vanilla game. +This adds 42 new item locations to the game. These bonk locations are limited to the ones that drop a static item in the vanilla game. - Bonk Locations consist of some trees, rocks, and statues - 33 Trees - 8 of the tree locations require Agahnim to be defeated to access the item - 6 Rocks - 1 of the rocks drops 2 items - - 1 Statue + - 2 Statues + - 1 of them is the Cold Fairy Statue next to Ice Rod Cave - Bonk locations can be collected by bonking into them with the Pegasus Boots or using the Quake Medallion - One of the bonk locations are guaranteed to have a full magic decanter - Some of the drops can be farmed repeatedly, but only increments the collection rate once - All of the bonk trees have been given an alternate color (and all non-bonk trees are reverted to normal tree color) - 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 -- Since Fairies and Apples are new items that can appear in plain sight, they don't have a proper graphic for them yet. For now, they show up as Power Stars -Here is a map that shows all the [Bonk Locations](https://media.discordapp.net/attachments/783989090017738753/1000880877548609607/unknown.png?width=1399&height=702). FYI, the 2-4 and 2-3-4 refer to the tree numbers that have the items. The 2 by Dark Fortune Teller indicate that there are 2 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?width=1399&height=702). 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. -Future Note: This does NOT include the Good Bee (Cold Bee) Cave Statue...yet. In the future, this could be an additional item location. - #### Items Added To Pool: - 15 Fairies - 8 Apples - 6 Bee Traps - 3 Red Rupees - 3 Blue Rupees -- 2 Single Bomb +- 2 Single Bombs - 2 Small Hearts - 1 Large Magic Decanter - 1 8x Bomb Pack +- 1 Good Bee ## New Goal Options (--goal) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d5a492b1..336c604e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -57,7 +57,7 @@ Please see [Customizer documentation](docs/Customizer.md) on how to create custo ## New Goals -### Triforce Hunt + Ganon +### Ganonhunt Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI ### Completionist @@ -108,6 +108,81 @@ These are now independent of retro mode and have three options: None, Random, an * Bonk Fairy (Dark) # Bug Fixes and Notes + +* 1.2.0.20u + * New generation feature that allows Spiral Stair to link to themselves (thank Catobat) + * Added logic for trap doors that could be opened using existing room triggers + * Fixed a problem with inverted generation and the experimental flag + * Added a notes field for user added notes either via CLI or Customizer (thanks Hiimcody and Codemann) + * Fixed a typo for a specific pot hint + * Fix for Hera Boss music (thanks Codemann) +* 1.1.6 (from Stable) + * Minor issue with dungeon counter hud interfering with timer +* 1.2.0.19u + * Added min/max for triforce pool, goal, and difference for CLI and Customizer. (Thanks Catobat) + * Fixed a bug with dungeon generation + * Multiworld: Fixed /missing command to not list all the pots + * Changed the "Ganonhunt" goal to use open pyramid on the Auto setting + * Customizer: Fixed the example yaml for shopsanity +* 1.2.0.18u + * Fixed an issue with pyramid hole being in logic when it is not opened. + * Crystal cutscene at GT use new symmetrical layouts (thanks Codemann) + * Fix for Hera Boss music (thanks Codemann) + * Fixed an issue where certain vanilla door types would not allow other types to be placed. + * Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead if no alternatives are found. + * Customizer: fixed an issue with assured sword and start_inventory + * Customizer: warns when trying to specifically place an item that's not in the item pool + * Fixed "accessibility: none" displaying a spoiling message + * Fixed warning message about custom item pool when it is fine +* 1.2.0.17u + * Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only) + * Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead + * Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped) + * Fix for customizer setting crystals to 0 for either GT/Ganon +* 1.2.0.16u + * Fix for partial key logic on vanilla Mire + * Fix for Kholdstare Shell collision when at Lanmo 2 + * Fix for Mire Attic Hint door (direction was swapped) + * Dungeon at Chest Game displays correctly on OW map option +* 1.2.0.15u + * GUI reorganization + * Logic fix for pots in GT conveyor cross + * Auto option for pyramid open (trinity or ER + crystals goal) + * World model refactor (combining inverted and normal world models) + * Partitioned fix for lamp logic and links house + * Fix starting flute logic + * Reduced universal keys in pool slightly for non-vanilla dungeons + * Fake world fix finally + * Some extra restrictions on links house placement for lite/lean + * Collection_rate works in customizer files +* 1.2.0.14u + * Small fix for key logic validation (got rid of a false negative) + * Customized doors in ice cross work properly now +* 1.2.0.13u + * Allow green/blue potion refills to be customized + * OW Map showing dungeon entrance at Snitch Lady (West) fixed (instead of @ HC Courtyard) + * Standing item data is cleared on transition to overworld (enemy drops won't bleed to overworld sprites) + * Escape assist won't give you a free quiver in retro bow mode + * Fixed an issue where a door would be opened magically (due to original pairing) + * MultiServer can now disable forfeits if desired +* 1.2.0.12u + * Fix for mirror portal in inverted + * Yet another fix for blocked door in Standard ER +* 1.2.0.11u + * Fixed an issue with lower layer doors in Standard + * Fix for doors in cave state (will no longer be vanilla) + * Added a logic rule for th murderdactyl near bumper ledge for OHKO purposes + * Enemizer alteration for Hovers and normal enemies in shallow water + * Fix for beemizer including modes with an increased item pool + * Fix for district algorithm +* 1.2.0.10u + * Fixed overrun issues with edge transitions + * Better support for customized start_inventory with dungeon items + * Colorized pots now available with lottery. Default is on. + * Dungeon_only support pottery + * Fix AllowAccidentalGlitches flag in OWG + * Potential fix for mirror portal and entering cave on same frame + * A few other minor issues, generation and graphical * 1.2.0.9-u * Disallowed standard exits (due to ER) are now graphically half blocked instead of missing * Graphical issues with Sanctuary and Swamp Hub lobbies are fixed diff --git a/Regions.py b/Regions.py index 0bbca8b0..7dd3617a 100644 --- a/Regions.py +++ b/Regions.py @@ -7,154 +7,165 @@ from PotShuffle import key_drop_data, vanilla_pots, choose_pots, PotSecretTable def create_regions(world, player): world.regions += [ create_menu_region(player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q', 'Other World S&Q']), - create_menu_region(player, 'Flute Sky', None, ['Flute Spot 1', 'Flute Spot 2', 'Flute Spot 3', 'Flute Spot 4', 'Flute Spot 5', 'Flute Spot 6', 'Flute Spot 7', 'Flute Spot 8']), - + create_menu_region(player, 'Flute Sky', None, ['Flute Spot 1', 'Flute Spot 2', 'Flute Spot 3', 'Flute Spot 4', + 'Flute Spot 5', 'Flute Spot 6', 'Flute Spot 7', 'Flute Spot 8']), + create_lw_region(player, 'Master Sword Meadow', ['Master Sword Pedestal'], ['Master Sword Meadow SC']), create_lw_region(player, 'Lost Woods West Area', None, ['Lost Woods Bush (West)', 'Lost Woods NW', 'Lost Woods SW', 'Lost Woods SC']), - create_lw_region(player, 'Lost Woods East Area', ['Mushroom'], ['Lost Woods Bush (East)', 'Lost Woods Gamble', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump', 'Lost Woods SE', 'Lost Woods EN']), + create_lw_region(player, 'Lost Woods East Area', ['Mushroom'], ['Lost Woods Gamble', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump', 'Lost Woods Bush (East)', 'Lost Woods SE', 'Lost Woods EN']), create_lw_region(player, 'Lumberjack Area', None, ['Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Lumberjack House', 'Lumberjack WN', 'Lumberjack SW']), - create_lw_region(player, 'West Death Mountain (Top)', ['Ether Tablet'], ['Spectacle Rock Approach', 'West Death Mountain Drop', 'Tower of Hera', 'West Death Mountain EN']), - create_lw_region(player, 'Spectacle Rock Ledge', ['Spectacle Rock'], ['Spectacle Rock Leave', 'Spectacle Rock Drop']), - create_lw_region(player, 'West Death Mountain (Bottom)', ['Old Man Drop Off'], ['Old Man Drop Off', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'West Death Mountain Teleporter', 'West Death Mountain ES']), + create_lw_region(player, 'West Death Mountain (Top)', ['Ether Tablet'], ['Tower of Hera', 'Spectacle Rock Approach', 'West Death Mountain Drop', 'West Death Mountain EN']), + create_lw_region(player, 'Spectacle Rock Ledge', ['Spectacle Rock'], ['Spectacle Rock Leave', 'Spectacle Rock Ledge Drop']), + create_lw_region(player, 'West Death Mountain (Bottom)', ['Old Man Drop Off'], ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', + 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', + 'Old Man Drop Off', 'West Death Mountain Teleporter', 'West Death Mountain ES']), create_lw_region(player, 'Old Man Drop Off', ['Old Man'], None), create_lw_region(player, 'East Death Mountain (Top West)', None, ['DM Hammer Bridge (West)', 'East Death Mountain WN']), - create_lw_region(player, 'East Death Mountain (Top East)', None, ['DM Hammer Bridge (East)', 'Floating Island Bridge (East)', 'East Death Mountain Spiral Ledge Drop', 'East Death Mountain Fairy Ledge Drop', 'East Death Mountain Mimic Ledge Drop', 'Paradox Cave (Top)', 'East Death Mountain EN']), - create_lw_region(player, 'Spiral Cave Ledge', None, ['Spiral Ledge Drop', 'Spiral Mimic Bridge (West)', 'Spiral Cave']), - create_lw_region(player, 'Mimic Cave Ledge', None, ['Mimic Ledge Drop', 'Spiral Mimic Bridge (East)', 'Mimic Cave']), - create_lw_region(player, 'Spiral Mimic Ledge Extend', None, ['Spiral Ledge Approach', 'Mimic Ledge Approach', 'Spiral Mimic Ledge Drop']), - create_lw_region(player, 'Fairy Ascension Ledge', None, ['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)']), - create_lw_region(player, 'Fairy Ascension Plateau', None, ['Fairy Ascension Rocks (North)', 'Fairy Ascension Plateau Ledge Drop', 'Fairy Ascension Cave (Bottom)']), - create_lw_region(player, 'East Death Mountain (Bottom Left)', None, ['DM Broken Bridge (West)', 'East Death Mountain WS']), - create_lw_region(player, 'East Death Mountain (Bottom)', None, ['DM Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Hookshot Fairy', 'Spiral Cave (Bottom)', 'Fairy Ascension Rocks (South)', 'East Death Mountain Teleporter']), + create_lw_region(player, 'East Death Mountain (Top East)', None, ['Paradox Cave (Top)', 'DM Hammer Bridge (East)', 'Floating Island Bridge (East)', + 'EDM To Spiral Ledge Drop', 'EDM To Fairy Ledge Drop', 'EDM To Mimic Ledge Drop', 'EDM Ledge Drop', 'East Death Mountain EN']), create_lw_region(player, 'Death Mountain Floating Island', ['Floating Island'], ['Floating Island Bridge (West)']), - create_lw_region(player, 'Death Mountain TR Pegs', None, ['TR Pegs Ledge Entry', 'Death Mountain TR Pegs WN']), + create_lw_region(player, 'Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Ledge Drop', 'Spiral Mimic Bridge (West)']), + create_lw_region(player, 'Mimic Cave Ledge', None, ['Mimic Cave', 'Spiral Mimic Bridge (East)']), + create_lw_region(player, 'Spiral Mimic Ledge Extend', None, ['Spiral Ledge Approach', 'Mimic Ledge Approach', 'Spiral Mimic Ledge Drop']), + create_lw_region(player, 'Fairy Ascension Ledge', None, ['Fairy Ascension Cave (Top)', 'Fairy Ascension Ledge Drop']), + create_lw_region(player, 'Fairy Ascension Plateau', None, ['Fairy Ascension Cave (Bottom)', 'Fairy Ascension Rocks (Inner)', 'Fairy Ascension Plateau Ledge Drop']), + create_lw_region(player, 'East Death Mountain (Bottom Left)', None, ['DM Broken Bridge (West)', 'East Death Mountain WS']), + create_lw_region(player, 'East Death Mountain (Bottom)', None, ['Spiral Cave (Bottom)', 'Hookshot Fairy', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', + 'DM Broken Bridge (East)', 'Fairy Ascension Rocks (Outer)', 'East Death Mountain Teleporter']), + create_lw_region(player, 'Death Mountain TR Pegs Area', None, ['TR Pegs Ledge Entry', 'Death Mountain TR Pegs WN']), create_lw_region(player, 'Death Mountain TR Pegs Ledge', None, ['TR Pegs Ledge Leave', 'TR Pegs Ledge Drop', 'TR Pegs Teleporter']), - create_lw_region(player, 'Mountain Entry Area', None, ['Mountain Entry Entrance Rock (West)', 'Mountain Entry NW', 'Mountain Entry SE']), - create_lw_region(player, 'Mountain Entry Entrance', None, ['Mountain Entry Entrance Rock (East)', 'Mountain Entry Entrance Ledge Drop', 'Old Man Cave (West)']), - create_lw_region(player, 'Mountain Entry Ledge', None, ['Mountain Entry Ledge Drop', 'Death Mountain Return Cave (West)'], 'a ledge in the foothills'), + create_lw_region(player, 'Mountain Pass Area', None, ['Mountain Pass Rock (Outer)', 'Mountain Pass NW', 'Mountain Pass SE']), + create_lw_region(player, 'Mountain Pass Ledge', None, ['Death Mountain Return Cave (West)', 'Mountain Pass Ledge Drop'], 'a ledge in the foothills'), + create_lw_region(player, 'Mountain Pass Entry', None, ['Old Man Cave (West)', 'Mountain Pass Rock (Inner)', 'Mountain Pass Entry Ledge Drop']), create_lw_region(player, 'Zora Waterfall Area', None, ['Zora Waterfall Water Entry', 'Zora Waterfall SE', 'Zora Waterfall NE']), - create_lw_region(player, 'Zora Waterfall Water', None, ['Zora Waterfall Water Approach', 'Zora Waterfall Landing', 'Zora Whirlpool'], 'Light World', Terrain.Water), - create_lw_region(player, 'Zora Waterfall Entryway', None, ['Zora Waterfall Water Drop', 'Waterfall of Wishing']), + create_lw_region(player, 'Zora Waterfall Water', None, ['Zora Waterfall Approach', 'Zora Waterfall Landing', 'Zora Whirlpool'], 'Light World', Terrain.Water), + create_lw_region(player, 'Zora Waterfall Entryway', None, ['Waterfall of Wishing', 'Zora Waterfall Water Drop']), create_lw_region(player, 'Zoras Domain', ['King Zora', 'Zora\'s Ledge'], ['Zoras Domain SW']), create_lw_region(player, 'Lost Woods Pass West Area', None, ['Lost Woods Pass NW', 'Lost Woods Pass SW']), create_lw_region(player, 'Lost Woods Pass East Top Area', None, ['Lost Woods Pass Hammer (North)', 'Lost Woods Pass NE']), - create_lw_region(player, 'Lost Woods Pass Portal Area', None, ['Kakariko Teleporter', 'Lost Woods Pass Hammer (South)', 'Lost Woods Pass Rock (North)']), + create_lw_region(player, 'Lost Woods Pass Portal Area', None, ['Lost Woods Pass Hammer (South)', 'Lost Woods Pass Rock (North)', 'Kakariko Teleporter']), create_lw_region(player, 'Lost Woods Pass East Bottom Area', None, ['Lost Woods Pass Rock (South)', 'Lost Woods Pass SE']), create_lw_region(player, 'Kakariko Fortune Area', None, ['Fortune Teller (Light)', 'Kakariko Fortune NE', 'Kakariko Fortune EN', 'Kakariko Fortune ES', 'Kakariko Fortune SC']), - create_lw_region(player, 'Kakariko Pond Area', None, ['Kakariko Pond NE', 'Kakariko Pond WN', 'Kakariko Pond WS', 'Kakariko Pond SW', 'Kakariko Pond SE', 'Kakariko Pond EN', 'Kakariko Pond ES', 'Kakariko Pond Whirlpool']), + create_lw_region(player, 'Kakariko Pond Area', None, ['Kakariko Pond Whirlpool', 'Kakariko Pond NE', 'Kakariko Pond WN', 'Kakariko Pond WS', + 'Kakariko Pond SW', 'Kakariko Pond SE', 'Kakariko Pond EN', 'Kakariko Pond ES']), create_lw_region(player, 'Sanctuary Area', None, ['Sanctuary', 'Sanctuary WS', 'Sanctuary EC']), create_lw_region(player, 'Bonk Rock Ledge', None, ['Bonk Rock Cave', 'Bonk Rock Ledge Drop', 'Sanctuary WN']), - create_lw_region(player, 'Graveyard Area', None, ['Sanctuary Grave', 'Kings Grave Outer Rocks', 'Graveyard Ladder (Bottom)', 'Graveyard WC', 'Graveyard EC']), - create_lw_region(player, 'Graveyard Ledge', None, ['Graveyard Ledge Drop', 'Graveyard Ladder (Top)', 'Graveyard Cave']), - create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave Inner Rocks', 'Kings Grave']), - create_lw_region(player, 'River Bend Area', None, ['North Fairy Cave Drop', 'River Bend Water Drop', 'North Fairy Cave', 'River Bend WC', 'River Bend SW']), + create_lw_region(player, 'Graveyard Area', None, ['Sanctuary Grave', 'Kings Grave Rocks (Outer)', 'Graveyard Ladder (Bottom)', 'Graveyard WC', 'Graveyard EC']), + create_lw_region(player, 'Graveyard Ledge', None, ['Graveyard Cave', 'Graveyard Ledge Drop', 'Graveyard Ladder (Top)']), + create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Rocks (Inner)']), + create_lw_region(player, 'River Bend Area', None, ['North Fairy Cave Drop', 'North Fairy Cave', 'River Bend Water Drop', 'River Bend WC', 'River Bend SW']), create_lw_region(player, 'River Bend East Bank', None, ['River Bend East Water Drop', 'River Bend SE', 'River Bend EC', 'River Bend ES']), - create_lw_region(player, 'River Bend Water', None, ['River Bend West Pier', 'River Bend East Pier', 'River Bend EN', 'River Bend SC', 'River Bend Whirlpool'], 'Light World', Terrain.Water), - create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop Water Drop', 'Potion Shop Rock (South)', 'Potion Shop', 'Potion Shop WC', 'Potion Shop WS']), + create_lw_region(player, 'River Bend Water', None, ['River Bend West Pier', 'River Bend East Pier', 'River Bend Whirlpool', 'River Bend EN', 'River Bend SC'], 'Light World', Terrain.Water), + create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop', 'Potion Shop Water Drop', 'Potion Shop Rock (South)', 'Potion Shop WC', 'Potion Shop WS']), create_lw_region(player, 'Potion Shop Northeast', None, ['Potion Shop Northeast Water Drop', 'Potion Shop Rock (North)', 'Potion Shop EC']), create_lw_region(player, 'Potion Shop Water', None, ['Potion Shop WN', 'Potion Shop EN'], 'Light World', Terrain.Water), create_lw_region(player, 'Zora Approach Area', None, ['Zora Approach Rocks (West)', 'Zora Approach Bottom Ledge Drop', 'Zora Approach Water Drop', 'Zora Approach WC']), create_lw_region(player, 'Zora Approach Ledge', None, ['Zora Approach Rocks (East)', 'Zora Approach Ledge Drop', 'Zora Approach NE']), create_lw_region(player, 'Zora Approach Water', None, ['Zora Approach WN'], 'Light World', Terrain.Water), - create_lw_region(player, 'Kakariko Area', ['Bottle Merchant'], ['Kakariko Southwest Bush (North)', 'Kakariko Yard Bush (South)', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Blinds Hideout', - 'Elder House (West)', 'Elder House (East)', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', - 'Kakariko Shop', 'Tavern (Front)', 'Tavern North', 'Kakariko NW', 'Kakariko NC', 'Kakariko NE', 'Kakariko ES', 'Kakariko SE']), - create_lw_region(player, 'Kakariko Southwest', None, ['Kakariko Southwest Bush (South)', 'Light World Bomb Hut']), - create_lw_region(player, 'Kakariko Grass Yard', None, ['Kakariko Yard Bush (North)', 'Bush Covered House']), + create_lw_region(player, 'Kakariko Village', ['Bottle Merchant'], ['Kakariko Well Drop', 'Kakariko Well Cave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)', + 'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Kakariko Shop', 'Tavern (Front)', 'Tavern North', + 'Kakariko Southwest Bush (North)', 'Kakariko Yard Bush (South)', 'Kakariko NW', 'Kakariko NC', 'Kakariko NE', 'Kakariko ES', 'Kakariko SE']), + create_lw_region(player, 'Kakariko Southwest', None, ['Light World Bomb Hut', 'Kakariko Southwest Bush (South)']), + create_lw_region(player, 'Kakariko Bush Yard', None, ['Bush Covered House', 'Kakariko Yard Bush (North)']), create_lw_region(player, 'Forgotten Forest Area', None, ['Forgotten Forest NW', 'Forgotten Forest NE', 'Forgotten Forest ES']), - create_lw_region(player, 'Hyrule Castle Area', None, ['Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Main Gate (South)', 'Hyrule Castle Inner East Rock', 'Hyrule Castle Southwest Bush (North)', 'Top of Pyramid', 'Hyrule Castle WN', 'Hyrule Castle SE']), + create_lw_region(player, 'Hyrule Castle Area', None, ['Hyrule Castle Secret Entrance Drop', 'Hyrule Castle East Rock (Inner)', 'Hyrule Castle Southwest Bush (North)', + 'Hyrule Castle Main Gate (South)', 'Castle Gate Teleporter', 'Hyrule Castle WN', 'Hyrule Castle SE']), create_lw_region(player, 'Hyrule Castle Southwest', None, ['Hyrule Castle Southwest Bush (South)', 'Hyrule Castle SW']), - create_lw_region(player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Entrance (South)', 'Top of Pyramid (Inner)']), - create_lw_region(player, 'Hyrule Castle Courtyard Northeast', None, ['Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Secret Entrance Stairs']), - create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Ledge Drop', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Entrance', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Entrance (East)', 'Inverted Pyramid Hole'], 'the castle rampart'), - create_lw_region(player, 'Hyrule Castle East Entry', None, ['Hyrule Castle Outer East Rock', 'Hyrule Castle ES']), + create_lw_region(player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Entrance (South)', 'Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Main Gate (North)', 'Castle Gate Teleporter (Inner)']), + create_lw_region(player, 'Hyrule Castle Courtyard Northeast', None, ['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Courtyard Bush (North)']), + create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Entrance (East)', 'Inverted Pyramid Entrance', 'Inverted Pyramid Hole', + 'Hyrule Castle Ledge Courtyard Drop', 'Hyrule Castle Ledge Drop'], 'the castle rampart'), + create_lw_region(player, 'Hyrule Castle East Entry', None, ['Hyrule Castle East Rock (Outer)', 'Hyrule Castle ES']), create_lw_region(player, 'Hyrule Castle Water', None, [], 'Light World', Terrain.Water), create_lw_region(player, 'Wooden Bridge Area', None, ['Wooden Bridge Bush (South)', 'Wooden Bridge Water Drop', 'Wooden Bridge NW', 'Wooden Bridge SW']), create_lw_region(player, 'Wooden Bridge Northeast', None, ['Wooden Bridge Bush (North)', 'Wooden Bridge Northeast Water Drop', 'Wooden Bridge NE']), create_lw_region(player, 'Wooden Bridge Water', None, ['Wooden Bridge NC'], 'Light World', Terrain.Water), create_lw_region(player, 'Eastern Palace Area', None, ['Sahasrahlas Hut', 'Eastern Palace', 'Eastern Palace SW', 'Eastern Palace SE']), - create_lw_region(player, 'Eastern Cliff', None, ['Sand Dunes Ledge Drop', 'Stone Bridge East Ledge Drop', 'Tree Line Ledge Drop', 'Eastern Palace Ledge Drop']), - create_lw_region(player, 'Blacksmith Area', None, ['Blacksmiths Hut', 'Bat Cave Cave', 'Bat Cave Ledge Peg', 'Blacksmith WS']), - create_lw_region(player, 'Bat Cave Ledge', None, ['Bat Cave Ledge Peg (East)', 'Bat Cave Drop']), + create_lw_region(player, 'Eastern Cliff', None, ['Sand Dunes Cliff Ledge Drop', 'Stone Bridge East Cliff Ledge Drop', 'Tree Line Cliff Ledge Drop', 'Eastern Palace Cliff Ledge Drop']), + create_lw_region(player, 'Blacksmith Area', None, ['Blacksmiths Hut', 'Bat Cave Cave', 'Blacksmith Ledge Peg (West)', 'Blacksmith WS']), + create_lw_region(player, 'Blacksmith Ledge', None, ['Bat Cave Drop', 'Blacksmith Ledge Peg (East)']), create_lw_region(player, 'Sand Dunes Area', None, ['Sand Dunes NW', 'Sand Dunes WN', 'Sand Dunes SC']), create_lw_region(player, 'Maze Race Area', None, ['Maze Race ES']), create_lw_region(player, 'Maze Race Ledge', None, ['Two Brothers House (West)', 'Maze Race Game'], 'a race against time'), - create_lw_region(player, 'Maze Race Prize', ['Maze Race'], ['Maze Race Ledge Drop']), #this is a separate region to make OWG item get possible without allowing the Entrance access + create_lw_region(player, 'Maze Race Prize', ['Maze Race'], ['Maze Race Ledge Drop']), # this is a separate region to make OWG item get possible without allowing the Entrance access create_lw_region(player, 'Kakariko Suburb Area', None, ['Library', 'Two Brothers House (East)', 'Kakariko Gamble Game', 'Kakariko Suburb NE', 'Kakariko Suburb WS', 'Kakariko Suburb ES']), create_lw_region(player, 'Flute Boy Area', ['Flute Spot'], ['Flute Boy SC']), create_lw_region(player, 'Flute Boy Pass', None, ['Flute Boy WS', 'Flute Boy SW']), - create_lw_region(player, 'Central Bonk Rocks Area', None, ['Bonk Fairy (Light)', 'Central Bonk Rocks NW', 'Central Bonk Rocks SW', 'Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES']), + create_lw_region(player, 'Central Bonk Rocks Area', None, ['Bonk Fairy (Light)', 'Central Bonk Rocks NW', 'Central Bonk Rocks SW', + 'Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES']), create_lw_region(player, 'Links House Area', None, ['Links House', 'Links House NE', 'Links House WN', 'Links House WC', 'Links House WS', 'Links House SC', 'Links House ES']), - create_lw_region(player, 'Stone Bridge North Area', None, ['Stone Bridge Southbound', 'Stone Bridge NC', 'Stone Bridge EN']), - create_lw_region(player, 'Stone Bridge South Area', None, ['Stone Bridge Northbound', 'Stone Bridge WS', 'Stone Bridge SC']), + create_lw_region(player, 'Stone Bridge North Area', None, ['Stone Bridge (Southbound)', 'Stone Bridge NC', 'Stone Bridge EN']), + create_lw_region(player, 'Stone Bridge South Area', None, ['Stone Bridge (Northbound)', 'Stone Bridge WS', 'Stone Bridge SC']), create_lw_region(player, 'Stone Bridge Water', None, ['Stone Bridge WC', 'Stone Bridge EC'], 'Light World', Terrain.Water), create_lw_region(player, 'Hobo Bridge', ['Hobo'], ['Hobo EC'], 'Light World', Terrain.Water), - create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'C Whirlpool Portal Cliff Ledge Drop', 'Statues Cliff Ledge Drop']), + create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Northwest Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'C Whirlpool Portal Cliff Ledge Drop', 'Statues Cliff Ledge Drop']), create_lw_region(player, 'Tree Line Area', None, ['Lake Hylia Fairy', 'Tree Line WN', 'Tree Line NW', 'Tree Line SE']), create_lw_region(player, 'Tree Line Water', None, ['Tree Line WC', 'Tree Line SC'], 'Light World', Terrain.Water), create_lw_region(player, 'Eastern Nook Area', None, ['Long Fairy Cave', 'East Hyrule Teleporter', 'Eastern Nook NE']), - create_lw_region(player, 'Desert Area', None, ['Desert Palace Statue Move', 'Checkerboard Ledge Approach', 'Aginahs Cave', 'Desert ES']), - create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Ledge Outer Rocks', 'Desert Ledge Drop', 'Desert Palace Entrance (West)'], 'the desert ledge'), - create_lw_region(player, 'Desert Palace Entrance (North) Spot', None, ['Desert Ledge Inner Rocks', 'Desert Palace Entrance (North)'], 'the desert ledge'), - create_lw_region(player, 'Desert Checkerboard Ledge', None, ['Checkerboard Ledge Leave', 'Checkerboard Ledge Drop', 'Checkerboard Cave']), - create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)'], 'a sandy vista'), - create_lw_region(player, 'Desert Palace Mouth', None, ['Desert Mouth Drop', 'Desert Palace Entrance (East)']), - create_lw_region(player, 'Desert Palace Teleporter Ledge', None, ['Desert Teleporter Drop', 'Desert Teleporter']), - create_lw_region(player, 'Desert Northeast Cliffs', None, ['Desert Boss Cliff Ledge Drop', 'Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', 'Dam Cliff Ledge Drop']), + create_lw_region(player, 'Desert Area', None, ['Aginahs Cave', 'Desert Statue Move', 'Checkerboard Ledge Approach', 'Desert ES']), + create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (West)', 'Desert Ledge Rocks (Outer)', 'Desert Ledge Drop'], 'the desert ledge'), + create_lw_region(player, 'Desert Ledge Keep', None, ['Desert Palace Entrance (North)', 'Desert Ledge Rocks (Inner)'], 'the desert ledge'), + create_lw_region(player, 'Desert Checkerboard Ledge', None, ['Checkerboard Cave', 'Checkerboard Ledge Drop', 'Checkerboard Ledge Leave']), + create_lw_region(player, 'Desert Stairs', None, ['Desert Palace Entrance (South)']), + create_lw_region(player, 'Desert Mouth', None, ['Desert Palace Entrance (East)', 'Desert Mouth Drop'], 'a sandy vista'), + create_lw_region(player, 'Desert Teleporter Ledge', None, ['Desert Teleporter Drop', 'Desert Teleporter']), + create_lw_region(player, 'Desert Northern Cliffs', None, ['Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', 'Dam Cliff Ledge Drop']), create_lw_region(player, 'Bombos Tablet Ledge', ['Bombos Tablet'], ['Bombos Tablet Drop', 'Desert EC']), - create_lw_region(player, 'Flute Boy Approach Area', None, ['Flute Boy Bush (South)', 'Cave 45 Inverted Approach', 'Flute Boy Approach NW', 'Flute Boy Approach EC']), + create_lw_region(player, 'Flute Boy Approach Area', None, ['Flute Boy Bush (South)', 'Cave 45 Approach', 'Flute Boy Approach NW', 'Flute Boy Approach EC']), create_lw_region(player, 'Flute Boy Bush Entry', None, ['Flute Boy Bush (North)', 'Flute Boy Approach NC']), - create_lw_region(player, 'Cave 45 Ledge', None, ['Cave 45 Inverted Leave', 'Cave 45 Ledge Drop', 'Cave 45']), - create_lw_region(player, 'C Whirlpool Area', None, ['C Whirlpool Rock (Bottom)', 'C Whirlpool Pegs (Right)', 'C Whirlpool Water Entry', 'C Whirlpool EN', 'C Whirlpool ES', 'C Whirlpool SC']), - create_lw_region(player, 'C Whirlpool Portal Area', None, ['C Whirlpool Pegs (Left)', 'South Hyrule Teleporter']), + create_lw_region(player, 'Cave 45 Ledge', None, ['Cave 45', 'Cave 45 Ledge Drop', 'Cave 45 Leave']), + create_lw_region(player, 'C Whirlpool Area', None, ['C Whirlpool Rock (Bottom)', 'C Whirlpool Pegs (Outer)', 'C Whirlpool Water Entry', 'C Whirlpool EN', 'C Whirlpool ES', 'C Whirlpool SC']), + create_lw_region(player, 'C Whirlpool Portal Area', None, ['C Whirlpool Pegs (Inner)', 'South Hyrule Teleporter']), create_lw_region(player, 'C Whirlpool Water', None, ['C Whirlpool Landing', 'C Whirlpool', 'C Whirlpool EC'], 'Light World', Terrain.Water), create_lw_region(player, 'C Whirlpool Outer Area', None, ['C Whirlpool Rock (Top)', 'C Whirlpool WC', 'C Whirlpool NW']), - create_lw_region(player, 'Statues Area', None, ['Statues Water Entry', 'Light Hype Fairy', 'Statues NC', 'Statues WN', 'Statues WS', 'Statues SC']), + create_lw_region(player, 'Statues Area', None, ['Light Hype Fairy', 'Statues Water Entry', 'Statues NC', 'Statues WN', 'Statues WS', 'Statues SC']), create_lw_region(player, 'Statues Water', None, ['Statues Landing', 'Statues WC'], 'Light World', Terrain.Water), - create_lw_region(player, 'Lake Hylia Area', None, ['Lake Hylia Water Drop', 'Lake Hylia Fortune Teller', 'Cave Shop (Lake Hylia)', 'Lake Hylia NW']), - create_lw_region(player, 'Lake Hylia South Shore', None, ['Lake Hylia South Water Drop', 'Mini Moldorm Cave', 'Lake Hylia WS', 'Lake Hylia ES']), + create_lw_region(player, 'Lake Hylia Northwest Bank', None, ['Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Lake Hylia Water Drop', 'Lake Hylia NW']), create_lw_region(player, 'Lake Hylia Northeast Bank', None, ['Lake Hylia Northeast Water Drop', 'Lake Hylia NE']), - create_lw_region(player, 'Lake Hylia Central Island', None, ['Lake Hylia Central Water Drop', 'Capacity Upgrade', 'Lake Hylia Teleporter']), + create_lw_region(player, 'Lake Hylia South Shore', None, ['Mini Moldorm Cave', 'Lake Hylia South Water Drop', 'Lake Hylia WS', 'Lake Hylia ES']), + create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Water Drop', 'Lake Hylia Teleporter']), create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island'], ['Lake Hylia Island Water Drop']), - create_lw_region(player, 'Lake Hylia Water', None, ['Lake Hylia Central Island Pier', 'Lake Hylia Island Pier', 'Lake Hylia West Pier', 'Lake Hylia East Pier', 'Lake Hylia Water D Approach', 'Lake Hylia NC', 'Lake Hylia EC', 'Lake Hylia Whirlpool'], 'Light World', Terrain.Water), - create_lw_region(player, 'Lake Hylia Water D', None, ['Lake Hylia Water D Leave'], 'Light World', Terrain.Water), - create_lw_region(player, 'Ice Cave Area', None, ['Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave', 'Ice Cave SE', 'Ice Cave SW']), - create_lw_region(player, 'Desert Pass Area', ['Middle Aged Man'], ['Desert Pass Ladder (South)', 'Middle Aged Man', 'Desert Fairy', '50 Rupee Cave', 'Desert Pass WS', 'Desert Pass EC', 'Desert Pass Rocks (North)']), + create_lw_region(player, 'Lake Hylia Water', None, ['Lake Hylia Central Island Pier', 'Lake Hylia Island Pier', 'Lake Hylia West Pier', 'Lake Hylia East Pier', + 'Lake Hylia Water D Approach', 'Lake Hylia Whirlpool', 'Lake Hylia NC', 'Lake Hylia EC'], 'Light World', Terrain.Water), + create_lw_region(player, 'Lake Hylia Water D', None, ['Lake Hylia Water D Leave']), + create_lw_region(player, 'Ice Cave Area', None, ['Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave', 'Ice Cave Water Drop', 'Ice Cave SE']), + create_lw_region(player, 'Ice Cave Water', None, ['Ice Cave Pier', 'Ice Cave SW'], 'Light World', Terrain.Water), + create_lw_region(player, 'Desert Pass Area', ['Middle Aged Man'], ['Desert Fairy', '50 Rupee Cave', 'Middle Aged Man', 'Desert Pass Ladder (South)', 'Desert Pass Rocks (North)', 'Desert Pass WS', 'Desert Pass EC']), create_lw_region(player, 'Middle Aged Man', ['Purple Chest'], None), create_lw_region(player, 'Desert Pass Southeast', None, ['Desert Pass Rocks (South)', 'Desert Pass ES']), create_lw_region(player, 'Desert Pass Ledge', None, ['Desert Pass Ladder (North)', 'Desert Pass Ledge Drop', 'Desert Pass WC']), create_lw_region(player, 'Dam Area', ['Sunken Treasure'], ['Dam', 'Dam WC', 'Dam WS', 'Dam NC', 'Dam EC']), create_lw_region(player, 'South Pass Area', None, ['South Pass WC', 'South Pass NC', 'South Pass ES']), create_lw_region(player, 'Octoballoon Area', None, ['Octoballoon Water Drop', 'Octoballoon WS', 'Octoballoon NE']), - create_lw_region(player, 'Octoballoon Water', None, ['Octoballoon Pier', 'Octoballoon WC', 'Octoballoon Whirlpool'], 'Light World', Terrain.Water), + create_lw_region(player, 'Octoballoon Water', None, ['Octoballoon Pier', 'Octoballoon Whirlpool', 'Octoballoon WC'], 'Light World', Terrain.Water), create_lw_region(player, 'Octoballoon Water Ledge', None, ['Octoballoon Waterfall Water Drop', 'Octoballoon NW'], 'Light World', Terrain.Water), - - create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods Bush Rock (East)', 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', - 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods SE']), - create_dw_region(player, 'Skull Woods Portal Entry', None, ['Skull Woods Bush Rock (West)', 'Skull Woods SC']), + + create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', + 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Rock (East)', 'Skull Woods SE']), + create_dw_region(player, 'Skull Woods Portal Entry', None, ['Skull Woods Rock (West)', 'Skull Woods SC']), create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section'], 'a deep, dark forest'), create_dw_region(player, 'Skull Woods Forgotten Path (Southwest)', None, ['Skull Woods Forgotten Bush (West)', 'Skull Woods SW']), create_dw_region(player, 'Skull Woods Forgotten Path (Northeast)', None, ['Skull Woods Forgotten Bush (East)', 'Skull Woods EN']), - create_dw_region(player, 'Dark Lumberjack Area', None, ['Dark World Lumberjack Shop', 'Dark Lumberjack WN', 'Dark Lumberjack SW']), - create_dw_region(player, 'West Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (West)', 'GT Entry Approach', 'West Dark Death Mountain EN']), - create_dw_region(player, 'GT Approach', None, ['GT Entry Leave', 'Ganons Tower']), + create_dw_region(player, 'Dark Lumberjack Area', None, ['Dark Lumberjack Shop', 'Dark Lumberjack WN', 'Dark Lumberjack SW']), + create_dw_region(player, 'West Dark Death Mountain (Top)', None, ['GT Approach', 'West Dark Death Mountain Drop', 'West Dark Death Mountain EN']), + create_dw_region(player, 'GT Stairs', None, ['Ganons Tower', 'GT Leave']), create_dw_region(player, 'West Dark Death Mountain (Bottom)', None, ['Spike Cave', 'Dark Death Mountain Fairy', 'Dark Death Mountain Teleporter (West)', 'West Dark Death Mountain ES']), - create_dw_region(player, 'East Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Superbunny Cave (Top)', 'Hookshot Cave', 'East Dark Death Mountain WN', 'East Dark Death Mountain EN']), - create_dw_region(player, 'East Dark Death Mountain (Bottom)', None, ['East Dark Death Mountain Bushes', 'Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Teleporter (East)']), + create_dw_region(player, 'East Dark Death Mountain (Top)', None, ['Superbunny Cave (Top)', 'Hookshot Cave', 'East Dark Death Mountain Drop', 'East Dark Death Mountain WN', 'East Dark Death Mountain EN']), + create_dw_region(player, 'Dark Death Mountain Floating Island', None, ['Hookshot Cave Back Entrance', 'Floating Island Drop'], 'a dark floating island'), + create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)'], 'a dark ledge'), + create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Turtle Rock Isolated Ledge Entrance'], 'a dark vista'), + create_dw_region(player, 'East Dark Death Mountain (Bottom)', None, ['Superbunny Cave (Bottom)', 'Dark Death Mountain Shop', 'East Dark Death Mountain Bushes', 'East Dark Death Mountain Teleporter']), create_dw_region(player, 'East Dark Death Mountain (Bushes)', None, []), create_dw_region(player, 'East Dark Death Mountain (Bottom Left)', None, ['East Dark Death Mountain WS']), - create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)'], 'a dark ledge'), - create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Turtle Rock Isolated Ledge Entrance'], 'a dark vista'), - create_dw_region(player, 'Dark Death Mountain Floating Island', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance'], 'a dark floating island'), - create_dw_region(player, 'Turtle Rock Area', None, ['Turtle Rock Tail Ledge Drop', 'Turtle Rock', 'Turtle Rock WN']), + create_dw_region(player, 'Turtle Rock Area', None, ['Turtle Rock', 'Turtle Rock Tail Ledge Drop', 'Turtle Rock WN']), create_dw_region(player, 'Turtle Rock Ledge', ['Turtle Medallion Pad'], ['Turtle Rock Ledge Drop', 'Turtle Rock Teleporter']), - create_dw_region(player, 'Bumper Cave Area', None, ['Bumper Cave Entrance Rock', 'Bumper Cave NW', 'Bumper Cave SE']), - create_dw_region(player, 'Bumper Cave Entrance', None, ['Bumper Cave Ledge Drop', 'Bumper Cave (Bottom)']), - create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Entrance Drop', 'Bumper Cave (Top)'], 'a ledge with an item'), + create_dw_region(player, 'Bumper Cave Area', None, ['Bumper Cave Rock (Outer)', 'Bumper Cave NW', 'Bumper Cave SE']), + create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave (Top)', 'Bumper Cave Ledge Drop'], 'a ledge with an item'), + create_dw_region(player, 'Bumper Cave Entry', None, ['Bumper Cave (Bottom)', 'Bumper Cave Rock (Inner)', 'Bumper Cave Entry Drop']), create_dw_region(player, 'Catfish Area', ['Catfish'], ['Catfish SE']), create_dw_region(player, 'Skull Woods Pass West Area', None, ['Skull Woods Pass Bush Row (West)', 'Skull Woods Pass NW', 'Skull Woods Pass SW']), create_dw_region(player, 'Skull Woods Pass East Top Area', None, ['Skull Woods Pass Bush Row (East)', 'Skull Woods Pass Bush (North)', 'Skull Woods Pass NE']), - create_dw_region(player, 'Skull Woods Pass Portal Area', None, ['West Dark World Teleporter', 'Skull Woods Pass Bush (South)', 'Skull Woods Pass Rock (North)']), + create_dw_region(player, 'Skull Woods Pass Portal Area', None, ['Skull Woods Pass Bush (South)', 'Skull Woods Pass Rock (North)', 'West Dark World Teleporter']), create_dw_region(player, 'Skull Woods Pass East Bottom Area', None, ['Skull Woods Pass Rock (South)', 'Skull Woods Pass SE']), create_dw_region(player, 'Dark Fortune Area', None, ['Fortune Teller (Dark)', 'Dark Fortune NE', 'Dark Fortune EN', 'Dark Fortune ES', 'Dark Fortune SC']), create_dw_region(player, 'Outcast Pond Area', None, ['Outcast Pond NE', 'Outcast Pond WN', 'Outcast Pond WS', 'Outcast Pond SW', 'Outcast Pond SE', 'Outcast Pond EN', 'Outcast Pond ES']), @@ -164,35 +175,36 @@ def create_regions(world, player): create_dw_region(player, 'Qirn Jump Area', None, ['Qirn Jump Water Drop', 'Qirn Jump WC', 'Qirn Jump SW']), create_dw_region(player, 'Qirn Jump East Bank', None, ['Qirn Jump East Water Drop', 'Qirn Jump SE', 'Qirn Jump EC', 'Qirn Jump ES']), create_dw_region(player, 'Qirn Jump Water', None, ['Qirn Jump Pier', 'Qirn Jump Whirlpool', 'Qirn Jump EN', 'Qirn Jump SC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Dark Witch Area', None, ['Dark Witch Water Drop', 'Dark Witch Rock (South)', 'Dark World Potion Shop', 'Dark Witch WC', 'Dark Witch WS']), + create_dw_region(player, 'Dark Witch Area', None, ['Dark Potion Shop', 'Dark Witch Water Drop', 'Dark Witch Rock (South)', 'Dark Witch WC', 'Dark Witch WS']), create_dw_region(player, 'Dark Witch Northeast', None, ['Dark Witch Northeast Water Drop', 'Dark Witch Rock (North)', 'Dark Witch EC']), create_dw_region(player, 'Dark Witch Water', None, ['Dark Witch WN', 'Dark Witch EN'], 'Dark World', Terrain.Water), create_dw_region(player, 'Catfish Approach Area', None, ['Catfish Approach Rocks (West)', 'Catfish Approach Bottom Ledge Drop', 'Catfish Approach Water Drop', 'Catfish Approach WC']), create_dw_region(player, 'Catfish Approach Ledge', None, ['Catfish Approach Rocks (East)', 'Catfish Approach Ledge Drop', 'Catfish Approach NE']), create_dw_region(player, 'Catfish Approach Water', None, ['Catfish Approach WN'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Village of Outcasts Area', None, ['Village of Outcasts Pegs', 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Brewery', 'Village of Outcasts NW', 'Village of Outcasts NC', 'Village of Outcasts NE', 'Village of Outcasts ES', 'Village of Outcasts SE']), - create_dw_region(player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']), - create_dw_region(player, 'Shield Shop Area', None, ['Shield Shop Fence (Outer) Ledge Drop', 'Shield Shop NW', 'Shield Shop NE']), - create_dw_region(player, 'Shield Shop Fence', None, ['Shield Shop Fence (Inner) Ledge Drop', 'Red Shield Shop']), - create_dw_region(player, 'Pyramid Area', ['Pyramid Crack', 'Pyramid'], ['Pyramid Crack', 'Pyramid Hole', 'Pyramid ES']), + create_dw_region(player, 'Village of Outcasts', None, ['Chest Game', 'Thieves Town', 'C-Shaped House', 'Brewery', 'Bush Yard Pegs (Outer)', + 'Village of Outcasts NW', 'Village of Outcasts NC', 'Village of Outcasts NE', 'Village of Outcasts ES', 'Village of Outcasts SE']), + create_dw_region(player, 'Village of Outcasts Bush Yard', None, ['Dark World Shop', 'Bush Yard Pegs (Inner)']), + create_dw_region(player, 'Shield Shop Area', None, ['Shield Shop Fence Drop (Outer)', 'Shield Shop NW', 'Shield Shop NE']), + create_dw_region(player, 'Shield Shop Fence', None, ['Red Shield Shop', 'Shield Shop Fence Drop (Inner)']), + create_dw_region(player, 'Pyramid Area', ['Pyramid Crack', 'Pyramid'], ['Pyramid Hole', 'Pyramid Crack', 'Pyramid ES']), create_dw_region(player, 'Pyramid Crack', None, ['Pyramid Fairy']), - create_dw_region(player, 'Pyramid Exit Ledge', None, ['Pyramid Exit Ledge Drop', 'Pyramid Entrance']), - create_dw_region(player, 'Pyramid Pass', None, ['Post Aga Inverted Teleporter', 'Pyramid SW', 'Pyramid SE']), + create_dw_region(player, 'Pyramid Exit Ledge', None, ['Pyramid Entrance', 'Pyramid Exit Ledge Drop']), + create_dw_region(player, 'Pyramid Pass', None, ['Post Aga Teleporter', 'Pyramid SW', 'Pyramid SE']), create_dw_region(player, 'Pyramid Water', None, [], 'Dark World', Terrain.Water), create_dw_region(player, 'Broken Bridge Area', None, ['Broken Bridge Hammer Rock (South)', 'Broken Bridge Water Drop', 'Broken Bridge SW']), create_dw_region(player, 'Broken Bridge Northeast', None, ['Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', 'Broken Bridge Northeast Water Drop', 'Broken Bridge NE']), create_dw_region(player, 'Broken Bridge West', None, ['Broken Bridge West Water Drop', 'Broken Bridge NW']), create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], 'Dark World', Terrain.Water), create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Palace of Darkness SW', 'Palace of Darkness SE']), - create_dw_region(player, 'Darkness Cliff', None, ['Dark Dunes Ledge Drop', 'Hammer Bridge North Ledge Drop', 'Dark Tree Line Ledge Drop', 'Palace of Darkness Ledge Drop']), + create_dw_region(player, 'Darkness Cliff', None, ['Dark Dunes Cliff Ledge Drop', 'Hammer Bridge North Cliff Ledge Drop', 'Dark Tree Line Cliff Ledge Drop', 'Palace of Darkness Cliff Ledge Drop']), + create_dw_region(player, 'Hammer Pegs Area', ['Dark Blacksmith Ruins'], ['Hammer Peg Cave', 'Peg Area Rocks (East)']), create_dw_region(player, 'Hammer Pegs Entry', None, ['Peg Area Rocks (West)', 'Hammer Pegs WS']), - create_dw_region(player, 'Hammer Pegs Area', ['Dark Blacksmith Ruins'], ['Peg Area Rocks (East)', 'Dark World Hammer Peg Cave']), create_dw_region(player, 'Dark Dunes Area', None, ['Dark Dunes NW', 'Dark Dunes WN', 'Dark Dunes SC']), create_dw_region(player, 'Dig Game Area', ['Digging Game'], ['Dig Game To Ledge Drop', 'Dig Game ES']), create_dw_region(player, 'Dig Game Ledge', None, ['Dig Game Ledge Drop', 'Dig Game EC']), create_dw_region(player, 'Frog Area', None, ['Frog Ledge Drop', 'Frog Rock (Outer)', 'Archery Game Rock (North)', 'Frog NE']), create_dw_region(player, 'Frog Prison', ['Frog'], ['Frog Rock (Inner)']), - create_dw_region(player, 'Archery Game Area', None, ['Archery Game Rock (South)', 'Archery Game', 'Frog WC', 'Frog WS', 'Frog ES']), + create_dw_region(player, 'Archery Game Area', None, ['Archery Game', 'Archery Game Rock (South)', 'Frog WC', 'Frog WS', 'Frog ES']), create_dw_region(player, 'Stumpy Area', ['Stumpy'], ['Stumpy SC']), create_dw_region(player, 'Stumpy Pass', None, ['Stumpy WS', 'Stumpy SW']), create_dw_region(player, 'Dark Bonk Rocks Area', None, ['Bonk Fairy (Dark)', 'Dark Bonk Rocks NW', 'Dark Bonk Rocks SW', 'Dark Bonk Rocks EN', 'Dark Bonk Rocks EC', 'Dark Bonk Rocks ES']), @@ -200,30 +212,32 @@ def create_regions(world, player): create_dw_region(player, 'Hammer Bridge North Area', None, ['Hammer Bridge Pegs (North)', 'Hammer Bridge Water Drop', 'Hammer Bridge NC', 'Hammer Bridge EN']), create_dw_region(player, 'Hammer Bridge South Area', None, ['Hammer Bridge Pegs (South)', 'Hammer Bridge WS', 'Hammer Bridge SC']), create_dw_region(player, 'Hammer Bridge Water', None, ['Hammer Bridge Pier', 'Hammer Bridge EC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Dark Central Cliffs', None, ['Dark Bonk Rocks Cliff Ledge Drop', 'Bomb Shop Cliff Ledge Drop', 'Hammer Bridge South Cliff Ledge Drop', 'Ice Lake Area Cliff Ledge Drop', 'Ice Palace Island FAWT Ledge Drop', - 'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Portal Cliff Ledge Drop', 'Hype Cliff Ledge Drop']), + create_dw_region(player, 'Dark Central Cliffs', None, ['Dark Bonk Rocks Cliff Ledge Drop', 'Bomb Shop Cliff Ledge Drop', 'Hammer Bridge South Cliff Ledge Drop', 'Ice Lake Northwest Cliff Ledge Drop', 'Ice Palace Island FAWT Ledge Drop', + 'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Portal Cliff Ledge Drop', 'Hype Cliff Ledge Drop']), create_dw_region(player, 'Dark Tree Line Area', None, ['Dark Lake Hylia Fairy', 'Dark Tree Line WN', 'Dark Tree Line NW', 'Dark Tree Line SE']), create_dw_region(player, 'Dark Tree Line Water', None, ['Dark Tree Line WC', 'Dark Tree Line SC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Palace of Darkness Nook Area', None, ['East Dark World Hint', 'East Dark World Teleporter', 'Palace of Darkness Nook NE']), - create_dw_region(player, 'Misery Mire Area', None, ['Mire Shed', 'Misery Mire', 'Dark Desert Fairy', 'Dark Desert Hint']), - create_dw_region(player, 'Misery Mire Teleporter Ledge', None, ['Misery Mire Teleporter Ledge Drop', 'Misery Mire Teleporter']), - create_dw_region(player, 'Mire Northeast Cliffs', None, ['Mire Cliff Ledge Drop', 'Dark Checkerboard Cliff Ledge Drop', 'Archery Game Cliff Ledge Drop', 'Stumpy Approach Cliff Ledge Drop', 'Mire C Whirlpool Cliff Ledge Drop', 'Swamp Nook Cliff Ledge Drop', 'Swamp Cliff Ledge Drop', 'Mirror To Bombos Tablet Ledge']), + create_dw_region(player, 'Darkness Nook Area', None, ['East Dark World Hint', 'East Dark World Teleporter', 'Palace of Darkness Nook NE']), + create_dw_region(player, 'Mire Area', None, ['Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint']), + create_dw_region(player, 'Mire Teleporter Ledge', None, ['Mire Teleporter Ledge Drop', 'Mire Teleporter']), + create_dw_region(player, 'Mire Northern Cliffs', None, ['Mire Cliff Ledge Drop', 'Dark Checkerboard Cliff Ledge Drop', 'Archery Game Cliff Ledge Drop', 'Stumpy Approach Cliff Ledge Drop', 'Mire C Whirlpool Cliff Ledge Drop', 'Swamp Nook Cliff Ledge Drop', 'Swamp Cliff Ledge Drop', 'Mirror To Bombos Tablet Ledge']), create_dw_region(player, 'Stumpy Approach Area', None, ['Stumpy Approach Bush (South)', 'Stumpy Approach NW', 'Stumpy Approach EC']), create_dw_region(player, 'Stumpy Approach Bush Entry', None, ['Stumpy Approach Bush (North)', 'Stumpy Approach NC']), - create_dw_region(player, 'Dark C Whirlpool Area', None, ['Dark C Whirlpool Rock (Bottom)', 'Dark C Whirlpool Pegs (Right)', 'Dark C Whirlpool Water Entry', 'Dark C Whirlpool EN', 'Dark C Whirlpool ES', 'Dark C Whirlpool SC']), - create_dw_region(player, 'Dark C Whirlpool Portal Area', None, ['Dark C Whirlpool Pegs (Left)', 'South Dark World Teleporter']), + create_dw_region(player, 'Dark C Whirlpool Area', None, ['Dark C Whirlpool Rock (Bottom)', 'Dark C Whirlpool Pegs (Outer)', 'Dark C Whirlpool Water Entry', + 'Dark C Whirlpool EN', 'Dark C Whirlpool ES', 'Dark C Whirlpool SC']), + create_dw_region(player, 'Dark C Whirlpool Portal Area', None, ['Dark C Whirlpool Pegs (Inner)', 'South Dark World Teleporter']), create_dw_region(player, 'Dark C Whirlpool Water', None, ['Dark C Whirlpool Landing', 'Dark C Whirlpool EC'], 'Dark World', Terrain.Water), create_dw_region(player, 'Dark C Whirlpool Outer Area', None, ['Dark C Whirlpool Rock (Top)', 'Dark C Whirlpool WC', 'Dark C Whirlpool NW']), - create_dw_region(player, 'Hype Cave Area', None, ['Hype Cave Water Entry', 'Hype Cave', 'Hype Cave NC', 'Hype Cave WN', 'Hype Cave WS', 'Hype Cave SC']), + create_dw_region(player, 'Hype Cave Area', None, ['Hype Cave', 'Hype Cave Water Entry', 'Hype Cave NC', 'Hype Cave WN', 'Hype Cave WS', 'Hype Cave SC']), create_dw_region(player, 'Hype Cave Water', None, ['Hype Cave Landing', 'Hype Cave WC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Ice Lake Area', None, ['Ice Lake Water Drop', 'Dark Lake Hylia Shop', 'Ice Lake NW']), - create_dw_region(player, 'Ice Lake Northeast Bank', None, ['Ice Lake Northeast Water Drop', 'Ice Lake NE']), - create_dw_region(player, 'Ice Lake Ledge (West)', None, ['Ice Lake Southwest Water Drop', 'Ice Lake WS']), - create_dw_region(player, 'Ice Lake Ledge (East)', None, ['Ice Lake Southeast Water Drop', 'Ice Lake ES']), - create_dw_region(player, 'Ice Lake Water', None, ['Ice Lake Northeast Pier', 'Ice Lake Moat Bomb Jump', 'Ice Lake NC', 'Ice Lake EC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Ice Lake Moat', None, ['Ice Palace Teleporter', 'Ice Lake Moat Water Entry', 'Ice Lake Northeast Pier Hop']), + create_dw_region(player, 'Ice Lake Northwest Bank', None, ['Dark Lake Hylia Shop', 'Ice Lake Water Drop', 'Ice Lake NW']), + create_dw_region(player, 'Ice Lake Northeast Bank', None, ['Ice Lake Northeast Water Drop', 'Ice Lake Iceberg Bomb Jump', 'Ice Lake NE']), + create_dw_region(player, 'Ice Lake Southwest Ledge', None, ['Ice Lake Southwest Water Drop', 'Ice Lake WS']), + create_dw_region(player, 'Ice Lake Southeast Ledge', None, ['Ice Lake Southeast Water Drop', 'Ice Lake ES']), + create_dw_region(player, 'Ice Lake Water', None, ['Ice Lake Northeast Pier', 'Ice Lake NC', 'Ice Lake EC'], 'Dark World', Terrain.Water), + create_dw_region(player, 'Ice Lake Iceberg', None, ['Ice Lake Iceberg Water Entry', 'Ice Lake Northeast Pier Hop', 'Ice Lake Teleporter']), create_dw_region(player, 'Ice Palace Area', None, ['Ice Palace']), - create_dw_region(player, 'Shopping Mall Area', None, ['Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Shopping Mall SW', 'Shopping Mall SE']), + create_dw_region(player, 'Shopping Mall Area', None, ['Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Shopping Mall Water Drop', 'Shopping Mall SE']), + create_dw_region(player, 'Shopping Mall Water', None, ['Shopping Mall Pier', 'Shopping Mall SW'], 'Dark World', Terrain.Water), create_dw_region(player, 'Swamp Nook Area', None, ['Swamp Nook EC', 'Swamp Nook ES']), create_dw_region(player, 'Swamp Area', None, ['Swamp Palace', 'Swamp WC', 'Swamp WS', 'Swamp NC', 'Swamp EC']), create_dw_region(player, 'Dark South Pass Area', None, ['Dark South Pass WC', 'Dark South Pass NC', 'Dark South Pass ES']), @@ -246,37 +260,38 @@ def create_regions(world, player): create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), - create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'), - create_cave_region(player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']), - create_cave_region(player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', '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, 'Light World Death Mountain Shop', 'a common shop', ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right']), - create_cave_region(player, 'Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']), - create_cave_region(player, 'Fairy Ascension Cave (Drop)', 'a connector', None, ['Fairy Ascension Cave Pots']), - create_cave_region(player, 'Fairy Ascension Cave (Top)', 'a connector', None, ['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']), create_cave_region(player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']), create_cave_region(player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']), create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']), + create_cave_region(player, 'Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']), + create_cave_region(player, 'Fairy Ascension Cave (Drop)', 'a connector', None, ['Fairy Ascension Cave Pots']), + create_cave_region(player, 'Fairy Ascension Cave (Top)', 'a connector', None, ['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']), + create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'), + create_cave_region(player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Paradox Shop']), + create_cave_region(player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', + '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 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'), create_cave_region(player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']), - create_dungeon_region(player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks + create_dungeon_region(player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks create_cave_region(player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']), create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']), create_cave_region(player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']), create_cave_region(player, 'Potion Shop', 'the potion shop', ['Potion Shop', 'Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right']), - create_cave_region(player, 'Kakariko Well (top)', 'a drop', ['Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right', - 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']), + create_cave_region(player, 'Kakariko Well (top)', 'a drop', ['Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right', 'Kakariko Well - Bottom'], + ['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']), create_cave_region(player, 'Kakariko Well (back)', 'a drop', ['Kakariko Well - Top']), create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']), - create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind's Hideout - Left", "Blind's Hideout - Right", - "Blind's Hideout - Far Left", "Blind's Hideout - Far Right"], ['Blinds Hideout N']), + create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', [ "Blind's Hideout - Left", "Blind's Hideout - Right", + "Blind's Hideout - Far Left", "Blind's Hideout - Far Right"], ['Blinds Hideout N']), create_cave_region(player, 'Blinds Hideout (Top)', 'a bounty of five items', ["Blind's Hideout - Top"]), create_cave_region(player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']), - create_cave_region(player, 'Snitch Lady (West)', 'a boring house'), create_cave_region(player, 'Snitch Lady (East)', 'a boring house'), + create_cave_region(player, 'Snitch Lady (West)', 'a boring house'), create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']), create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']), create_cave_region(player, 'Bush Covered House', 'the grass man'), @@ -301,60 +316,60 @@ def create_regions(world, player): create_cave_region(player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']), create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']), create_cave_region(player, 'Cave 45', 'a cave with an item', ['Cave 45']), - create_cave_region(player, 'Swamp Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Light Hype Fairy', 'a fairy fountain'), create_cave_region(player, 'Lake Hylia Fortune Teller', 'a fortune teller'), - create_cave_region(player, 'Cave Shop (Lake Hylia)', 'a common shop', ['Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right']), + create_cave_region(player, 'Lake Hylia Shop', 'a common shop', ['Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right']), create_cave_region(player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade - Left', 'Capacity Upgrade - Right']), + create_cave_region(player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', + 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']), create_cave_region(player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']), create_cave_region(player, 'Good Bee Cave', 'a cold bee'), create_cave_region(player, '20 Rupee Cave', 'a cave with some cash'), create_cave_region(player, 'Desert Healer Fairy', 'a fairy fountain'), create_cave_region(player, '50 Rupee Cave', 'a cave with some cash'), create_cave_region(player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']), - create_cave_region(player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', - 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']), - - create_cave_region(player, 'Dark World Lumberjack Shop', 'a common shop', ['Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right']), - create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), + + create_cave_region(player, 'Dark Lumberjack Shop', 'a common shop', ['Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right']), create_cave_region(player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'), - create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', None, - ['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit', 'Hookshot Cave Bonk Path', 'Hookshot Cave Hook Path']), + create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), + create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', None, ['Hookshot Cave Front Exit', 'Hookshot Cave Front to Middle', + 'Hookshot Cave Bonk Path', 'Hookshot Cave Hook Path']), create_cave_region(player, 'Hookshot Cave (Bonk Islands)', 'a connector', ['Hookshot Cave - Bottom Right']), create_cave_region(player, 'Hookshot Cave (Hook Islands)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Left']), - create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']), create_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']), + create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']), create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']), create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']), - create_cave_region(player, 'Cave Shop (Dark Death Mountain)', 'a common shop', ['Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right']), + create_cave_region(player, 'Dark Death Mountain Shop', 'a common shop', ['Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right']), create_cave_region(player, 'Bumper Cave (bottom)', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Bottom to Top']), create_cave_region(player, 'Bumper Cave (top)', 'a connector', None, ['Bumper Cave Exit (Top)', 'Bumper Cave Top To Bottom']), create_cave_region(player, 'Fortune Teller (Dark)', 'a fortune teller'), create_cave_region(player, 'Dark Sanctuary Hint', 'a storyteller', None, ['Dark Sanctuary Hint Exit']), - create_cave_region(player, 'Dark World Potion Shop', 'a common shop', ['Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right']), - create_cave_region(player, 'Village of Outcasts Shop', 'a common shop', ['Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right']), + create_cave_region(player, 'Dark Potion Shop', 'a common shop', ['Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right']), create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']), create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']), create_cave_region(player, 'Brewery', 'a house with a chest', ['Brewery']), + create_cave_region(player, 'Village of Outcasts Shop', 'a common shop', ['Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right']), create_cave_region(player, 'Red Shield Shop', 'the rare shop', ['Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right']), create_cave_region(player, 'Pyramid Fairy', 'a cave with two chests', ['Pyramid Fairy - Left', 'Pyramid Fairy - Right']), create_cave_region(player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']), create_cave_region(player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']), create_cave_region(player, 'Palace of Darkness Hint', 'a storyteller'), - create_cave_region(player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']), + create_cave_region(player, 'Hammer Peg Cave', 'a cave with an item', ['Peg Cave']), create_cave_region(player, 'Archery Game', 'a game of skill'), create_cave_region(player, 'Bonk Fairy (Dark)', 'a fairy fountain'), create_cave_region(player, 'Big Bomb Shop', 'the bomb shop', ['Big Bomb'], ['Big Bomb Shop Exit']), create_cave_region(player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'), create_cave_region(player, 'East Dark World Hint', 'a storyteller'), + create_cave_region(player, 'Mire Shed', 'a cave with two chests', ['Mire Shed - Left', 'Mire Shed - Right']), + create_cave_region(player, 'Mire Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Mire Hint', 'a storyteller'), create_cave_region(player, 'Hype Cave', 'a bounty of five items', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', - 'Hype Cave - Bottom', 'Hype Cave - Generous Guy']), + 'Hype Cave - Bottom', 'Hype Cave - Generous Guy']), create_cave_region(player, 'Dark Lake Hylia Shop', 'a common shop', ['Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right']), create_cave_region(player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'), create_cave_region(player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'), - create_cave_region(player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'), - create_cave_region(player, 'Mire Shed', 'a cave with two chests', ['Mire Shed - Left', 'Mire Shed - Right']), - create_cave_region(player, 'Dark Desert Healer Fairy', 'a fairy fountain'), - create_cave_region(player, 'Dark Desert Hint', 'a storyteller') + create_cave_region(player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint') ] @@ -688,6 +703,8 @@ def create_dungeon_regions(world, player): 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 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']), @@ -811,8 +828,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Mire BK Chest Ledge', 'Misery Mire', ['Misery Mire - Big Key Chest'], ['Mire BK Chest Ledge WS']), create_dungeon_region(player, 'Mire Warping Pool', 'Misery Mire', None, ['Mire Warping Pool ES', 'Mire Warping Pool Warp']), create_dungeon_region(player, 'Mire Torches Top', 'Misery Mire', None, ['Mire Torches Top Down Stairs', 'Mire Torches Top SW', 'Mire Torches Top Holes']), - create_dungeon_region(player, 'Mire Torches Bottom', 'Misery Mire', None, ['Mire Torches Bottom NW', 'Mire Torches Bottom WS', 'Mire Torches Bottom Holes']), - create_dungeon_region(player, 'Mire Attic Hint', 'Misery Mire', None, ['Mire Attic Hint ES', 'Mire Attic Hint Hole']), + create_dungeon_region(player, 'Mire Torches Bottom', 'Misery Mire', None, ['Mire Torches Bottom NW', 'Mire Torches Bottom ES', 'Mire Torches Bottom Holes']), + create_dungeon_region(player, 'Mire Attic Hint', 'Misery Mire', None, ['Mire Attic Hint WS', 'Mire Attic Hint Hole']), create_dungeon_region(player, 'Mire Dark Shooters', 'Misery Mire', None, ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE']), create_dungeon_region(player, 'Mire Key Rupees', 'Misery Mire', None, ['Mire Key Rupees NE']), create_dungeon_region(player, 'Mire Block X', 'Misery Mire', None, ['Mire Block X NW', 'Mire Block X WS']), @@ -906,7 +923,8 @@ def create_dungeon_regions(world, player): ['GT Compass Room EN', 'GT Compass Room Warp']), create_dungeon_region(player, 'GT Invisible Bridges', 'Ganon\'s Tower', None, ['GT Invisible Bridges WS']), create_dungeon_region(player, 'GT Invisible Catwalk', 'Ganon\'s Tower', None, ['GT Invisible Catwalk ES', 'GT Invisible Catwalk WS', 'GT Invisible Catwalk NW', 'GT Invisible Catwalk NE']), - create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross WN']), + create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross Hammer Path']), + create_dungeon_region(player, 'GT Conveyor Cross Across Pits', 'Ganon\'s Tower', None, ['GT Conveyor Cross Hookshot Path', 'GT Conveyor Cross WN']), create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-Mid Path']), create_dungeon_region(player, 'GT Hookshot Mid Platform', 'Ganon\'s Tower', None, ['GT Hookshot Mid-East Path', 'GT Hookshot Mid-South Path', 'GT Hookshot Mid-North Path']), create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-Mid Path']), @@ -1092,42 +1110,31 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None def mark_light_dark_world_regions(world, player): # cross world caves may have some sections marked as both in_light_world, and in_dark_work. # That is ok. the bunny logic will check for this case and incorporate special rules. - def mark_light(): - queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) - seen = set(queue) - while queue: - current = queue.popleft() - current.is_light_world = True - for exit in current.exits: - if exit.connected_region is None or exit.connected_region.type == RegionType.DarkWorld: # todo: remove none check - # Don't venture into the dark world - continue - if exit.connected_region not in seen: - seen.add(exit.connected_region) - queue.append(exit.connected_region) + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) + seen = set(queue) + while queue: + current = queue.popleft() + current.is_light_world = True + for exit in current.exits: + if exit.connected_region is None or exit.connected_region.type == RegionType.DarkWorld: + # Don't venture into the dark world + continue + if exit.connected_region not in seen: + seen.add(exit.connected_region) + queue.append(exit.connected_region) - def mark_dark(): - queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) - seen = set(queue) - while queue: - current = queue.popleft() - current.is_dark_world = True - for exit in current.exits: - if exit.connected_region is not None: - if exit.connected_region.type == RegionType.LightWorld: - # Don't venture into the light world - continue - if exit.connected_region not in seen: - seen.add(exit.connected_region) - queue.append(exit.connected_region) - - # Note: I don't see why the order would matter, but the original Inverted code reversed the order - if world.mode[player] != 'inverted': - mark_light() - mark_dark() - else: - mark_dark() - mark_light() + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) + seen = set(queue) + while queue: + current = queue.popleft() + current.is_dark_world = True + for exit in current.exits: + if exit.connected_region is None or exit.connected_region.type == RegionType.LightWorld: + # Don't venture into the light world + continue + if exit.connected_region not in seen: + seen.add(exit.connected_region) + queue.append(exit.connected_region) def create_shops(world, player): @@ -1135,7 +1142,7 @@ def create_shops(world, 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 == 'Cave Shop (Lake Hylia)'): + or (0x35 in world.owswaps[player][0] and region_name == 'Lake Hylia Shop'): locked = True inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] custom = True @@ -1265,8 +1272,8 @@ def pot_address(pot_index, super_tile): bonk_prize_table = { 'Lost Woods Hideout Tree': (0x00, 0x10, False, '', 'Lost Woods East Area', 'in a tree'), 'Death Mountain Bonk Rocks': (0x01, 0x10, False, '', 'East Death Mountain (Top East)', 'encased in stone'), - 'Mountain Entry Pull Tree': (0x02, 0x10, False, '', 'Mountain Entry Area', 'in a tree'), - 'Mountain Entry Southeast Tree': (0x03, 0x08, False, '', 'Mountain Entry Area', 'in a tree'), + 'Mountain Pass Pull Tree': (0x02, 0x10, False, '', 'Mountain Pass Area', 'in a tree'), + 'Mountain Pass Southeast Tree': (0x03, 0x08, False, '', 'Mountain Pass Area', 'in a tree'), 'Lost Woods Pass West Tree': (0x04, 0x10, False, '', 'Lost Woods Pass West Area', 'in a tree'), 'Kakariko Portal Tree': (0x05, 0x08, False, '', 'Lost Woods Pass East Top Area', 'in a tree'), 'Fortune Bonk Rocks': (0x06, 0x10, False, '', 'Kakariko Fortune Area', 'encased in stone'), @@ -1275,8 +1282,8 @@ bonk_prize_table = { 'Sanctuary Tree': (0x09, 0x08, False, '', 'Sanctuary Area', 'in a tree'), 'River Bend West Tree': (0x0a, 0x10, True, '', 'River Bend Area', 'in a tree'), 'River Bend East Tree': (0x0b, 0x08, False, '', 'River Bend East Bank', 'in a tree'), - 'Blinds Hideout Tree': (0x0c, 0x10, False, '', 'Kakariko Area', 'in a tree'), - 'Kakariko Welcome Tree': (0x0d, 0x08, False, '', 'Kakariko Area', 'in a tree'), + 'Blinds Hideout Tree': (0x0c, 0x10, False, '', 'Kakariko Village', 'in a tree'), + 'Kakariko Welcome Tree': (0x0d, 0x08, False, '', 'Kakariko Village', 'in a tree'), 'Forgotten Forest Southwest Tree': (0x0e, 0x10, False, '', 'Forgotten Forest Area', 'in a tree'), 'Forgotten Forest Central Tree': (0x0f, 0x08, False, '', 'Forgotten Forest Area', 'in a tree'), #'Forgotten Forest Southeast Tree': (0x10, 0x04, False, '', 'Forgotten Forest Area', 'in a tree'), @@ -1304,7 +1311,8 @@ bonk_prize_table = { 'Dark Tree Line Tree 2': (0x26, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), 'Dark Tree Line Tree 3': (0x27, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), 'Dark Tree Line Tree 4': (0x28, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Hype Cave Statue': (0x29, 0x10, False, '', 'Hype Cave Area', 'encased in stone') + 'Hype Cave Statue': (0x29, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), + 'Cold Fairy Statue': (0x2a, 0x02, False, '', 'Good Bee Cave', 'encased in stone') } bonk_table_by_location_id = {0x2ABB00+(data[0]*6)+3: name for name, data in bonk_prize_table.items()} @@ -1316,16 +1324,16 @@ bonk_table_by_location = {y: x for x, y in bonk_table_by_location_id.items()} _basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)] _dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)] shop_table = { - 'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, False, False, _basic_shop_defaults, 0), + 'Dark Death Mountain Shop': (0x0112, ShopType.Shop, 0xC1, False, False, _basic_shop_defaults, 0), 'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, False, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)], 3), 'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults, 6), - 'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults, 9), + 'Dark Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults, 9), 'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults, 12), - 'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults, 15), - 'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults, 18), + 'Dark Potion Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults, 15), + 'Paradox Shop': (0x00FF, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults, 18), 'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults, 21), - 'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults, 24), + 'Lake Hylia Shop': (0x0112, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults, 24), 'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], 27), 'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, @@ -1334,15 +1342,15 @@ shop_table = { shop_to_location_table = { - 'Cave Shop (Dark Death Mountain)': ['Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right'], + 'Dark Death Mountain Shop': ['Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right'], 'Red Shield Shop': ['Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right'], 'Dark Lake Hylia Shop': ['Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right'], - 'Dark World Lumberjack Shop': ['Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right'], + 'Dark Lumberjack Shop': ['Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right'], 'Village of Outcasts Shop': ['Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right'], - 'Dark World Potion Shop': ['Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right'], - 'Light World Death Mountain Shop': ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right'], + 'Dark Potion Shop': ['Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right'], + 'Paradox Shop': ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right'], 'Kakariko Shop': ['Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right'], - 'Cave Shop (Lake Hylia)': ['Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right'], + 'Lake Hylia Shop': ['Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right'], 'Potion Shop': ['Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right'], 'Capacity Upgrade': ['Capacity Upgrade - Left', 'Capacity Upgrade - Right'], } @@ -1378,6 +1386,8 @@ flooded_keys_reverse = { 'Swamp Palace - Trench 1 Pot Key': 'Trench 1 Switch', 'Swamp Palace - Trench 2 Pot Key': 'Trench 2 Switch' } + + location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), 'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'), 'Flute Spot': (0x18014a, 0x18633d, False, 'underground'), diff --git a/Rom.py b/Rom.py index 1736c14c..a0729156 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '793bf8996f983ca60bfcd814c50e896e' +RANDOMIZERBASEHASH = '1602d9fd14dd9132df15c71cc7e6fa21' class JsonRom(object): @@ -701,17 +701,18 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): flute_spots = default_flute_connections else: flute_spots = world.owflutespots[player] - owFlags |= 0x100 + owFlags |= 0x0100 - for o in range(0, len(flute_spots)): - owslot = flute_spots[o] + flute_writes = sorted([(f, flute_data[f][1]) for f in flute_spots], key = lambda f: f[1]) + for o in range(0, len(flute_writes)): + owid = flute_writes[o][0] offset = 0 - data = flute_data[owslot] + data = flute_data[owid] if world.is_tile_swapped(data[1], player): offset = 0x40 - write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), data[1] + offset) # owid + write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid write_int16(rom, snes_to_pc(0x02E8D1 + (o * 2)), data[13] if offset > 0 and len(data) > 13 else data[5]) # link Y write_int16(rom, snes_to_pc(0x02E8F3 + (o * 2)), data[14] if offset > 0 and len(data) > 13 else data[6]) # link X @@ -755,12 +756,12 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): owMode = 2 if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] in ['limited', 'chaos']): - owMode |= 0x100 + owMode |= 0x0100 if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]): - owMode |= 0x200 + owMode |= 0x0200 world.fix_fake_world[player] = True if world.owMixed[player]: - owMode |= 0x400 + owMode |= 0x0400 # patches map data specific for OW Shuffle #inverted_buffer[0x03] = inverted_buffer[0x03] | 0x2 # convenient portal on WDM @@ -808,7 +809,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # for prize, address in zip(bonk_prizes, bonk_addresses): # rom.write_byte(address, prize) - owFlags |= 0x200 + owFlags |= 0x0200 # setting spriteID to D8, a placeholder sprite we use to inform ROM to spawn a dynamic item #for address in bonk_addresses: @@ -818,6 +819,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(snes_to_pc(0x09AE32), 0xD8) rom.write_byte(snes_to_pc(0x09AE35), 0xD8) + rom.write_byte(snes_to_pc(0x06918E), 0x80) # skip good bee bottle check + write_int16(rom, 0x150002, owMode) write_int16(rom, 0x150004, owFlags) @@ -948,6 +951,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): old_man_house = world.get_region('Old Man House', player) if should_be_bunny(old_man_house, world.mode[player]): rom.write_bytes(0x13fff4, [0xe4, 0x00]) + + old_man_cave = world.get_entrance('Old Man Cave Exit (East)', player) + if old_man_cave.connected_region.type == RegionType.DarkWorld: + rom.write_byte(0x13fff6, 0x40) # patch doors if world.doorShuffle[player] not in ['vanilla', 'basic']: @@ -1341,7 +1348,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x036D7F, 0xFF) # remove hammer usage rom.write_byte(0x043BD4, 0xFF) # remove powder usage elif world.swords[player] == 'bombs': - rom.write_byte(0x18002F, 0x01) # special bombs + rom.write_byte(0x18002F, 0x81) # special bombs rom.initial_sram.set_swordless_curtains() # open curtains rom.write_byte(0x180041, 0x02) # swordless medallions (always) rom.write_byte(0x180034, 0x00) # starting max bombs = 0 @@ -1389,7 +1396,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif world.swords[player] in ['byrna', 'somaria', 'cane']: rom.initial_sram.set_swordless_curtains() # open curtains rom.write_byte(0x180041, 0x02) # swordless medallions (always) - rom.write_byte(0x180034, 0x00) # starting max bombs = 0 # remove magic cost of cane(s) if world.swords[player] in ['byrna', 'cane']: @@ -1406,13 +1412,13 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x03B0AE, 0x81) if world.swords[player] == 'byrna': - rom.write_byte(0x18002F, 0x03) + rom.write_byte(0x18002F, 0x83) colr = 0x2C elif world.swords[player] == 'somaria': - rom.write_byte(0x18002F, 0x04) + rom.write_byte(0x18002F, 0x84) colr = 0x24 elif world.swords[player] == 'cane': - rom.write_byte(0x18002F, 0x05) + rom.write_byte(0x18002F, 0x85) colr = 0x28 spritedata = [ @@ -1512,7 +1518,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180212, warningflags) # Warning flags # assorted fixes - rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world else 0x00) # remain in real dark world when dying in dark world dungeon before killing aga1 + rom.write_byte(0x1800A2, 0x01 if world.fix_fake_world[player] else 0x00) # remain in real dark world when dying in dark world dungeon before killing aga1 rom.write_byte(0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. if world.is_atgt_swapped(player): rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted @@ -1755,7 +1761,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x301FC, 0xDA if world.bow_mode[player].startswith('retro') else 0xE1) # rupees replace arrows under pots if enemized: rom.write_byte(0x1B152E, 0xDA if world.bow_mode[player].startswith('retro') else 0xE1) - rom.write_byte(0x30052, 0xDB if world.bow_mode[player].startswith('retro') else 0xE2) # replace arrows in fish prize from bottle merchant + if world.bow_mode[player].startswith('retro'): + rom.write_byte(0x30052, 0xE4 if world.keyshuffle[player] == 'universal' else 0xDB) # replace arrows in fish prize from bottle merchant rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.bow_mode[player].startswith('retro') else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.bow_mode[player].startswith('retro') else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows rom.write_bytes(0xEDA5, [0x35, 0x41] if world.bow_mode[player].startswith('retro') else [0x43, 0x44]) # Chest game gives rupees instead of arrows @@ -1765,7 +1772,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill - rom.write_byte(0x180358, 0x01 if world.logic[player] in ['owglitches', 'nologic'] else 0x00) + rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'nologic']) else 0x00) # remove shield from uncle rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) @@ -1820,7 +1827,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # powder patch: remove the need to leave the screen after powder, since it causes problems for potion shop at race game # temporarally we are just nopping out this check we will conver this to a rom fix soon. - rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) + if world.powder_patch_required[player]: + rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA]) # sprite patches rom.write_byte(snes_to_pc(0x0DB7D1), 0x03) # patch apple sprites to not permadeatch like enemies @@ -1828,9 +1836,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.shuffle_bonk_drops[player]: # warning, this temporary patch might cause fairies to respawn differently?, limiting this to bonk drop mode only rom.write_byte(snes_to_pc(0x0DB808), 0x03) # patch fairies sprites to not permadeath like enemies + rom.write_byte(snes_to_pc(0x0DB810), 0x8A) # allows heart pieces to travel across water + # rom.write_byte(snes_to_pc(0x0DB730), 0x08) # allows chickens to travel across water # allow smith into multi-entrance caves in appropriate shuffles - if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): + if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -1868,13 +1878,13 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): Room0127.write_to_rom(snes_to_pc(0x2B8000), rom) if world.pot_contents[player]: - colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] - and (world.colorizepots[player] - or world.pottery[player] in ['reduced', 'clustered'])) + colorize_pots = (world.pottery[player] != 'vanilla' + and (world.colorizepots[player] or world.pottery[player] in ['reduced', 'clustered'])) if world.pot_contents[player].size() > 0x2800: raise Exception('Pot table is too big for current area') world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots) + write_enemizer_tweaks(rom, world, player) write_strings(rom, world, player, team) # write initial sram @@ -1885,12 +1895,16 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # set rom name # 21 bytes from Main import __version__ + from OverworldShuffle import __version__ as ORVersion seedstring = f'{world.seed:09}' if isinstance(world.seed, int) else world.seed # todo: change to DR when Enemizer is okay with DR rom.name = bytearray(f'ER{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}O\0', 'utf8')[:21] rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) + rom.write_bytes(0x138010, bytearray(__version__, 'utf8')) + rom.write_bytes(0x150010, bytearray(ORVersion, 'utf8')) + # set player names for p in range(1, min(world.players, 255) + 1): rom.write_bytes(0x195FFC + ((p - 1) * 32), hud_format_text(world.player_names[p][team])) @@ -1965,6 +1979,11 @@ def write_custom_shops(rom, world, player): rom.write_bytes(0x184900, items_data) +def write_enemizer_tweaks(rom, world, player): + if world.enemy_shuffle[player] != 'none': + rom.write_byte(snes_to_pc(0x1DF6D8), 0) # lets enemies walk on water instead of clipping into infinity? + rom.write_byte(snes_to_pc(0x0DB6B3), 0x82) # hovers don't need water necessarily? + def hud_format_text(text): output = bytes() for char in text.lower(): @@ -2332,8 +2351,9 @@ def write_strings(rom, world, player, team): entrances_to_hint = {} break # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. - entrances_to_hint.update(InconvenientOtherEntrances) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] not in ['lite', 'lean']: + entrances_to_hint.update(InconvenientOtherEntrances) + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'swapped']: hint_count = 0 elif world.shuffle[player] in ['simple', 'restricted']: hint_count = 2 @@ -2378,7 +2398,7 @@ def write_strings(rom, world, player, team): entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) - hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0 + hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 0 hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: @@ -2397,7 +2417,7 @@ def write_strings(rom, world, player, team): if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: locations_to_hint.extend(InconvenientVanillaLocations) random.shuffle(locations_to_hint) - hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5 + hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 5 hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 del locations_to_hint[hint_count:] for location in locations_to_hint: @@ -2456,10 +2476,9 @@ def write_strings(rom, world, player, team): # of how many exist. This supports many settings well. items_to_hint = RelevantItems.copy() flute_item = 'Ocarina' - if world.is_tile_swapped(0x18, player): + if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active': items_to_hint.remove(flute_item) flute_item = 'Ocarina (Activated)' - items_to_hint.append(flute_item) if world.owShuffle[player] != 'vanilla' or world.owMixed[player]: # Adding a guaranteed hint for the Flute in overworld shuffle. this_location = world.find_items_not_key_only(flute_item, player) @@ -2473,7 +2492,7 @@ def write_strings(rom, world, player, team): if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) random.shuffle(items_to_hint) - hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 + hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 8 hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0 while hint_count > 0 and len(items_to_hint) > 0: @@ -2672,7 +2691,7 @@ def write_strings(rom, world, player, team): tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' - tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) + tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! You can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) elif world.goal[player] in ['pedestal']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' @@ -2681,7 +2700,7 @@ def write_strings(rom, world, player, team): if world.goal[player] == 'trinity': trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player] tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text - tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) + tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! You can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) elif world.goal[player] == 'ganonhunt': tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon' elif world.goal[player] == 'completionist': @@ -2715,46 +2734,6 @@ def write_strings(rom, world, player, team): rom.write_byte(0x04a529, 0x19) # x tile shifted right a few tiles rom.write_byte(0x04a52e, 0x06) # follower set to blind maiden - if world.trolls[player]: - tt['telepathic_tile_eastern_palace'] = "Health will not help you against the Armos Knights." - tt['telepathic_tile_desert_bonk_torch_room'] = "Who ever saw lanmolas fly so fast?" - tt['telepathic_tile_tower_of_hera_entrance'] = "Please stop tickling my tail. It makes me freeze up and lower my defenses.\n\n - Moldorm" - tt['telepathic_tile_tower_of_hera_floor_4'] = "Hookshots and boomerangs are forbidden in my tower.\n\n - Moldorm" - tt['telepathic_tile_spectacle_rock'] = "With greater health and armor come shorter periods of refuge from damage." - tt['telepathic_tile_castle_tower'] = "Agahnim does not care for your foolish mortal patterns." - tt['telepathic_tile_palace_of_darkness'] = "Who weakened my mask?\n\n - Helmasaur King" - tt['telepathic_tile_swamp_entrance'] = "Please take your boots off before entering my room.\n\n - Arrghus" - tt['telepathic_tile_thieves_town_upstairs'] = "Secret power is said to be in the shovel." - tt['blind_by_the_light'] = "I'd really dig it if you didn't bring a shovel." - tt['telepathic_tile_ice_entrance'] = "Kholdstare's hypnotic eyes can be confusing..." - tt['telepathic_tile_ice_stalfos_knights_room'] = "Bomb fuses may be shorter or longer than they appear." - tt['telepathic_tile_ice_large_room'] = "Does your sword feel heavier than usual?" - tt['telepathic_tile_misery_mire'] = "Vitreous' small eyes require explosives to defeat." - tt['telepathic_tile_turtle_rock'] = "Trinexx has upped her defenses, requiring more powerful spells to defeat her. Consider whether you have enough magic reserves." - - ganon_mock = 'Can you keep up with my changing weaknesses?'; - tt['ganon_phase_3_no_bow'] = ganon_mock - tt['ganon_phase_3_no_silvers'] = ganon_mock - tt['ganon_phase_3_no_silvers_alt'] = ganon_mock - tt['ganon_phase_3_silvers'] = ganon_mock - - tt.insertText('ganon_fall_in_mushroom', "I smell a mushroom!") - tt.insertText('ganon_phase_3_mushroom', "Your mushroom grants me invulnerability!") - tt.insertText('ganon_phase_4_silvers', "\n SILVER ARROWS ") - tt.insertText('ganon_phase_4_boomerang', "\n BOOMERANG ") - tt.insertText('ganon_phase_4_hookshot', "\n HOOKSHOT ") - tt.insertText('ganon_phase_4_bomb', "\n BOMB ") - tt.insertText('ganon_phase_4_powder', "\n POWDER ") - tt.insertText('ganon_phase_4_fire_rod', "\n FIRE ROD ") - tt.insertText('ganon_phase_4_ice_rod', "\n ICE ROD ") - tt.insertText('ganon_phase_4_bombos', "\n BOMBOS ") - tt.insertText('ganon_phase_4_ether', "\n ETHER ") - tt.insertText('ganon_phase_4_quake', "\n QUAKE ") - tt.insertText('ganon_phase_4_hammer', "\n HAMMER ") - tt.insertText('ganon_phase_4_bee', "\n BEE ") - tt.insertText('ganon_phase_4_somaria', "\n SOMARIA ") - tt.insertText('ganon_phase_4_byrna', "\n BYRNA ") - # inverted spawn menu changes if world.mode[player] == 'inverted': tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s House\n Dark Chapel\n{CHOICE3}" @@ -3104,7 +3083,7 @@ InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main', InconvenientOtherEntrances = {'Death Mountain Return Cave (West)': 'The SW DM foothills cave', 'Mimic Cave': 'Mimic Ledge', - 'Dark World Hammer Peg Cave': 'The rows of pegs', + 'Hammer Peg Cave': 'The rows of pegs', 'Pyramid Fairy': 'The crack on the pyramid' } @@ -3173,15 +3152,15 @@ ItemEntrances = {'Blinds Hideout': 'Blind\'s old house', 'Chest Game': 'The westmost building in the Village of Outcasts' } -ShopEntrances = {'Cave Shop (Lake Hylia)': 'The cave NW Lake Hylia', +ShopEntrances = {'Lake Hylia Shop': 'The cave NW Lake Hylia', 'Kakariko Shop': 'The old Kakariko shop', 'Capacity Upgrade': 'The cave on the island', 'Dark Lake Hylia Shop': 'The building NW dark Lake Hylia', 'Dark World Shop': 'The hammer sealed building', 'Red Shield Shop': 'The fenced in building', - 'Cave Shop (Dark Death Mountain)': 'The base of east dark DM', - 'Dark World Potion Shop': 'The building near the catfish', - 'Dark World Lumberjack Shop': 'The northmost Dark World building' + 'Dark Death Mountain Shop': 'The base of east dark DM', + 'Dark Potion Shop': 'The building near the catfish', + 'Dark Lumberjack Shop': 'The northmost Dark World building' } OtherEntrances = {'Lake Hylia Fairy': 'A cave NE of Lake Hylia', @@ -3207,12 +3186,12 @@ OtherEntrances = {'Lake Hylia Fairy': 'A cave NE of Lake Hylia', 'Dark Lake Hylia Fairy': 'The cave NE dark Lake Hylia', 'Dark Death Mountain Fairy': 'The SW cave on dark DM', 'East Dark World Hint': 'The dark cave near the eastmost portal', - 'Dark Desert Hint': 'The cave east of the mire', + 'Mire Hint': 'The cave east of the mire', 'Palace of Darkness Hint': 'The building south of Kiki', 'Dark Lake Hylia Ledge Spike Cave': 'The rock SE dark Lake Hylia', 'Archery Game': 'The old archery game', 'Dark Lake Hylia Ledge Hint': 'The open cave SE dark Lake Hylia', - 'Dark Desert Fairy': 'The eastern hut in the mire', + 'Mire Fairy': 'The eastern hut in the mire', 'Dark Lake Hylia Ledge Fairy': 'The sealed cave SE dark Lake Hylia', 'Fortune Teller (Dark)': 'The building NE the Village of Outcasts' } diff --git a/Rules.py b/Rules.py index caa7df33..484266a6 100644 --- a/Rules.py +++ b/Rules.py @@ -21,23 +21,16 @@ def set_rules(world, player): return global_rules(world, player) - default_rules(world, player) ow_inverted_rules(world, player) - ow_bunny_rules(world, player) - - ow_terrain_rules(world, player) - - if world.mode[player] == 'standard': - if not world.is_copied_world: - standard_rules(world, player) - elif world.mode[player] == 'open' or world.mode[player] == 'inverted': - open_rules(world, player) - else: - raise NotImplementedError('Not implemented yet') - - bomb_rules(world, player) - pot_rules(world, player) + if world.swords[player] in ['swordless', 'swordless_hammer', 'bees']: + swordless_rules(world, player) + if world.swords[player] == 'bombs': + bomb_mode_rules(world, player) + if world.swords[player] in ['byrna', 'somaria', 'cane']: + cane_mode_rules(world, player) + if world.swords[player] in ['pseudo', 'assured_pseudo']: + pseudo_sword_mode_rules(world, player) if world.logic[player] == 'noglitches': no_glitches_rules(world, player) @@ -55,6 +48,18 @@ def set_rules(world, player): else: raise NotImplementedError('Not implemented yet') + ow_bunny_rules(world, player) + ow_terrain_rules(world, player) + + if world.mode[player] == 'standard': + if not world.is_copied_world: + standard_rules(world, player) + else: + misc_key_rules(world, player) + + bomb_rules(world, player) + pot_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)) @@ -70,6 +75,14 @@ def set_rules(world, player): elif world.goal[player] == 'completionist': add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) + if (world.flute_mode[player] != 'active' and not world.is_tile_swapped(0x18, player) + and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))): + if not world.is_copied_world: + # Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory + # kak_region = self.world.get_region('Kakariko Village', player) + # add_rule(world.get_location('Flute Activation', player), lambda state: state.has('Ocarina', player) and state.is_not_bunny(kak_region, player)) + add_rule(world.get_location('Flute Activation', player), lambda state: state.has('Ocarina', player)) + # if swamp and dam have not been moved we require mirror for swamp palace if not world.swamp_patch_required[player]: add_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has_Mirror(player)) @@ -128,6 +141,7 @@ 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_always_allow(spot, rule): spot.always_allow = rule @@ -188,8 +202,11 @@ def global_rules(world, player): #for exit in world.get_region('Flute Sky', player).exits: # exit.hide_path = True + # s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached set_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.has('Return Old Man', player)) + set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player)) + # flute rules set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Flute Spot 2', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Flute Spot 3', player), lambda state: state.can_flute(player)) @@ -199,25 +216,44 @@ def global_rules(world, player): set_rule(world.get_entrance('Flute Spot 7', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Flute Spot 8', player), lambda state: state.can_flute(player)) - set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) - set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) + # overworld location rules + set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_sword(player, 2)) set_rule(world.get_location('Old Man', player), lambda state: state.has('Return Old Man', player)) set_rule(world.get_location('Old Man Drop Off', player), lambda state: state.has('Escort Old Man', player)) + set_rule(world.get_location('Turtle Medallion Pad', player), lambda state: state.can_use_medallions(player) and state.has_turtle_rock_medallion(player)) # sword required to cast magic (!) + set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_sword(player, 2)) set_rule(world.get_location('Middle Aged Man', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Deliver Purple Chest', player)) # Can S&Q with chest + set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) + set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) set_rule(world.get_location('Big Bomb', player), lambda state: state.has('Crystal 5', player) and state.has('Crystal 6', player)) - set_rule(world.get_location('Pyramid Crack', player), lambda state: state.has('Pick Up Big Bomb', player)) - set_rule(world.get_entrance('Pyramid Crack', player), lambda state: state.has('Detonate Big Bomb', player)) - set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) + # bonk items + if world.shuffle_bonk_drops[player]: + if not world.is_copied_world: + from Regions import bonk_prize_table + for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items(): + loc = world.get_location(location_name, player) + if location_name == 'Cold Fairy Statue': + set_rule(loc, lambda state: state.can_use_bombs(player) and state.can_collect_bonkdrops(player)) + elif not aga_required: + set_rule(loc, lambda state: state.can_collect_bonkdrops(player)) + else: + set_rule(loc, lambda state: state.can_collect_bonkdrops(player) and state.has_beaten_aga(player)) + add_bunny_rule(loc, player) + + # underworld location rules + set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and state.can_reach('Potion Shop Area', 'Region', player)) + set_rule(world.get_location('Sick Kid', player), lambda state: state.has_bottle(player)) + set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player)) set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player)) set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player)) - set_rule(world.get_location('Sick Kid', player), lambda state: state.has_bottle(player)) set_rule(world.get_location('Library', player), lambda state: state.has_Boots(player)) - set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) - set_rule(world.get_location('Spike Cave', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and ((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or @@ -226,22 +262,145 @@ def global_rules(world, player): (state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4)))))) ) - set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_location('Hookshot Cave - Top Left', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player)) - set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player)) - + # underworld rules + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) # can erase block - overridden in noglitches set_rule(world.get_entrance('Hookshot Cave Bonk Path', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player)) set_rule(world.get_entrance('Hookshot Cave Hook Path', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Bumper Cave Bottom to Top', player), lambda state: state.has('Cape', player)) + set_rule(world.get_entrance('Bumper Cave Top To Bottom', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) + + # terrain rules + set_rule(world.get_entrance('DM Hammer Bridge (West)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('DM Hammer Bridge (East)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('DM Broken Bridge (West)', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('DM Broken Bridge (East)', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Fairy Ascension Rocks (Inner)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Fairy Ascension Rocks (Outer)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('TR Pegs Ledge Entry', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Mountain Pass Rock (Outer)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Mountain Pass Rock (Inner)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Zora Waterfall Water Entry', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Zora Waterfall Approach', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Kakariko Pond Whirlpool', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Kings Grave Rocks (Outer)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Kings Grave Rocks (Inner)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Potion Shop Rock (South)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Zora Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) + set_rule(world.get_entrance('Zora Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) + set_rule(world.get_entrance('Hyrule Castle East Rock (Inner)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Hyrule Castle East Rock (Outer)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Wooden Bridge Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Wooden Bridge Northeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Blacksmith Ledge Peg (West)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Blacksmith Ledge Peg (East)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Desert Statue Move', player), lambda state: state.has('Book of Mudora', player)) + set_rule(world.get_entrance('Desert Ledge Rocks (Outer)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Desert Ledge Rocks (Inner)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('C Whirlpool Rock (Bottom)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('C Whirlpool Rock (Top)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('C Whirlpool Pegs (Outer)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('C Whirlpool Pegs (Inner)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Lake Hylia Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Lake Hylia Central Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Lake Hylia Island Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Lake Hylia Water D Leave', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Ice Cave Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Desert Pass Rocks (North)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Desert Pass Rocks (South)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) + + set_rule(world.get_entrance('Skull Woods Rock (West)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Skull Woods Rock (East)', player), lambda state: state.can_lift_rocks(player)) + # this more like an ohko rule - dependent on bird being present too - so enemizer could turn this off? + set_rule(world.get_entrance('Bumper Cave Ledge Drop', player), lambda state: (state.has('Cape', player) or state.has('Cane of Byrna', player) or state.has_sword(player))) + set_rule(world.get_entrance('Bumper Cave Rock (Outer)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Bumper Cave Rock (Inner)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Qirn Jump Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Witch Rock (North)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Dark Witch Rock (South)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Catfish Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) + set_rule(world.get_entrance('Catfish Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) + set_rule(world.get_entrance('Bush Yard Pegs (Outer)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Bush Yard Pegs (Inner)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Broken Bridge Hammer Rock (South)', player), lambda state: state.can_lift_rocks(player) or state.has('Hammer', player)) + set_rule(world.get_entrance('Broken Bridge Hammer Rock (North)', player), lambda state: state.can_lift_rocks(player) or state.has('Hammer', player)) + set_rule(world.get_entrance('Broken Bridge Hookshot Gap', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Broken Bridge Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Broken Bridge Northeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Broken Bridge West Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Peg Area Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Peg Area Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Dig Game To Ledge Drop', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Frog Rock (Inner)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Frog Rock (Outer)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Archery Game Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Archery Game Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Hammer Bridge Pegs (North)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Hammer Bridge Pegs (South)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Hammer Bridge Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark C Whirlpool Rock (Bottom)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Dark C Whirlpool Rock (Top)', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Dark C Whirlpool Pegs (Outer)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Dark C Whirlpool Pegs (Inner)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Ice Lake Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Ice Lake Northeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Ice Lake Southwest Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Ice Lake Iceberg Water Entry', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Ice Lake Iceberg Bomb Jump', player), lambda state: state.can_use_bombs(player)) + set_rule(world.get_entrance('Shopping Mall Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) + + # entrance rules + # Caution: If king's grave is relaxed at all to account for reaching it via a two way cave's exit in insanity mode, then the bomb shop logic will need to be updated (that would involve create a small ledge-like Region for it) + # TODO: Not sure if this ^ is true anymore since Kings Grave is its own region now + set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has_beaten_aga(player)) + set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Checkerboard Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('20 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Hookshot Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_location('Pyramid Crack', player), lambda state: state.has('Pick Up Big Bomb', player)) + set_rule(world.get_entrance('Pyramid Crack', player), lambda state: state.has('Detonate Big Bomb', player)) + set_rule(world.get_entrance('Hammer Peg Cave', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Bonk Fairy (Dark)', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: state.can_lift_rocks(player)) + + set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player)) + set_rule(world.get_entrance('Misery Mire', player), lambda state: state.can_use_medallions(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Turtle Opened', player)) + + if not world.is_atgt_swapped(player): + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_sword(player, 2)) + set_rule(world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) # Start of door rando rules # TODO: Do these need to flag off when door rando is off? - some of them, yes + def is_trapped(entrance): + return world.get_entrance(entrance, player).door.trapped + # Eastern Palace # Eyegore room needs a bow set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player)) + if is_trapped('Eastern Single Eyegore ES'): + set_rule(world.get_entrance('Eastern Single Eyegore ES', player), lambda state: state.can_shoot_arrows(player)) + if is_trapped('Eastern Duo Eyegores SE'): + set_rule(world.get_entrance('Eastern Duo Eyegores SE', player), lambda state: state.can_shoot_arrows(player)) # Boss rules. Same as below but no BK or arrow requirement. set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player)) @@ -266,13 +425,18 @@ def global_rules(world, player): set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player)) + set_rule(world.get_entrance('Tower Circle of Pots NW', player), lambda state: state.can_kill_most_things(player)) + if is_trapped('Tower Circle of Pots ES'): + set_rule(world.get_entrance('Tower Circle of Pots ES', player), + lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) - set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player)) + if is_trapped('PoD Mimics 2 SW'): + set_rule(world.get_entrance('PoD Mimics 2 SW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player)) @@ -321,6 +485,8 @@ def global_rules(world, 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)) + if is_trapped('Skull Torch Room WS'): + set_rule(world.get_entrance('Skull Torch Room WS', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) hidden_pits_door = world.get_door('Skull Small Hall WS', player) @@ -358,6 +524,8 @@ def global_rules(world, player): 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_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player)) + if is_trapped('Ice Lobby SE'): + set_rule(world.get_entrance('Ice Lobby SE', player), lambda state: state.can_melt_things(player)) set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) @@ -372,6 +540,12 @@ def global_rules(world, player): set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player)) if not world.get_door('Ice Switch Room SE', player).entranceFlag: set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) + if is_trapped('Ice Switch Room ES'): + set_rule(world.get_entrance('Ice Switch Room ES', player), + lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', 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)) @@ -393,8 +567,15 @@ def global_rules(world, player): or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player)) + if is_trapped('Mire Tile Room SW'): + set_rule(world.get_entrance('Mire Tile Room SW', player), lambda state: state.has_fire_source(player)) + if is_trapped('Mire Tile Room ES'): + set_rule(world.get_entrance('Mire Tile Room ES', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player)) + if is_trapped('Mire Dark Shooters SE'): + 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)) @@ -410,6 +591,9 @@ def global_rules(world, player): set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + if is_trapped('TR Torches WN'): + set_rule(world.get_entrance('TR Torches WN', player), + lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player)) set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player)) @@ -429,10 +613,20 @@ def global_rules(world, 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)) - set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player)) + if is_trapped('GT Hope Room WN'): + set_rule(world.get_entrance('GT Hope Room WN', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player)) + if is_trapped('GT Conveyor Cross EN'): + set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hammer', player)) if not world.get_door('GT Speed Torch SE', player).entranceFlag: set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('GT Speed Torch NE'): + set_rule(world.get_entrance('GT Speed Torch NE', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('GT Speed Torch WS'): + set_rule(world.get_entrance('GT Speed Torch WS', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('GT Speed Torch WN'): + set_rule(world.get_entrance('GT Speed Torch WN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) @@ -467,6 +661,8 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player)) + if is_trapped('GT Torch Cross WN'): + set_rule(world.get_entrance('GT Torch Cross WN', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player)) # todo: the following only applies to crystal state propagation from this supertile # you can also reset the supertile, but I'm not sure how to model that @@ -687,7 +883,7 @@ def global_rules(world, player): def bomb_rules(world, player): # todo: kak well, pod hint (bonkable pots), hookshot pot, spike cave pots bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended. - bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Mini Moldorm Cave', + bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Paradox Shop', 'Mini Moldorm Cave', 'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front', 'Hookshot Cave Middle to Back', 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery', 'Paradox Cave Chest Area NE', 'Blinds Hideout N', 'Kakariko Well (top to back)', @@ -703,10 +899,10 @@ def bomb_rules(world, player): bombable_items = ['Chicken House', 'Aginah\'s Cave', 'Graveyard Cave', 'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom'] for location in bonkable_items: - add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) + add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) add_bunny_rule(world.get_location(location, player), player) for location in bombable_items: - add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player)) add_bunny_rule(world.get_location(location, player), player) cave_kill_locations = ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Spiral Cave'] @@ -737,13 +933,29 @@ def bomb_rules(world, player): ('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots. ('GT DMs Room SW', False) # Four red stalfos ] + conditional_kill_traps = [ + ('Hyrule Dungeon Armory Interior Key Door N', True), + ('Desert Compass Key Door WN', True), + ('Thieves Blocked Entry SW', True), + ('TR Tongue Pull WS', True), + ('TR Twin Pokeys NW', False), + ] for killdoor,bombable in easy_kill_rooms: if bombable: add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_kill_with_bombs(player) or state.can_kill_most_things(player))) else: add_rule(world.get_entrance(killdoor, player), lambda state: state.can_kill_most_things(player)) + for kill_door, bombable in conditional_kill_traps: + if world.get_entrance(kill_door, player).door.trapped: + if bombable: + add_rule(world.get_entrance(kill_door, player), + lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player))) + else: + add_rule(world.get_entrance(kill_door, player), lambda state: state.can_kill_most_things(player)) add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_kill_with_bombs(player) and state.has('Magic Powder'), player) + if world.get_entrance('Mire Cross SW', player).door.trapped: + add_rule(world.get_entrance('Mire Cross SW', player), lambda state: state.can_kill_most_things(player)) enemy_kill_drops = [ # Location, bool-bombable ('Hyrule Castle - Map Guard Key Drop', True), @@ -777,7 +989,7 @@ def bomb_rules(world, player): if world.doorShuffle[player] == 'vanilla': add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? - + dungeon_bonkable = ['Sewers Rat Path WS', 'Sewers Rat Path WN', 'PoD Warp Hint SE', 'PoD Jelly Hall NW', 'PoD Jelly Hall NE', 'PoD Mimics 1 SW', 'Thieves Ambush E', 'Thieves Rail Ledge W', @@ -820,7 +1032,7 @@ def pot_rules(world, player): (state.has('Cane of Byrna', player) and (state.can_extend_magic(player, 12, True) or (state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4))))))) - for l in world.get_region('Dark Desert Hint', player).locations: + for l in world.get_region('Mire Hint', player).locations: if l.type == LocationType.Pot: add_rule(l, lambda state: state.can_use_bombs(player)) for l in world.get_region('Palace of Darkness Hint', player).locations: @@ -850,165 +1062,7 @@ def pot_rules(world, player): add_rule(l, lambda state: state.can_hit_crystal(player)) -def default_rules(world, player): - set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player)) - # Underworld Logic - set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up - set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) # can erase block, overwritten in noglitches - set_rule(world.get_entrance('Bumper Cave Bottom to Top', player), lambda state: state.has('Cape', player)) - set_rule(world.get_entrance('Bumper Cave Top To Bottom', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) - set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling - - # Item Access - set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player)) - set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player)) - set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_sword(player, 2)) - set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_sword(player, 2)) - set_rule(world.get_location('Turtle Medallion Pad', player), lambda state: state.can_use_medallions(player) and state.has_turtle_rock_medallion(player)) - - # Bonk Item Access - if world.shuffle_bonk_drops[player]: - if not world.is_copied_world: - from Regions import bonk_prize_table - for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items(): - loc = world.get_location(location_name, player) - if not aga_required: - set_rule(loc, lambda state: state.can_collect_bonkdrops(player)) - else: - set_rule(loc, lambda state: state.can_collect_bonkdrops(player) and state.has_beaten_aga(player)) - add_bunny_rule(loc, player) - - # Entrance Access - set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has_beaten_aga(player)) - set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player)) - set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) - set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player)) - set_rule(world.get_entrance('Checkerboard Cave', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('20 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player)) - set_rule(world.get_entrance('Hookshot Cave', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Turtle Opened', player)) - set_rule(world.get_entrance('Dark World Hammer Peg Cave', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Bonk Fairy (Dark)', player), lambda state: state.has_Boots(player)) - set_rule(world.get_entrance('Misery Mire', player), lambda state: state.can_use_medallions(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) - set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: state.can_lift_rocks(player)) - - # Region Access - set_rule(world.get_entrance('DM Hammer Bridge (West)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('DM Hammer Bridge (East)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('DM Broken Bridge (West)', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_entrance('DM Broken Bridge (East)', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_entrance('Fairy Ascension Rocks (North)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Fairy Ascension Rocks (South)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('TR Pegs Ledge Entry', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Mountain Entry Entrance Rock (West)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Mountain Entry Entrance Rock (East)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Kings Grave Inner Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Potion Shop Rock (South)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Zora Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) - set_rule(world.get_entrance('Zora Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) - set_rule(world.get_entrance('Hyrule Castle Inner East Rock', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Hyrule Castle Outer East Rock', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Bat Cave Ledge Peg', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Bat Cave Ledge Peg (East)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Desert Palace Statue Move', player), lambda state: state.has('Book of Mudora', player)) - set_rule(world.get_entrance('Desert Ledge Outer Rocks', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Desert Ledge Inner Rocks', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('C Whirlpool Rock (Bottom)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('C Whirlpool Rock (Top)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('C Whirlpool Pegs (Left)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('C Whirlpool Pegs (Right)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Desert Pass Rocks (North)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Desert Pass Rocks (South)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Skull Woods Bush Rock (West)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Skull Woods Bush Rock (East)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Dark Witch Rock (North)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Dark Witch Rock (South)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Catfish Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) - set_rule(world.get_entrance('Catfish Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) - set_rule(world.get_entrance('Village of Outcasts Pegs', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Grassy Lawn Pegs', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Broken Bridge Hammer Rock (South)', player), lambda state: state.can_lift_rocks(player) or state.has('Hammer', player)) - set_rule(world.get_entrance('Broken Bridge Hammer Rock (North)', player), lambda state: state.can_lift_rocks(player) or state.has('Hammer', player)) - set_rule(world.get_entrance('Broken Bridge Hookshot Gap', player), lambda state: state.has('Hookshot', player)) - set_rule(world.get_entrance('Peg Area Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Peg Area Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Dig Game To Ledge Drop', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Frog Rock (Inner)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Frog Rock (Outer)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Archery Game Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Archery Game Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Hammer Bridge Pegs (North)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Hammer Bridge Pegs (South)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Dark C Whirlpool Rock (Bottom)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Dark C Whirlpool Rock (Top)', player), lambda state: state.can_lift_rocks(player)) - set_rule(world.get_entrance('Dark C Whirlpool Pegs (Left)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Dark C Whirlpool Pegs (Right)', player), lambda state: state.has('Hammer', player)) - - set_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Zora Waterfall Water Entry', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Zora Waterfall Water Approach', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Kakariko Pond Whirlpool', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Zora Approach Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Wooden Bridge Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Wooden Bridge Northeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('C Whirlpool Water Entry', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Statues Water Entry', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Lake Hylia Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Lake Hylia South Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Lake Hylia Central Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Lake Hylia Island Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Lake Hylia Water D Leave', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Ice Cave SW', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Octoballoon Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Qirn Jump Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Qirn Jump East Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Witch Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Catfish Approach Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Broken Bridge Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Broken Bridge Northeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Broken Bridge West Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Hammer Bridge Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Ice Lake Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Ice Lake Northeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Ice Lake Southwest Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Ice Lake Moat Water Entry', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Shopping Mall SW', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), lambda state: state.has('Flippers', player)) - - if world.swords[player] in ['swordless', 'swordless_hammer', 'bees']: - swordless_rules(world, player) - if world.swords[player] == 'bombs': - bomb_mode_rules(world, player) - if world.swords[player] in ['byrna', 'somaria', 'cane']: - cane_mode_rules(world, player) - if world.swords[player] in ['pseudo', 'assured_pseudo']: - pseudo_sword_mode_rules(world, player) def ow_inverted_rules(world, player): @@ -1016,8 +1070,8 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) else: set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_sword(player, 2) or state.has_beaten_aga(player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle - set_rule(world.get_entrance('GT Entry Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) - set_rule(world.get_entrance('GT Entry Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'crossed', 'insanity')) + set_rule(world.get_entrance('GT Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) + set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity')) if world.is_tile_swapped(0x03, player): set_rule(world.get_entrance('Spectacle Rock Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'] and state.has_Pearl(player)) @@ -1026,14 +1080,14 @@ def ow_inverted_rules(world, player): if not world.is_tile_swapped(0x05, player): set_rule(world.get_entrance('East Death Mountain Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) else: - set_rule(world.get_entrance('Dark Death Mountain Teleporter (East)', player), lambda state: state.can_lift_heavy_rocks(player)) - + set_rule(world.get_entrance('East Dark Death Mountain Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) + if not world.is_tile_swapped(0x07, player): set_rule(world.get_entrance('TR Pegs Teleporter', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.can_lift_heavy_rocks(player)) else: set_rule(world.get_entrance('Turtle Rock Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('TR Pegs Ledge Drop', player), lambda state: False) - set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.has('Hammer', player) and state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.has('Hammer', player) and state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Turtle Rock Tail Ledge Drop', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) if not world.is_tile_swapped(0x10, player): @@ -1048,14 +1102,14 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has_beaten_aga(player)) - set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has_beaten_aga(player)) + set_rule(world.get_entrance('Castle Gate Teleporter', player), lambda state: state.has_beaten_aga(player)) + set_rule(world.get_entrance('Castle Gate Teleporter (Inner)', player), lambda state: state.has_beaten_aga(player)) else: set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Pyramid Entrance', player), lambda state: False) - set_rule(world.get_entrance('Post Aga Inverted Teleporter', player), lambda state: state.has_beaten_aga(player)) + set_rule(world.get_entrance('Post Aga Teleporter', player), lambda state: state.has_beaten_aga(player)) if not world.is_tile_swapped(0x2f, player): set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer @@ -1066,13 +1120,13 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('Mirror To Bombos Tablet Ledge', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Desert Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) else: - set_rule(world.get_entrance('Misery Mire Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Mire Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Checkerboard Ledge Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) set_rule(world.get_entrance('Checkerboard Ledge Leave', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) if world.is_tile_swapped(0x32, player): - set_rule(world.get_entrance('Cave 45 Inverted Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) - set_rule(world.get_entrance('Cave 45 Inverted Leave', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) + set_rule(world.get_entrance('Cave 45 Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) + set_rule(world.get_entrance('Cave 45 Leave', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) if not world.is_tile_swapped(0x33, player): set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.can_lift_rocks(player)) @@ -1082,7 +1136,7 @@ def ow_inverted_rules(world, player): if not world.is_tile_swapped(0x35, player): set_rule(world.get_entrance('Lake Hylia Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) else: - set_rule(world.get_entrance('Ice Palace Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Ice Lake Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches']) if world.is_tile_swapped(0x3a, player): @@ -1091,13 +1145,15 @@ def ow_inverted_rules(world, player): def ow_bunny_rules(world, player): + # locations add_bunny_rule(world.get_location('Mushroom', player), player) + add_bunny_rule(world.get_location('Turtle Medallion Pad', player), player) add_bunny_rule(world.get_location('Zora\'s Ledge', player), player) add_bunny_rule(world.get_location('Maze Race', player), player) add_bunny_rule(world.get_location('Flute Spot', player), player) - add_bunny_rule(world.get_location('Turtle Medallion Pad', player), player) add_bunny_rule(world.get_location('Catfish', player), player) + # entrances add_bunny_rule(world.get_entrance('Lost Woods Hideout Drop', player), player) add_bunny_rule(world.get_entrance('Lumberjack Tree Tree', player), player) add_bunny_rule(world.get_entrance('Waterfall of Wishing', player), player) @@ -1108,41 +1164,52 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Hyrule Castle Secret Entrance Drop', player), player) add_bunny_rule(world.get_entrance('Bonk Fairy (Light)', player), player) add_bunny_rule(world.get_entrance('Checkerboard Cave', player), player) - add_bunny_rule(world.get_entrance('50 Rupee Cave', player), player) add_bunny_rule(world.get_entrance('20 Rupee Cave', player), player) + add_bunny_rule(world.get_entrance('50 Rupee Cave', player), player) + add_bunny_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), player) # bunny cannot lift bush add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player) # bunny cannot lift bush add_bunny_rule(world.get_entrance('Skull Woods Final Section', player), player) # bunny cannot use fire rod add_bunny_rule(world.get_entrance('Hookshot Cave', player), player) add_bunny_rule(world.get_entrance('Thieves Town', player), player) # bunny cannot pull - add_bunny_rule(world.get_entrance('Brewery', player), player) # bomb required add_bunny_rule(world.get_entrance('Palace of Darkness', player), player) # kiki needs pearl - add_bunny_rule(world.get_entrance('Dark World Hammer Peg Cave', player), player) + add_bunny_rule(world.get_entrance('Hammer Peg Cave', player), player) add_bunny_rule(world.get_entrance('Bonk Fairy (Dark)', player), player) add_bunny_rule(world.get_entrance('Misery Mire', player), player) - add_bunny_rule(world.get_entrance('Hype Cave', player), player) # bomb required - add_bunny_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), player) # bomb required add_bunny_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), player) + # terrain add_bunny_rule(world.get_entrance('Lost Woods Bush (West)', player), player) add_bunny_rule(world.get_entrance('Lost Woods Bush (East)', player), player) add_bunny_rule(world.get_entrance('DM Hammer Bridge (West)', player), player) add_bunny_rule(world.get_entrance('DM Hammer Bridge (East)', player), player) - add_bunny_rule(world.get_entrance('Fairy Ascension Rocks (North)', player), player) - add_bunny_rule(world.get_entrance('Fairy Ascension Rocks (South)', player), player) add_bunny_rule(world.get_entrance('DM Broken Bridge (West)', player), player) add_bunny_rule(world.get_entrance('DM Broken Bridge (East)', player), player) + add_bunny_rule(world.get_entrance('Fairy Ascension Rocks (Inner)', player), player) + add_bunny_rule(world.get_entrance('Fairy Ascension Rocks (Outer)', player), player) add_bunny_rule(world.get_entrance('TR Pegs Ledge Entry', player), player) - add_bunny_rule(world.get_entrance('Mountain Entry Entrance Rock (West)', player), player) - add_bunny_rule(world.get_entrance('Mountain Entry Entrance Rock (East)', player), player) + add_bunny_rule(world.get_entrance('TR Pegs Ledge Leave', player), player) + add_bunny_rule(world.get_entrance('Mountain Pass Rock (Outer)', player), player) + add_bunny_rule(world.get_entrance('Mountain Pass Rock (Inner)', player), player) + add_bunny_rule(world.get_entrance('Zora Waterfall Water Drop', player), player) + add_bunny_rule(world.get_entrance('Zora Waterfall Water Entry', player), player) + add_bunny_rule(world.get_entrance('Zora Waterfall Approach', player), player) add_bunny_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), player) add_bunny_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), player) add_bunny_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), player) add_bunny_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), player) - add_bunny_rule(world.get_entrance('Kings Grave Outer Rocks', player), player) - add_bunny_rule(world.get_entrance('Kings Grave Inner Rocks', player), player) - add_bunny_rule(world.get_entrance('Potion Shop Rock (South)', player), player) + add_bunny_rule(world.get_entrance('Kakariko Pond Whirlpool', player), player) + add_bunny_rule(world.get_entrance('Kings Grave Rocks (Outer)', player), player) + add_bunny_rule(world.get_entrance('Kings Grave Rocks (Inner)', player), player) + add_bunny_rule(world.get_entrance('Graveyard Ladder (Top)', player), player) + add_bunny_rule(world.get_entrance('Graveyard Ladder (Bottom)', player), player) + add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player) + add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player) + add_bunny_rule(world.get_entrance('Potion Shop Water Drop', player), player) + add_bunny_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), player) add_bunny_rule(world.get_entrance('Potion Shop Rock (North)', player), player) + add_bunny_rule(world.get_entrance('Potion Shop Rock (South)', player), player) + add_bunny_rule(world.get_entrance('Zora Approach Water Drop', player), player) add_bunny_rule(world.get_entrance('Zora Approach Rocks (West)', player), player) add_bunny_rule(world.get_entrance('Zora Approach Rocks (East)', player), player) add_bunny_rule(world.get_entrance('Kakariko Southwest Bush (North)', player), player) @@ -1153,29 +1220,46 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Hyrule Castle Southwest Bush (South)', player), player) add_bunny_rule(world.get_entrance('Hyrule Castle Courtyard Bush (North)', player), player) add_bunny_rule(world.get_entrance('Hyrule Castle Courtyard Bush (South)', player), player) - add_bunny_rule(world.get_entrance('Hyrule Castle Inner East Rock', player), player) - add_bunny_rule(world.get_entrance('Hyrule Castle Outer East Rock', player), player) + add_bunny_rule(world.get_entrance('Hyrule Castle East Rock (Inner)', player), player) + add_bunny_rule(world.get_entrance('Hyrule Castle East Rock (Outer)', player), player) add_bunny_rule(world.get_entrance('Wooden Bridge Bush (North)', player), player) add_bunny_rule(world.get_entrance('Wooden Bridge Bush (South)', player), player) - add_bunny_rule(world.get_entrance('Bat Cave Ledge Peg', player), player) - add_bunny_rule(world.get_entrance('Bat Cave Ledge Peg (East)', player), player) - add_bunny_rule(world.get_entrance('Desert Ledge Outer Rocks', player), player) - add_bunny_rule(world.get_entrance('Desert Ledge Inner Rocks', player), player) + add_bunny_rule(world.get_entrance('Wooden Bridge Water Drop', player), player) + add_bunny_rule(world.get_entrance('Wooden Bridge Northeast Water Drop', player), player) + add_bunny_rule(world.get_entrance('Blacksmith Ledge Peg (West)', player), player) + add_bunny_rule(world.get_entrance('Blacksmith Ledge Peg (East)', player), player) + add_bunny_rule(world.get_entrance('Maze Race Game', player), player) + add_bunny_rule(world.get_entrance('Desert Ledge Rocks (Outer)', player), player) + add_bunny_rule(world.get_entrance('Desert Ledge Rocks (Inner)', player), player) add_bunny_rule(world.get_entrance('Flute Boy Bush (North)', player), player) add_bunny_rule(world.get_entrance('Flute Boy Bush (South)', player), player) + add_bunny_rule(world.get_entrance('C Whirlpool Water Entry', player), player) add_bunny_rule(world.get_entrance('C Whirlpool Rock (Bottom)', player), player) add_bunny_rule(world.get_entrance('C Whirlpool Rock (Top)', player), player) - add_bunny_rule(world.get_entrance('C Whirlpool Pegs (Left)', player), player) - add_bunny_rule(world.get_entrance('C Whirlpool Pegs (Right)', player), player) + add_bunny_rule(world.get_entrance('C Whirlpool Pegs (Outer)', player), player) + add_bunny_rule(world.get_entrance('C Whirlpool Pegs (Inner)', player), player) + add_bunny_rule(world.get_entrance('Statues Water Entry', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia Water Drop', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia South Water Drop', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia Central Water Drop', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia Island Water Drop', player), player) + add_bunny_rule(world.get_entrance('Lake Hylia Water D Leave', player), player) + add_bunny_rule(world.get_entrance('Ice Cave Water Drop', player), player) add_bunny_rule(world.get_entrance('Desert Pass Rocks (North)', player), player) add_bunny_rule(world.get_entrance('Desert Pass Rocks (South)', player), player) - add_bunny_rule(world.get_entrance('Skull Woods Bush Rock (West)', player), player) - add_bunny_rule(world.get_entrance('Skull Woods Bush Rock (East)', player), player) + add_bunny_rule(world.get_entrance('Octoballoon Water Drop', player), player) + add_bunny_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), player) + + add_bunny_rule(world.get_entrance('Skull Woods Rock (West)', player), player) + add_bunny_rule(world.get_entrance('Skull Woods Rock (East)', player), player) add_bunny_rule(world.get_entrance('Skull Woods Forgotten Bush (West)', player), player) add_bunny_rule(world.get_entrance('Skull Woods Forgotten Bush (East)', player), player) add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player) add_bunny_rule(world.get_entrance('East Dark Death Mountain Bushes', player), player) - add_bunny_rule(world.get_entrance('Bumper Cave Entrance Rock', player), player) + add_bunny_rule(world.get_entrance('Bumper Cave Ledge Drop', player), player) + add_bunny_rule(world.get_entrance('Bumper Cave Rock (Outer)', player), player) + add_bunny_rule(world.get_entrance('Bumper Cave Rock (Inner)', player), player) add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (West)', player), player) add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (East)', player), player) add_bunny_rule(world.get_entrance('Skull Woods Pass Bush (North)', player), player) @@ -1184,15 +1268,23 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), player) add_bunny_rule(world.get_entrance('Dark Graveyard Bush (South)', player), player) add_bunny_rule(world.get_entrance('Dark Graveyard Bush (North)', player), player) + add_bunny_rule(world.get_entrance('Qirn Jump Water Drop', player), player) + add_bunny_rule(world.get_entrance('Qirn Jump East Water Drop', player), player) + add_bunny_rule(world.get_entrance('Dark Witch Water Drop', player), player) + add_bunny_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), player) add_bunny_rule(world.get_entrance('Dark Witch Rock (North)', player), player) add_bunny_rule(world.get_entrance('Dark Witch Rock (South)', player), player) + add_bunny_rule(world.get_entrance('Catfish Approach Water Drop', player), player) add_bunny_rule(world.get_entrance('Catfish Approach Rocks (West)', player), player) add_bunny_rule(world.get_entrance('Catfish Approach Rocks (East)', player), player) - add_bunny_rule(world.get_entrance('Village of Outcasts Pegs', player), player) - add_bunny_rule(world.get_entrance('Grassy Lawn Pegs', player), player) + add_bunny_rule(world.get_entrance('Bush Yard Pegs (Outer)', player), player) + add_bunny_rule(world.get_entrance('Bush Yard Pegs (Inner)', player), player) add_bunny_rule(world.get_entrance('Broken Bridge Hammer Rock (South)', player), player) add_bunny_rule(world.get_entrance('Broken Bridge Hammer Rock (North)', player), player) add_bunny_rule(world.get_entrance('Broken Bridge Hookshot Gap', player), player) + add_bunny_rule(world.get_entrance('Broken Bridge Water Drop', player), player) + add_bunny_rule(world.get_entrance('Broken Bridge Northeast Water Drop', player), player) + add_bunny_rule(world.get_entrance('Broken Bridge West Water Drop', player), player) add_bunny_rule(world.get_entrance('Peg Area Rocks (West)', player), player) add_bunny_rule(world.get_entrance('Peg Area Rocks (East)', player), player) add_bunny_rule(world.get_entrance('Dig Game To Ledge Drop', player), player) @@ -1202,54 +1294,35 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Archery Game Rock (South)', player), player) add_bunny_rule(world.get_entrance('Hammer Bridge Pegs (North)', player), player) add_bunny_rule(world.get_entrance('Hammer Bridge Pegs (South)', player), player) + add_bunny_rule(world.get_entrance('Hammer Bridge Water Drop', player), player) add_bunny_rule(world.get_entrance('Stumpy Approach Bush (North)', player), player) add_bunny_rule(world.get_entrance('Stumpy Approach Bush (South)', player), player) + add_bunny_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), player) add_bunny_rule(world.get_entrance('Dark C Whirlpool Rock (Bottom)', player), player) add_bunny_rule(world.get_entrance('Dark C Whirlpool Rock (Top)', player), player) - add_bunny_rule(world.get_entrance('Dark C Whirlpool Pegs (Left)', player), player) - add_bunny_rule(world.get_entrance('Dark C Whirlpool Pegs (Right)', player), player) - - add_bunny_rule(world.get_entrance('Zora Waterfall Water Drop', player), player) - add_bunny_rule(world.get_entrance('Zora Waterfall Water Entry', player), player) - add_bunny_rule(world.get_entrance('Zora Waterfall Water Approach', player), player) - add_bunny_rule(world.get_entrance('Kakariko Pond Whirlpool', player), player) - add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player) - add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player) - add_bunny_rule(world.get_entrance('Potion Shop Water Drop', player), player) - add_bunny_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Zora Approach Water Drop', player), player) - add_bunny_rule(world.get_entrance('Wooden Bridge Water Drop', player), player) - add_bunny_rule(world.get_entrance('Wooden Bridge Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('C Whirlpool Water Entry', player), player) - add_bunny_rule(world.get_entrance('Statues Water Entry', player), player) - add_bunny_rule(world.get_entrance('Lake Hylia Water Drop', player), player) - add_bunny_rule(world.get_entrance('Lake Hylia South Water Drop', player), player) - add_bunny_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Lake Hylia Central Water Drop', player), player) - add_bunny_rule(world.get_entrance('Lake Hylia Island Water Drop', player), player) - add_bunny_rule(world.get_entrance('Ice Cave SW', player), player) - add_bunny_rule(world.get_entrance('Octoballoon Water Drop', player), player) - add_bunny_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), player) - add_bunny_rule(world.get_entrance('Qirn Jump Water Drop', player), player) - add_bunny_rule(world.get_entrance('Qirn Jump East Water Drop', player), player) - add_bunny_rule(world.get_entrance('Dark Witch Water Drop', player), player) - add_bunny_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Catfish Approach Water Drop', player), player) - add_bunny_rule(world.get_entrance('Broken Bridge Water Drop', player), player) - add_bunny_rule(world.get_entrance('Broken Bridge Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Broken Bridge West Water Drop', player), player) - add_bunny_rule(world.get_entrance('Hammer Bridge Water Drop', player), player) - add_bunny_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), player) + add_bunny_rule(world.get_entrance('Dark C Whirlpool Pegs (Outer)', player), player) + add_bunny_rule(world.get_entrance('Dark C Whirlpool Pegs (Inner)', player), player) add_bunny_rule(world.get_entrance('Hype Cave Water Entry', player), player) add_bunny_rule(world.get_entrance('Ice Lake Water Drop', player), player) add_bunny_rule(world.get_entrance('Ice Lake Northeast Water Drop', player), player) add_bunny_rule(world.get_entrance('Ice Lake Southwest Water Drop', player), player) add_bunny_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Ice Lake Moat Water Entry', player), player) - add_bunny_rule(world.get_entrance('Shopping Mall SW', player), player) + add_bunny_rule(world.get_entrance('Ice Lake Iceberg Water Entry', player), player) + add_bunny_rule(world.get_entrance('Shopping Mall Water Drop', player), player) add_bunny_rule(world.get_entrance('Bomber Corner Water Drop', player), player) add_bunny_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), player) + # OWG rules + add_bunny_rule(world.get_entrance('Stone Bridge EC Cliff Water Drop', player), player) + add_bunny_rule(world.get_entrance('Hammer Bridge EC Cliff Water Drop', player), player) + + if not world.is_atgt_swapped(player): + add_bunny_rule(world.get_entrance('Agahnims Tower', player), player) + + #TODO: This needs to get applied after bunny rules, move somewhere else tho + if not world.is_atgt_swapped(player): + add_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_beaten_aga(player), 'or') # barrier gets removed after killing agahnim, relevant for entrance shuffle + def ow_terrain_rules(world, player): for edge in world.owedges: @@ -1267,20 +1340,38 @@ def ow_terrain_rules(world, player): def no_glitches_rules(world, player): - # todo: move some dungeon rules to no glictes logic - see these for examples + set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Zora Approach Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('C Whirlpool Water Entry', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Statues Water Entry', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Lake Hylia South Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Octoballoon Water Drop', player), lambda state: state.has('Flippers', player)) + + set_rule(world.get_entrance('Qirn Jump East Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Witch Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Catfish Approach Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: state.has('Flippers', player)) + + # todo: move some dungeon rules to no glicthes logic - see these for examples # add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) # add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player)) # DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'] # for location in DMs_room_chests: # add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override + set_rule(world.get_entrance('Ice Lake Northeast Pier Hop', player), lambda state: False) forbid_bomb_jump_requirements(world, player) if not world.is_copied_world: add_conditional_lamps(world, player) def fake_flipper_rules(world, player): - set_rule(world.get_entrance('Zora Waterfall Water Approach', player), lambda state: True) # warning, assumes FF possible on other end of whirlpool or local ancilla splash delete set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: True) set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: True) set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: True) @@ -1299,25 +1390,6 @@ def fake_flipper_rules(world, player): set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: True) set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: True) set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: True) - add_bunny_rule(world.get_entrance('Zora Waterfall Water Approach', player), player) - add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player) - add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player) - add_bunny_rule(world.get_entrance('Potion Shop Water Drop', player), player) - add_bunny_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Zora Approach Water Drop', player), player) - add_bunny_rule(world.get_entrance('C Whirlpool Water Entry', player), player) - add_bunny_rule(world.get_entrance('Statues Water Entry', player), player) - add_bunny_rule(world.get_entrance('Lake Hylia South Water Drop', player), player) - add_bunny_rule(world.get_entrance('Octoballoon Water Drop', player), player) - add_bunny_rule(world.get_entrance('Qirn Jump Water Drop', player), player) - add_bunny_rule(world.get_entrance('Qirn Jump East Water Drop', player), player) - add_bunny_rule(world.get_entrance('Dark Witch Water Drop', player), player) - add_bunny_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Catfish Approach Water Drop', player), player) - add_bunny_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), player) - add_bunny_rule(world.get_entrance('Hype Cave Water Entry', player), player) - add_bunny_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), player) - add_bunny_rule(world.get_entrance('Bomber Corner Water Drop', player), player) def forbid_bomb_jump_requirements(world, player): @@ -1325,6 +1397,7 @@ def forbid_bomb_jump_requirements(world, player): for location in DMs_room_chests: add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) + set_rule(world.get_entrance('Ice Lake Iceberg Bomb Jump', player), lambda state: False) def add_conditional_lamps(world, player): def add_conditional_lamp(spot, spottype='Location'): @@ -1381,9 +1454,9 @@ def add_conditional_lamps(world, player): is_dark = False if not world.sewer_light_cone[player]: is_dark = True - elif world.doorShuffle[player] != 'crossed' and not info['sewer']: + elif world.doorShuffle[player] not in ['crossed', 'partitioned'] and not info['sewer']: is_dark = True - elif world.doorShuffle[player] == 'crossed': + elif world.doorShuffle[player] in ['crossed', 'partitioned']: sewer_builder = world.dungeon_layouts[player]['Hyrule Castle'] is_dark = region not in sewer_builder.master_sector.region_set() if is_dark: @@ -1397,7 +1470,8 @@ def add_conditional_lamps(world, player): add_conditional_lamp('Old Man House Front to Back', 'Entrance') -def open_rules(world, player): + +def misc_key_rules(world, player): # softlock protection as you can reach the sewers small key door with a guard drop key set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player)) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player)) @@ -1407,20 +1481,21 @@ def swordless_rules(world, player): set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) + if world.get_entrance('Ice Lobby SE', player).door.trapped: + set_rule(world.get_entrance('Ice Lobby SE', player), + lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.can_hit_stunned_ganon(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)) set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop - - if not world.is_atgt_swapped(player): - set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has_beaten_aga(player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_location('Turtle Medallion Pad', player), lambda state: state.has_turtle_rock_medallion(player)) # sword not required to use medallion for opening in swordless (!) - add_bunny_rule(world.get_entrance('Misery Mire', player), player) - add_bunny_rule(world.get_location('Turtle Medallion Pad', player), player) + + if not world.is_atgt_swapped(player): + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player)) # barrier gets removed after killing agahnim, rule for that added later def bomb_mode_rules(world, player): set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) @@ -1432,7 +1507,6 @@ def bomb_mode_rules(world, player): if world.mode[player] != 'inverted': set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_special_weapon_level(player, 2) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle - set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion in bomb-only add_bunny_rule(world.get_entrance('Turtle Rock', player), player) add_bunny_rule(world.get_entrance('Misery Mire', player), player) @@ -1446,7 +1520,6 @@ def cane_mode_rules(world, player): if world.mode[player] != 'inverted': set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_special_weapon_level(player, 2) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle - set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion in bomb-only add_bunny_rule(world.get_entrance('Turtle Rock', player), player) add_bunny_rule(world.get_entrance('Misery Mire', player), player) @@ -1454,6 +1527,7 @@ def pseudo_sword_mode_rules(world, player): set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.can_hit_stunned_ganon(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)) set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop +# todo: new traps std_kill_rooms = { 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard @@ -1484,6 +1558,18 @@ std_kill_rooms = { 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work } # all trap rooms? +std_kill_doors_if_trapped = { + 'Hyrule Dungeon Armory Main': 'Hyrule Dungeon Armory Interior Key Door N', + # 'Eastern Single Eyegore ES', # arrow rule is sufficient + # 'Eastern Duo Eyegores S', # arrow rule is sufficient + 'TR Twin Pokeys': 'TR Twin Pokeys NW', + 'Thieves Basement Block': 'Thieves Blocked Entry SW', + 'Desert Compass Room': 'Desert Compass Key Door WN', + 'Mire Cross': 'Mire Cross SW', + 'Tower Circle of Pots': 'Tower Circle of Pots ES', + # 'Ice Lobby S' # can melt rule is sufficient +} + def add_connection(parent_name, target_name, entrance_name, world, player): parent = world.get_region(parent_name, player) target = world.get_region(target_name, player) @@ -1504,8 +1590,6 @@ def standard_rules(world, player): entrance = world.get_portal(portal_name, player).door.entrance set_rule(entrance, lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Sanctuary Exit', player), lambda state: state.has('Zelda Delivered', player)) - set_rule(world.get_entrance('Hyrule Castle Ledge Drop', player), lambda state: state.has('Zelda Delivered', player)) - set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has('Zelda Delivered', player)) # zelda should be saved before agahnim is in play add_rule(world.get_location('Agahnim 1', player), lambda state: state.has('Zelda Delivered', player)) @@ -1538,6 +1622,10 @@ def standard_rules(world, player): if region.name in std_kill_rooms: for ent in std_kill_rooms[region.name]: add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state)) + if region.name in std_kill_doors_if_trapped: + ent = world.get_entrance(std_kill_doors_if_trapped[region.name], player) + if ent.door.trapped: + add_rule(ent, lambda state: standard_escape_rule(state)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player)) @@ -1548,15 +1636,9 @@ def standard_rules(world, player): rule_list, debug_path = find_rules_for_zelda_delivery(world, player) set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) - add_rule(world.get_entrance('Links House SC', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Links House ES', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Central Bonk Rocks SW', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Hyrule Castle WN', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Hyrule Castle ES', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Hyrule Castle Ledge Drop', player), lambda state: state.has('Zelda Delivered', player)) - add_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has('Zelda Delivered', player)) + for entrance in ['Links House SC', 'Links House ES', 'Central Bonk Rocks SW', 'Hyrule Castle WN', 'Hyrule Castle ES', + 'Bonk Fairy (Light)', 'Hyrule Castle Main Gate (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Ledge Drop']: + add_rule(world.get_entrance(entrance, player), lambda state: state.has('Zelda Delivered', player)) if world.shuffle_bonk_drops[player]: if not world.is_copied_world: @@ -1566,6 +1648,7 @@ def standard_rules(world, player): # don't allow bombs to get past here before zelda is rescued set_rule(world.get_entrance('GT Hookshot South Entry to Ranged Crystal', player), lambda state: (state.can_use_bombs(player) and state.has('Zelda Delivered', player)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player)) # or state.has('Cane of Somaria', player)) + def find_rules_for_zelda_delivery(world, player): # path rules for backtracking start_region = world.get_region('Hyrule Dungeon Cellblock', player) @@ -1600,11 +1683,11 @@ def set_bunny_rules(world, player, inverted): bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', - 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', - 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Old Man', - 'Frog', 'Missing Smith', 'Dark Blacksmith Ruins', 'Purple Chest', 'Pyramid Crack', 'Big Bomb', - 'Spectacle Rock', 'Bombos Tablet', 'Ether Tablet', 'Blacksmith', 'Stumpy', - 'Master Sword Pedestal', 'Bottle Merchant', 'Sunken Treasure', 'Desert Ledge', + 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Old Man', + 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins', + 'Spectacle Rock', 'Bombos Tablet', 'Ether Tablet', 'Purple Chest', 'Blacksmith', + 'Missing Smith', 'Master Sword Pedestal', 'Bottle Merchant', 'Sunken Treasure', 'Desert Ledge', + 'Pyramid Crack', 'Big Bomb', 'Stumpy', 'Lost Old Man', 'Old Man Drop Off', 'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right', 'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right', @@ -1724,7 +1807,7 @@ def set_bunny_rules(world, player, inverted): for ext in region.exits: add_rule(ext, rule) - paradox_shop = world.get_region('Light World Death Mountain Shop', player) + paradox_shop = world.get_region('Paradox Shop', player) if is_bunny(paradox_shop): add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop)) @@ -1733,6 +1816,11 @@ def set_bunny_rules(world, player, inverted): if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region): add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region)) + for ent_name in bunny_impassible_if_trapped: + bunny_exit = world.get_entrance(ent_name, player) + if bunny_exit.door.trapped and is_bunny(bunny_exit.parent_region): + add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region)) + doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors] doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked] for door in doors_to_check: @@ -1864,12 +1952,25 @@ bunny_impassible_doors = { 'GT Validation Block Path' } +bunny_impassible_if_trapped = { + 'Hyrule Dungeon Armory Interior Key Door N', 'Eastern Pot Switch WN', 'Eastern Lobby NW', + 'Eastern Lobby NE', 'Desert Compass Key Door WN', 'Tower Circle of Pots ES', 'PoD Mimics 2 SW', + 'PoD Middle Cage S', 'Swamp Push Statue S', 'Skull 2 East Lobby WS', 'Skull Torch Room WS', + 'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Thieves Blocked Entry SW', 'Ice Bomb Jump NW', + 'Ice Tall Hint EN', 'Ice Switch Room ES', 'Ice Switch Room NE', 'Mire Cross SW', + 'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Twin Pokeys NW', 'TR Torches WN', 'GT Hope Room WN', + 'GT Speed Torch NE', 'GT Speed Torch WS', 'GT Torch Cross WN', 'GT Hidden Spikes SE', 'GT Conveyor Cross EN', + 'GT Speed Torch WN', 'Ice Lobby SE' +} + def add_key_logic_rules(world, player): key_logic = world.key_logic[player] eval_func = eval_small_key_door if world.key_logic_algorithm[player] == 'strict' and world.keyshuffle[player] == 'wild': - eval_func = eval_small_key_door_strict + eval_func = eval_small_key_door_strict + elif world.key_logic_algorithm[player] != 'default': + eval_func = eval_small_key_door_partial for d_name, d_logic in key_logic.items(): for door_name, rule in d_logic.door_rules.items(): door_entrance = world.get_entrance(door_name, player) @@ -1901,6 +2002,8 @@ def eval_small_key_door_main(state, door_name, dungeon, player): if state.is_door_open(door_name, player): return True key_logic = state.world.key_logic[player][dungeon] + if door_name not in key_logic.door_rules: + return False door_rule = key_logic.door_rules[door_name] door_openable = False for ruleType, number in door_rule.new_rules.items(): @@ -1923,6 +2026,36 @@ def eval_small_key_door_main(state, door_name, dungeon, player): return door_openable +def eval_small_key_door_partial_main(state, door_name, dungeon, player): + if state.is_door_open(door_name, player): + return True + key_logic = state.world.key_logic[player][dungeon] + if door_name not in key_logic.door_rules: + return False + door_rule = key_logic.door_rules[door_name] + door_openable = False + for ruleType, number in door_rule.new_rules.items(): + if door_openable: + return True + if ruleType == KeyRuleType.WorstCase: + number = min(number, door_rule.small_key_num) + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) + elif ruleType == KeyRuleType.AllowSmall: + small_loc_item = door_rule.small_location.item + if small_loc_item and small_loc_item.name == key_logic.small_key_name and small_loc_item.player == player: + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) + elif isinstance(ruleType, tuple): + lock, lock_item = ruleType + # this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't + for loc in door_rule.alternate_big_key_loc: + spot = state.world.get_location(loc, player) + if spot.item and spot.item.name == lock_item: + number = min(number, door_rule.alternate_small_key) + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) + break + return door_openable + + def eval_small_key_door_strict_main(state, door_name, dungeon, player): if state.is_door_open(door_name, player): return True @@ -1937,6 +2070,10 @@ def eval_small_key_door(door_name, dungeon, player): return lambda state: eval_small_key_door_main(state, door_name, dungeon, player) +def eval_small_key_door_partial(door_name, dungeon, player): + return lambda state: eval_small_key_door_partial_main(state, door_name, dungeon, player) + + def eval_small_key_door_strict(door_name, dungeon, player): return lambda state: eval_small_key_door_strict_main(state, door_name, dungeon, player) diff --git a/Tables.py b/Tables.py index a30f24f0..52f96488 100644 --- a/Tables.py +++ b/Tables.py @@ -130,6 +130,7 @@ bonk_prize_lookup = { 'Chicken': (0x0b, 0, None), 'Bee Trap': (0x79, 6, None), 'Apples': (0xac, 8, None), + 'Good Bee': (0xb2, 1, None), 'Small Heart': (0xd8, 2, None), 'Rupee (1)': (0xd9, 0, None), 'Rupees (5)': (0xda, 3, None), # TODO: add in murahdahla tree rupee diff --git a/TestSuite.py b/TestSuite.py index 355c1883..9c2f29d0 100644 --- a/TestSuite.py +++ b/TestSuite.py @@ -49,6 +49,9 @@ def main(args=None): test("Shopsanity", "--shuffle vanilla --shopsanity") test("Simple ", "--shuffle simple") test("Full ", "--shuffle full") + test("Lite ", "--shuffle lite") + test("Lean ", "--shuffle lean") + test("Swapped ", "--shuffle swapped") test("Crossed ", "--shuffle crossed") test("Insanity ", "--shuffle insanity") test("OWG ", "--logic owglitches") diff --git a/TestSuiteStat.py b/TestSuiteStat.py index 92d066c6..ecde323d 100644 --- a/TestSuiteStat.py +++ b/TestSuiteStat.py @@ -14,7 +14,7 @@ ALL_SETTINGS = { 'mode': ['open', 'standard', 'inverted'], 'goal': ['ganon', 'pedestal', 'triforcehunt', 'trinity', 'crystals', 'dungeons'], 'swords': ['random', 'swordless', 'assured'], - 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','crossed','insanity'], + 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','swapped','crossed','insanity'], 'shufflelinks': [True, False], 'shuffleganon': [True, False], 'door_shuffle': ['vanilla', 'basic', 'crossed'], @@ -39,7 +39,7 @@ SETTINGS = { 'goal': ['ganon'], 'swords': ['random'], 'shuffle': ['vanilla', - 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'crossed', 'insanity' + 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity' ], 'shufflelinks': [True, False], 'shuffleganon': [True, False], diff --git a/Text.py b/Text.py index 9fc7d86b..1dd54995 100644 --- a/Text.py +++ b/Text.py @@ -113,7 +113,7 @@ Triforce_texts = [ " 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!", @@ -1309,6 +1309,7 @@ class GoldCreditMapper(CharTextMapper): class GreenCreditMapper(CharTextMapper): char_map = {' ': 0x9F, + '.': 0x52, '·': 0x52} alpha_offset = -0x29 alpha_lower_offset = -0x29 @@ -2029,6 +2030,6 @@ class TextTable(object): text['ganon_phase_3_no_silvers_alt'] = CompressedTextMapper.convert("You can't best me without silver arrows!") text['ganon_phase_3_no_silvers'] = CompressedTextMapper.convert("You can't best me without silver arrows!") text['ganon_phase_3_silvers'] = CompressedTextMapper.convert("Oh no! Silver! My one true weakness!") - text['murahdahla'] = CompressedTextMapper.convert("Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n{PAUSE3}\n… … …\nWait! you can see me? I knew I should have\nhidden in a hollow tree.") + text['murahdahla'] = CompressedTextMapper.convert("Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n{PAUSE3}\n… … …\nWait! You can see me? I knew I should have\nhidden in a hollow tree.") text['end_pad_data'] = bytearray([0xfb]) text['terminator'] = bytearray([0xFF, 0xFF]) diff --git a/Utils.py b/Utils.py index a356bc4b..c3c3ba68 100644 --- a/Utils.py +++ b/Utils.py @@ -737,6 +737,13 @@ class bidict(dict): super(bidict, self).__delitem__(key) +class HexInt(int): pass + +def hex_representer(dumper, data): + import yaml + return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}") + + if __name__ == '__main__': print(make_new_base2current()) # read_entrance_data(old_rom=sys.argv[1]) diff --git a/asm/owrando.asm b/asm/owrando.asm index 09fe010d..d74718dd 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -11,9 +11,11 @@ OWMode: dw 0 OWFlags: dw 0 -org $aa8010 OWReserved: dw 0 +org $aa8010 +OWVersionInfo: +dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000 ;Hooks org $02a929 @@ -45,6 +47,10 @@ Overworld_LoadSpecialOverworld_RoomId: org $04E8B4 Overworld_LoadSpecialOverworld: +org $02A9DA +JSL OWSkipPalettes +BCC OverworldHandleTransitions_change_palettes : NOP #4 + org $07982A Link_ResetSwimmingState: @@ -157,6 +163,14 @@ and #$7f : eor #$40 : nop #2 org $06AD4C jsl.l OWBonkDrops : nop #4 +org $1EDE6F +jsl.l OWBonkGoodBeeDrop : bra + +GoldBee_SpawnSelf_SetProperties: +phb : lda.b #$1E : pha : plb ; switch to bank 1E + jsr GoldBee_SpawnSelf+12 +plb : rtl +nop #3 ++ ;Code org $aa8800 @@ -230,6 +244,19 @@ OWWhirlpoolEnd: RTL } +OWDestroyItemSprites: +{ + PHX : LDX.b #$0F + .nextSprite + LDA.w $0E20,X + CMP.b #$D8 : BCC .continue + CMP.b #$EC : BCS .continue + .killSprite ; need to kill sprites from D8 to EB on screen transition + STZ.w $0DD0,X + .continue + DEX : BPL .nextSprite + PLX : RTL +} OWMirrorSpriteOnMap: { lda.w $1ac0,x : bit.b #$f0 : beq .continue @@ -395,13 +422,127 @@ LoadMapDarkOrMixed: dw $0400+$0210 ; bottom right } +OWBonkGoodBeeDrop: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled + .vanilla ; what we wrote over + STZ.w $0DD0,X + LDA.l BottleContentsOne : ORA.l BottleContentsTwo + ORA.l BottleContentsThree : ORA.l BottleContentsFour + RTL + .shuffled + LDA.w $0DD0,X : BNE + + JMP .return+1 + + PHY : TXY + LDA.l RoomDataWRAM[$0120].high : AND.b #$02 : PHA : BNE + ; check if collected + LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx + + + LDA.l OWBonkPrizeTable[42].mw_player : BEQ + ; multiworld item + LDA.l OWBonkPrizeTable[42].loot + JMP .spawn_item + + + + .determine_type ; S = Collected + LDA.l OWBonkPrizeTable[42].loot ; A = item id + CMP.b #$B0 : BNE + + LDA.b #$79 : JMP .sprite_transform ; transform to bees + + CMP.b #$42 : BNE + + JSL.l Sprite_TransmuteToBomb ; transform a heart to bomb, vanilla behavior + JMP .mark_collected + + CMP.b #$34 : BNE + + LDA.b #$D9 : JMP .sprite_transform ; transform to single rupee + + CMP.b #$35 : BNE + + LDA.b #$DA : JMP .sprite_transform ; transform to blue rupee + + CMP.b #$36 : BNE + + LDA.b #$DB : BRA .sprite_transform ; transform to red rupee + + CMP.b #$27 : BNE + + LDA.b #$DC : BRA .sprite_transform ; transform to 1 bomb + + CMP.b #$28 : BNE + + LDA.b #$DD : BRA .sprite_transform ; transform to 4 bombs + + CMP.b #$31 : BNE + + LDA.b #$DE : BRA .sprite_transform ; transform to 8 bombs + + CMP.b #$45 : BNE + + LDA.b #$DF : BRA .sprite_transform ; transform to small magic + + CMP.b #$B4 : BNE + + LDA.b #$E0 : BRA .sprite_transform ; transform to big magic + + CMP.b #$B5 : BNE + + LDA.b #$79 : JSL.l OWBonkSpritePrep + JSL.l GoldBee_SpawnSelf_SetProperties ; transform to good bee + BRA .mark_collected + + CMP.b #$44 : BNE + + LDA.b #$E2 : BRA .sprite_transform ; transform to 10 arrows + + CMP.b #$B1 : BNE + + LDA.b #$AC : BRA .sprite_transform ; transform to apples + + CMP.b #$B2 : BNE + + LDA.b #$E3 : BRA .sprite_transform ; transform to fairy + + CMP.b #$B3 : BNE .spawn_item + INX : INX : LDA.l OWBonkPrizeTable[42].vert_offset + CLC : ADC.b #$08 : PHA + LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y + LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX + LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken + + .sprite_transform + JSL.l OWBonkSpritePrep + + .mark_collected ; S = Collected + PLA : BNE + + LDA.l RoomDataWRAM[$0120].high : ORA.b #$02 : STA.l RoomDataWRAM[$0120].high + + REP #$20 + LDA.l TotalItemCounter : INC : STA.l TotalItemCounter + SEP #$20 + + BRA .return + + ; spawn itemget item + .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected + PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return + + PHA + + LDA.b #$01 : STA !FORCE_HEART_SPAWN + + LDA.b #$EB : STA.l $7FFE00 + JSL Sprite_SpawnDynamically+15 ; +15 to skip finding a new slot, use existing sprite + + LDA.b #$01 : STA.w !SPRITE_REDRAW,Y + + PLA : STA.w $0E80,Y + + ; affects the rate the item moves in the Y/X direction + LDA.b #$00 : STA.w $0D40,Y + LDA.b #$0A : STA.w $0D50,Y + + LDA.b #$1A : STA.w $0F80,Y ; amount of force (gives height to the arch) + LDA.b #$FF : STA.w $0B58,Y ; stun timer + LDA.b #$30 : STA.w $0F10,Y ; aux delay timer 4 ?? dunno what that means + + LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on + + ; sets the tile type that is underneath the sprite, water + TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this + + ; sets OW event bitmask flag, uses free RAM + LDA.l OWBonkPrizeTable[42].flag : STA.w $0ED0,Y + + ; determines the initial spawn point of item + LDA.w $0D00,Y : SEC : SBC.l OWBonkPrizeTable[42].vert_offset : STA.w $0D00,Y + LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y + + .return + PLY + LDA #$08 ; makes original good bee not spawn + RTL +} + ; Y = sprite slot index of bonk sprite OWBonkDrops: { CMP.b #$D8 : BEQ + RTL - + LDA.l OWFlags+1 : AND.b #!FLAG_OW_CROSSED : BNE + + + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE + JSL.l Sprite_TransmuteToBomb : RTL + + LDA.w $0DD0,Y : BNE + + RTL + ; loop thru rando bonk table to find match @@ -439,7 +580,7 @@ OWBonkDrops: + CMP.b #$34 : BNE + LDA.b #$D9 : CLC : JMP .sprite_transform ; transform to single rupee + CMP.b #$35 : BNE + - LDA.b #$DA : CLC : BRA .sprite_transform ; transform to blue rupee + LDA.b #$DA : CLC : JMP .sprite_transform ; transform to blue rupee + CMP.b #$36 : BNE + LDA.b #$DB : CLC : BRA .sprite_transform ; transform to red rupee + CMP.b #$27 : BNE + @@ -453,7 +594,9 @@ OWBonkDrops: + CMP.b #$B4 : BNE + LDA.b #$E0 : CLC : BRA .sprite_transform ; transform to big magic + CMP.b #$B5 : BNE + - LDA.b #$E1 : CLC : BRA .sprite_transform ; transform to 5 arrows + LDA.b #$79 : JSL.l OWBonkSpritePrep + JSL.l GoldBee_SpawnSelf_SetProperties ; transform to good bee + BRA .mark_collected + CMP.b #$44 : BNE + LDA.b #$E2 : CLC : BRA .sprite_transform ; transform to 10 arrows + CMP.b #$B1 : BNE + @@ -468,42 +611,37 @@ OWBonkDrops: LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken .sprite_transform - STA.w $0E20,Y - TYX : JSL.l Sprite_LoadProperties - BEQ + - ; these are sprite properties that make it fall out of the tree to the east - LDA #$30 : STA $0F80,Y ; amount of force (related to speed) - LDA #$10 : STA $0D50,Y ; eastward rate of speed - LDA #$FF : STA $0B58,Y ; expiration timer - + + JSL.l OWBonkSpritePrep .mark_collected ; S = Collected, FlagBitmask, X (row + 2) PLA : BNE + ; S = FlagBitmask, X (row + 2) + TYX : JSL Sprite_IsOnscreen : BCC + LDX.b $8A : LDA.l OverworldEventDataWRAM,X : ORA 1,S : STA.l OverworldEventDataWRAM,X REP #$20 - LDA.l TotalItemCounter : INC : STA.l TotalItemCounter + LDA.l TotalItemCounter : INC : STA.l TotalItemCounter SEP #$20 + JMP .return ; spawn itemget item .spawn_item ; A = item id ; Y = tree sprite slot ; S = Collected, FlagBitmask, X (row + 2) PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : JMP .return ; S = FlagBitmask, X (row + 2) - + LDA 2,S : TAX : INX : INX - LDA.w OWBonkPrizeData,X : STA.l !MULTIWORLD_SPRITEITEM_PLAYER_ID - DEX + + PHA - LDA.b #$01 : STA !REDRAW + LDA.b #$01 : STA !FORCE_HEART_SPAWN - LDA.b #$EB - STA.l $7FFE00 + LDA.b #$EB : STA.l $7FFE00 JSL Sprite_SpawnDynamically+15 ; +15 to skip finding a new slot, use existing sprite + LDA.b #$01 : STA.w !SPRITE_REDRAW,Y + + PLA : STA.w $0E80,Y + ; affects the rate the item moves in the Y/X direction LDA.b #$00 : STA.w $0D40,Y LDA.b #$0A : STA.w $0D50,Y - LDA.b #$20 : STA.w $0F80,Y ; amount of force (gives height to the arch) + LDA.b #$1A : STA.w $0F80,Y ; amount of force (gives height to the arch) LDA.b #$FF : STA.w $0B58,Y ; stun timer LDA.b #$30 : STA.w $0F10,Y ; aux delay timer 4 ?? dunno what that means @@ -511,23 +649,35 @@ OWBonkDrops: ; sets OW event bitmask flag, uses free RAM PLA : STA.w $0ED0,Y ; S = X (row + 2) - + ; determines the initial spawn point of item PLX : INX : INX : INX LDA.w $0D00,Y : SEC : SBC.w OWBonkPrizeData,X : STA.w $0D00,Y LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - LDA.b #$01 : STA !REDRAW : STA !FORCE_HEART_SPAWN - PLB : RTL .return PLA : PLA : PLB : RTL } +; A = SpriteID, Y = Sprite Slot Index, X = free/overwritten +OWBonkSpritePrep: +{ + STA.w $0E20,Y + TYX : JSL.l Sprite_LoadProperties + BEQ + + ; these are sprite properties that make it fall out of the tree to the east + LDA #$30 : STA $0F80,Y ; amount of force (related to speed) + LDA #$10 : STA $0D50,Y ; eastward rate of speed + LDA #$FF : STA $0B58,Y ; expiration timer + + RTL +} + org $aa9000 OWDetectEdgeTransition: { + JSL OWDestroyItemSprites STZ.w $06FC LDA.l OWMode : ORA.l OWMode+1 : BEQ .vanilla JSR OWShuffle @@ -721,31 +871,33 @@ OWNewDestination: { tya : sta $4202 : lda #16 : sta $4203 ;wait 8 cycles rep #$20 : txa : nop : !add $4216 : tax ;a = offset to dest record - lda.w $0006,x : sta $06 ;set coord lda.w $0008,x : sta $04 ;save dest OW slot/ID - lda.w $000a,x : sta $84 ;VRAM - + ldy $20 : lda $418 : dec #2 : bpl + : ldy $22 : + sty $06 + ;;22 e0 e2 61c 61e - X ;;20 e6 e8 618 61a - Y ;keep current position if within incoming gap lda.w $0000,x : and #$01ff : pha : lda.w $0002,x : and #$01ff : pha - ldy $20 : lda $418 : dec #2 : bpl + : ldy $22 - + tya : and #$01ff : cmp 3,s : !blt .adjustMainAxis - dec : cmp 1,s : !bge .adjustMainAxis - inc : pha : lda $06 : and #$fe00 : !add 1,s : sta $06 : pla + LDA.l OWMode : AND.w #$0007 : BEQ .noLayoutShuffle ;temporary fix until VRAM issues are solved + lda.w $0006,x : sta $06 ;set coord + lda.w $000a,x : sta $84 ;VRAM + tya : and #$01ff : cmp 3,s : !blt .adjustMainAxis + dec : cmp 1,s : !bge .adjustMainAxis + inc : pha : lda $06 : and #$fe00 : !add 1,s : sta $06 : pla - ; adjust and set other VRAM addresses - lda.w $0006,x : pha : lda $06 : !sub 1,s - jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : pha ; number of tiles - lda $418 : dec #2 : bmi + - pla : pea $0000 : bra ++ ;pla : asl #7 : pha : bra ++ ; y-axis shifts VRAM by increments of 0x80 (disabled for now) - + pla : asl : pha ; x-axis shifts VRAM by increments of 0x02 - ++ lda $84 : !add 1,s : sta $84 : pla : pla + ; adjust and set other VRAM addresses + lda.w $0006,x : pha : lda $06 : !sub 1,s + jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : jsl DivideByTwoPreserveSign : pha ; number of tiles + lda $418 : dec #2 : bmi + + pla : pea $0000 : bra ++ ;pla : asl #7 : pha : bra ++ ; y-axis shifts VRAM by increments of 0x80 (disabled for now) + + pla : asl : pha ; x-axis shifts VRAM by increments of 0x02 + ++ lda $84 : !add 1,s : sta $84 : pla : pla - .adjustMainAxis - LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram - LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 + .adjustMainAxis + LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram + LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 + .noLayoutShuffle LDA.w $000F,X : AND.w #$00FF : STA.w $06FC ; position to walk to after transition (if non-zero) LDY.w #$0000 @@ -805,8 +957,12 @@ OWNewDestination: ; crossed OW shuffle and terrain ldx $05 : ldy $08 : jsr OWWorldTerrainUpdate + + ldx $8a : lda $05 : sta $8a : stx $05 ; $05 is prev screen id, $8a is dest screen - lda $05 : sta $8a + jsr OWGfxUpdate + + lda $8a rep #$30 : rts } OWLoadSpecialArea: @@ -846,17 +1002,17 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, lda #$38 : sta $012f ; play sfx - #$3b is an alternative ; toggle bunny mode - lda MoonPearlEquipment : bne .nobunny - lda.l InvertedMode : bne .inverted + lda MoonPearlEquipment : beq + : jmp .nobunny + + lda.l InvertedMode : bne .inverted lda CurrentWorld : bra + .inverted lda CurrentWorld : eor #$40 + and #$40 : beq .nobunny - LDA.w $0703 : BEQ + ; check if forced transition - CPY.b #$03 : BEQ .end_forced_whirlpool - LDA.b #$17 : STA.b $5D - LDA.b #$01 : STA.w $02E0 : STA.b $56 - LDA.w $0703 : BRA .end_forced_edge + CPY.b #$03 : BEQ ++ + LDA.b #$17 : STA.b $5D + LDA.b #$01 : STA.w $02E0 : STA.b $56 + LDA.w $0703 : JSR OWLoadGearPalettes : BRA .end_forced_edge + ++ JSR OWLoadGearPalettes : BRA .end_forced_whirlpool + CPY.b #$01 : BEQ .auto ; check if going from land to water CPY.b #$02 : BEQ .to_bunny_reset_swim ; bunny state if swimming to land @@ -875,8 +1031,8 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, STZ.b $5D PLX BRA .to_pseudo_bunny - .whirlpool - PLX : RTS + .whirlpool + PLX : JMP OWLoadGearPalettes .to_bunny_reset_swim LDA.b $5D : CMP.b #$04 : BNE .to_bunny ; check if swimming JSL Link_ResetSwimmingState @@ -885,7 +1041,7 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, LDA.b #$17 : STA.b $5D .to_pseudo_bunny LDA.b #$01 : STA.w $02E0 : STA.b $56 - RTS + JMP OWLoadGearPalettes .nobunny lda $5d : cmp #$17 : bne + ; retain current state unless bunny @@ -934,6 +1090,68 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, .return RTS } +OWGfxUpdate: +{ + REP #$20 : LDA.l OWMode : AND.w #$0207 : BEQ .is_only_mixed : SEP #$20 + ;;;;PLA : AND.b #$3F : BEQ .leaving_woods + LDA.b $8A : AND.b #$3F : BEQ .entering_woods + ;LDA.b $05 : JSL OWSkipPalettes : BCS .skip_palettes + LDA.b $8A : JSR OWDetermineScreensPaletteSet + CPX.w $0AB3 : BEQ .skip_palettes ; check if next screen's palette is different + LDA $00 : PHA + JSL OverworldLoadScreensPaletteSet_long ; loading correct OW palette + PLA : STA $00 + .leaving_woods + .entering_woods + .is_only_mixed + .skip_palettes + SEP #$20 +} +OWLoadGearPalettes: +{ + PHX : PHY : LDA $00 : PHA + LDA.w $02E0 : BEQ + + JSL LoadGearPalettes_bunny + BRA .return + + + JSL LoadGearPalettes_link + .return + PLA : STA $00 : PLY : PLX + RTS +} +OWDetermineScreensPaletteSet: ; A = OWID to check +{ + LDX.b #$02 + PHA : AND.b #$3F + CMP.b #$03 : BEQ .death_mountain + CMP.b #$05 : BEQ .death_mountain + CMP.b #$07 : BEQ .death_mountain + LDX.b #$00 + .death_mountain + PLA : PHX : TAX : LDA.l OWTileWorldAssoc,X : BEQ + + PLX : INX : RTS + + PLX : RTS +} +OWSkipPalettes: +{ + STA.b $05 ; A = previous screen, also stored in $05 + ; only skip mosaic if OWR Layout or Crossed + PHP : REP #$20 : LDA.l OWMode : AND.w #$0207 : BEQ .vanilla : PLP + ; checks to see if going to from any DM screens + ;LDA.b $05 : JSR OWDetermineScreensPaletteSet : TXA : AND.b #$FE : STA $04 + ;LDA.b $8A : JSR OWDetermineScreensPaletteSet : TXA : AND.b #$FE + ;CMP.b $04 : BNE .skip_palettes + BRA .vanilla+1 + + .vanilla + PLP + LDA.b $05 : AND.b #$3F : BEQ .skip_palettes ; what we + LDA.b $8A : AND.b #$BF : BNE .change_palettes ; wrote over, kinda + .skip_palettes + SEC : RTL ; mosaic transition occurs + .change_palettes + CLC : RTL +} OWAdjustExitPosition: { LDA.w $06FC : CMP.b #$60 : BEQ .stone_bridge @@ -1287,7 +1505,7 @@ dw $0c78, $0ce3, $006b, $0cad, $3434, $0000, $0000, $001b dw $0ce4, $0d33, $004f, $0d0b, $3434, $0000, $0001, $001c dw $0d34, $0db8, $0084, $0d76, $3434, $0000, $0000, $001d dw $0ea8, $0f20, $0078, $0ee4, $3a3a, $0000, $0000, $001e -dw $0f70, $0fa8, $0038, $0f8c, $3a3a, $0000, $0000, $001f +dw $0f78, $0fa8, $0030, $0f90, $3a3a, $0000, $0000, $001f dw $0f18, $0f18, $0000, $0f18, $3b3b, $0000, $0000, $0020 dw $0fc8, $0fc8, $0000, $0fc8, $3b3b, $0000, $0000, $0021 dw $0e28, $0fb8, $0190, $0ef0, $3c3c, $0000, $0000, $0022 @@ -1362,7 +1580,7 @@ dw $0c78, $0ce3, $006b, $0cad, $3333, $0000, $0000, $001c dw $0ce4, $0d33, $004f, $0d0b, $3333, $0000, $0001, $001d dw $0d34, $0db8, $0084, $0d76, $3333, $0000, $0000, $001e dw $0ea8, $0f20, $0078, $0ee4, $3039, $0000, $0000, $001f -dw $0f70, $0fa8, $0038, $0f8c, $3039, $0000, $0000, $0020 +dw $0f78, $0fa8, $0030, $0f90, $3039, $0000, $0000, $0020 dw $0f18, $0f18, $0000, $0f18, $3a3a, $0000, $0000, $0021 dw $0fc8, $0fc8, $0000, $0fc8, $3a3a, $0000, $0000, $0022 dw $0e28, $0fb8, $0190, $0ef0, $3b3b, $0000, $0000, $0023 @@ -1539,6 +1757,7 @@ db $6e, $8c, $10, $35, $00, $10 db $6e, $90, $08, $b0, $00, $10 db $6e, $a4, $04, $b1, $00, $10 db $74, $4e, $10, $b1, $00, $1c +db $ff, $00, $02, $b5, $00, $08 ; temporary fix - murahdahla replaces one of the bonk tree prizes ; so we copy the sprite table here and update the pointer diff --git a/data/base2current.bps b/data/base2current.bps index 939c8f43..710a3e9f 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/docs/Customizer.md b/docs/Customizer.md index b95081bd..68687c23 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -86,7 +86,38 @@ 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. - + +### ow-tileflips + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined_chance`. + +#### force_flip / force_no_flip + +`force_flip` and `force_no_flip` should be used for tiles you want to flip or not flip. These sections are optional but must contain a list of OW Screen IDs. It is common to reference OW Screen IDs in hexadecimal (altho decimal is okay to use, if preferred), which range from: + 0x00 to 0x3f - Light World + 0x40 to 0x7f - Dark World + 0x80 - Pedestal/Hobo + 0x81 - Zoras Domain + +Here is an example which forces Links House and Sanctuary screens to stay in their original worlds. Note: It is unnecessary to supply both worlds' IDs. Links House is 0x2c and Big Bomb Shop is 0x6c. +``` +force_no_flip: + - 0x2c + - 0x13 +``` + +#### undefined_chance + +`undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped. + +### ow-flutespots + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have some form of Flute Shuffle in order for any values here to take effect. This section has two subsections: `force` and `forbid`. Both are lists of OW Screen IDs, please refer to ow-tileflips above for more information. + +Everything listed in `force` means that this screen must contain a flute spot. + +Everything listed in `forbid` means that this screen must not contain a flute spot. + ### entrances This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `entrances`, `exits`, and `two-way`. @@ -111,10 +142,6 @@ This must be defined by player. Each player number should be listed with the app `Chicken House: Kakariko Shop` if you walk into Chicken House door, you will in the Kakariko Shop. -##### Known Issues - -Chris Houlihan and Links House should be specified together or not at all. - ### doors This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `lobbies` and `doors`. diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index 3d0c7624..ecf209b1 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -1,8 +1,9 @@ meta: algorithm: balanced players: 1 - seed: 42 + seed: 41 # note to self: seed 42 had an interesting Swamp Palace problem names: Lonk + notes: "Some notes specified by the user" settings: 1: door_shuffle: basic @@ -17,6 +18,13 @@ settings: shopsanity: true shuffle: crossed shufflelinks: true + ow_shuffle: parallel + ow_terrain: true + ow_crossed: grouped + ow_keepsimilar: true + ow_mixed: true + ow_whirlpool: true + ow_fluteshuffle: balanced shufflebosses: unique item_pool: 1: @@ -61,6 +69,27 @@ placements: Palace of Darkness - Big Chest: Hammer Capacity Upgrade - Left: Moon Pearl Turtle Rock - Pokey 2 Key Drop: Ice Rod +ow-tileflips: + 1: + force_flip: + - 0x1b + force_no_flip: + - 0x2c + - 0x18 + undefined_chance: 50 +ow-flutespots: + 1: + force: + - 0x00 + - 0x12 + - 0x18 + - 0x1a + - 0x2f + - 0x30 + - 0x35 + forbid: + - 0x03 + - 0x05 entrances: 1: entrances: diff --git a/docs/presets/Swapkeys.yaml b/docs/presets/Swapkeys.yaml new file mode 100644 index 00000000..20b7e88b --- /dev/null +++ b/docs/presets/Swapkeys.yaml @@ -0,0 +1,22 @@ +meta: + branch: OWR + seed_name: Swapkeys + seed_notes: Crosskeys but Swapped ER +settings: + 1: + mode: open + logic: noglitches + goal: crystals + crystals_gt: "7" + crystals_ganon: "7" + accessibility: locations + mapshuffle: 1 + compassshuffle: 1 + keyshuffle: wild + bigkeyshuffle: 1 + shuffle: swapped + shuffleganon: 1 + shufflelinks: 0 + shuffletavern: 1 + experimental: 0 + hints: 0 diff --git a/docs/presets/swapkeys.yml b/docs/presets/swapkeys.yml new file mode 100644 index 00000000..e71e4521 --- /dev/null +++ b/docs/presets/swapkeys.yml @@ -0,0 +1,17 @@ +settings: + 1: + description: Swapkeys + glitches_required: none + mode: open + goal: crystals + crystals_gt: "7" + crystals_ganon: "7" + weapons: randomized + accessibility: locations + entrance_shuffle: swapped + shufflelinks: off + shuffletavern: on + mapshuffle: on + compassshuffle: on + keyshuffle: wild + bigkeyshuffle: on diff --git a/docs/standardinverted.yaml b/docs/standardinverted.yaml new file mode 100644 index 00000000..8195851a --- /dev/null +++ b/docs/standardinverted.yaml @@ -0,0 +1,18 @@ +meta: + players: 1 +settings: + 1: + mode: standard + ow_mixed: true +entrances: + 1: + entrances: + Sanctuary Grave: Sewer Drop + two-way: + Sanctuary: Sanctuary Exit +ow-tileflips: + 1: + force_no_flip: + - 0x13 + undefined_chance: 100 + diff --git a/docs/vanilla_multi_lobbies.yaml b/docs/vanilla_multi_lobbies.yaml new file mode 100644 index 00000000..06eaf835 --- /dev/null +++ b/docs/vanilla_multi_lobbies.yaml @@ -0,0 +1,28 @@ +doors: + 1: + lobbies: + #Agahnims Tower: Tower Lobby S + Desert Back: Desert Back Lobby S + Desert East: Desert East Lobby S + Desert South: Desert Main Lobby S + Desert West: Desert West S + #Eastern: Eastern Lobby S + #Ganons Tower: GT Lobby S + #Hera: Hera Lobby S + Hyrule Castle East: Hyrule Castle East Lobby S + Hyrule Castle South: Hyrule Castle Lobby S + Hyrule Castle West: Hyrule Castle West Lobby S + #Ice: Ice Lobby SE + #Mire: Mire Lobby S + #Palace of Darkness: PoD Lobby S + #Sanctuary: Sanctuary S + #Skull 1: Skull 1 Lobby S + #Skull 2 East: Skull 2 East Lobby SW + #Skull 2 West: Skull 2 West Lobby S + #Skull 3: Skull 3 Lobby SW + #Swamp: Swamp Lobby S + #Thieves Town: Thieves Lobby S + Turtle Rock Chest: TR Big Chest Entrance SE + Turtle Rock Eye Bridge: TR Eye Bridge SW + Turtle Rock Lazy Eyes: TR Lazy Eyes SE + Turtle Rock Main: TR Main Lobby SE diff --git a/mystery_example.yml b/mystery_example.yml index ff22b9b6..f37bc7c3 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -52,6 +52,9 @@ partial: 0 strict: 0 decoupledoors: off + door_self_loops: + on: 1 + off: 1 dropshuffle: on: 1 off: 1 @@ -91,6 +94,7 @@ full: 2 lite: 2 lean: 2 + swapped: 2 crossed: 3 insanity: 1 open_pyramid: @@ -133,6 +137,7 @@ triforce_pool_min: 20 triforce_pool_max: 40 triforce_min_difference: 10 + triforce_max_difference: 15 algorithm: balanced: 12 vanilla_fill: 1 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index f919b7cc..d2355f4f 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -31,6 +31,9 @@ key_logic_algorithm: decoupledoors: off: 9 # more strict on: 1 +door_self_loops: + on: 1 + off: 1 dropshuffle: on: 1 off: 1 @@ -56,6 +59,9 @@ entrance_shuffle: simple: 1 restricted: 1 full: 1 + lite: 1 + lean: 1 + swapped: 1 crossed: 1 insanity: 1 shufflelinks: @@ -83,6 +89,7 @@ triforce_goal_max: 30 triforce_pool_min: 30 triforce_pool_max: 40 triforce_min_difference: 10 +triforce_max_difference: 12 map_shuffle: on: 1 off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 177615e3..7c338824 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -218,6 +218,7 @@ "full", "lite", "lean", + "swapped", "crossed", "insanity", "dungeonsfull", @@ -267,6 +268,10 @@ "action": "store_true", "type": "bool" }, + "door_self_loops": { + "action": "store_true", + "type": "bool" + }, "experimental": { "action": "store_true", "type": "bool" @@ -430,6 +435,7 @@ "triforce_goal_min": {}, "triforce_goal_max": {}, "triforce_min_difference": {}, + "triforce_max_difference": {}, "custom": { "type": "bool", "help": "suppress" @@ -603,9 +609,6 @@ ] }, "outputname": {}, - "code": {}, - "trolls": { - "action": "store_true", - "type": "bool" - } + "notes": {}, + "code": {} } diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index c92476ef..20dce34c 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -1,448 +1,438 @@ { -"cli": { - "yes": "Yes", - "no": "No", - "app.title": "ALttP Door Randomizer Version %s : --seed %s --code %s", - "version": "Version", - "seed": "Seed", - "player": "Player", - "shuffling.overworld": "Shuffling overworld", - "shuffling.world": "Shuffling entrances", - "shuffling.prep": "Dungeon and Item prep", - "shuffling.dungeons": "Shuffling dungeons", - "shuffling.pots": "Shuffling pots", - "basic.traversal": "--Basic Traversal", - "generating.dungeon": "Generating dungeons", - "shuffling.keydoors": "Shuffling Key doors", - "lowering.keys.candidates": "Lowering key door count because not enough candidates", - "lowering.keys.layouts": "Lowering key door count because no valid layouts", - "keydoor.shuffle.time": "Key door shuffle time", - "keydoor.shuffle.time.crossed": "Cross Dungeon: Key door shuffle time", - "generating.itempool": "Generating Item Pool", - "calc.access.rules": "Calculating Access Rules", - "placing.dungeon.prizes": "Placing Dungeon Prizes", - "placing.dungeon.items": "Placing Dungeon Items", - "keylock.detected": "Keylock detected", - "fill.world": "Fill the world", - "balance.doors": "-Balancing Doors", - "re-balancing": "-Re-balancing", - "balancing": "--Balancing", - "splitting.up": "Splitting Up", - "balance.multiworld": "Balancing multiworld progression", - "cannot.beat.game": "Cannot beat game! Something went terribly wrong here!", - "cannot.reach.items": "The following items could not be reached: %s", - "cannot.reach.item": "%s (Player %d) at %s (Player %d)", - "check.item.location": "Checking if %s (Player %d) is required to beat the game.", - "check.item.location.true": "Yes, item is required.", - "check.item.location.false": "No, item is not required.", - "cannot.reach.progression": "Not all progression items reachable. Something went terribly wrong here.", - "cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.", - "patching.rom": "Patching ROM", - "patching.spoiler": "Creating Spoiler", - "create.meta": "Creating Meta Info", - "calc.playthrough": "Calculating Playthrough", - "made.rom": "Patched ROM: %s", - "made.playthrough": "Printed Playthrough: %s", - "made.spoiler": "Printed Spoiler: %s", - "used.enemizer": "Enemized: %s", - "done": "Done. Enjoy.", - "total.time": "Total Time: %s", - "finished.run": "Finished run", - "generation.failed": "Generation failed", - "generation.fail.rate": "Generation fail rate", - "generation.success.rate": "Generation success rate", - "enemizer.not.found": "Enemizer not found at", - "enemizer.nothing.applied": "No Enemizer options will be applied until this is resolved.", - "building.collection.spheres": "Building up collection spheres", - "building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.", - "building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.", - "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s" -}, -"help": { - "lang": [ "App Language, if available, defaults to English" ], - "create_spoiler": [ "Output a Spoiler File" ], - "bps": [ "Output BPS patches instead of ROMs"], - "logic": [ - "Select Enforcement of Item Requirements. (default: %(default)s)", - "No Glitches: No Glitch knowledge required.", - "Minor Glitches: May require Fake Flippers, Bunny Revival", - " and Dark Room Navigation.", - "No Logic: Distribute items without regard for", - " item requirements." - ], - "mode": [ - "Select game mode. (default: %(default)s)", - "Open: World starts with Zelda rescued.", - "Standard: Fixes Hyrule Castle Secret Entrance and Front Door", - " but may lead to weird rain state issues if you exit", - " through the Hyrule Castle side exits before rescuing", - " Zelda in a full shuffle.", - "Inverted: Starting locations are Dark Sanctuary in West Dark", - " World or at Link's House, which is shuffled freely.", - " Requires the moon pearl to be Link in the Light World", - " instead of a bunny.", - "Retro: Keys are universal, shooting arrows costs rupees,", - " and a few other little things make this more like Zelda-1." - ], - "swords": [ - "Select sword placement. (default: %(default)s)", - "Random: All swords placed randomly.", - "Assured: Start game with a sword already.", - "Swordless: No swords. Curtains in Skull Woods and Agahnim\\'s", - " Tower are removed, Agahnim\\'s Tower barrier can be", - " destroyed with hammer. Misery Mire and Turtle Rock", - " can be opened without a sword. Hammer damages Ganon.", - " Ether and Bombos Tablet can be activated with Hammer", - " (and Book). Bombos pads have been added in Ice", - " Palace, to allow for an alternative to firerod.", - "Bombs: Similar to swordless, but only bombs deal damage", - " to enemies. Bombs deal sword-class damage and can be", - " upgraded. Special enemies such as red eyegores still", - " require the traditional item to kill. Medallions can", - " be used despite not having a sword.", - "Vanilla: Swords are in vanilla locations." - ], - "goal": [ - "Select completion goal. (default: %(default)s)", - "Ganon: Collect all crystals, beat Agahnim 2 then", - " defeat Ganon.", - "Crystals: Collect all crystals then defeat Ganon.", - "Pedestal: Places the Triforce at the Master Sword Pedestal.", - "All Dungeons: Collect all crystals, pendants, beat both", - " Agahnim fights and then defeat Ganon.", - "Triforce Hunt: Places 30 Triforce Pieces in the world, collect", - " 20 of them to beat the game.", - "Trinity: Can beat the game by defeating Ganon, pulling", - " Pedestal, or delivering Triforce Pieces.", - "Zelda I: Collect triforce pieces off bosses then defeat Ganon.", - "Ganon Hunt: Places 30 Triforce Pieces in the world, collect", - " 20 of them then defeat Ganon.", - "Completionist: Find everything then defeat Ganon." - ], - "difficulty": [ - "Select game difficulty. Affects available itempool. (default: %(default)s)", - "Normal: Normal difficulty.", - "Hard: A harder setting with less equipment and reduced health.", - "Expert: A harder yet setting with minimum equipment and health." - ], - "item_functionality": [ - "Select limits on item functionality to increase difficulty. (default: %(default)s)", - "Normal: Normal functionality.", - "Hard: Reduced functionality.", - "Expert: Greatly reduced functionality." - ], - "flute_mode": [ - "Determines if you need to wake up the bird or not on flute pickup (default: %(default)s)", - "Normal: Normal functionality.", - "Active: Flute is activated on pickup." - ], - "bow_mode": [ - "Determines how the bow acts in the pool (default: %(default)s)", - "Progressive: Two progressive bows placed. First picked up is the bow. Second is silvers.", - "Silvers Separate: Bow and silvers are completely separate items.", - "Retro: Z1 Bow where arrows cost money and the Single Arrow must be bought or found to shoot", - "Retro + Silvers: Bow and silvers are completely separate items." - ], - "timer": [ - "Select game timer setting. Affects available itempool. (default: %(default)s)", - "None: No timer.", - "Display: Displays a timer but does not affect", - " the itempool.", - "Timed: Starts with clock at zero. Green Clocks", - " subtract 4 minutes (Total: 20), Blue Clocks", - " subtract 2 minutes (Total: 10), Red Clocks add", - " 2 minutes (Total: 10). Winner is player with", - " lowest time at the end.", - "Timed OHKO: Starts clock at 10 minutes. Green Clocks add", - " 5 minutes (Total: 25). As long as clock is at 0,", - " Link will die in one hit.", - "OHKO: Like Timed OHKO, but no clock items are present", - " and the clock is permenantly at zero.", - "Timed Countdown:Starts with clock at 40 minutes. Same clocks as", - " Timed mode. If time runs out, you lose (but can", - " still keep playing)." - ], - "progressive": [ - "Select progressive equipment setting. Affects available itempool. (default: %(default)s)", - "On: Swords, Shields, Armor, and Gloves will", - " all be progressive equipment. Each subsequent", - " item of the same type the player finds will", - " upgrade that piece of equipment by one stage.", - "Off: Swords, Shields, Armor, and Gloves will not", - " be progressive equipment. Higher level items may", - " be found at any time. Downgrades are not possible.", - "Random: Swords, Shields, Armor, and Gloves will, per", - " category, be randomly progressive or not.", - " Link will die in one hit." - ], - "algorithm": [ - "Select item filling algorithm. (default: %(default)s)", - "balanced: vt26 derivative that aims to strike a balance between", - " the overworld heavy vt25 and the dungeon heavy vt26", - " algorithm.", - "restricted placements: these consider all major items to be special and attempts", - "to place items from fixed to semi-random locations. For purposes of these shuffles, all", - "Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and", - "1/2 magic are considered to be part of a major items pool. Big Keys are added to the pool", - "if shuffled. Same for small keys, compasses, maps, keydrops (if small keys are also shuffled),", - "1 of each capacity upgrade for shopsanity, the quiver item for retro+shopsanity, and", - "triforce pieces for Triforce Hunt. Future modes will add to these as appropriate.", - "vanilla_fill As above, but attempts to place items in their vanilla", - " location first. Major items that cannot be placed that way", - " will attempt to be placed in other failed locations first.", - " Also attempts to place all items in vanilla locations", - "major_only As above, but uses the major items' location preferentially", - " major item location are defined as the group of location where", - " the items are found in the vanilla game.", - "dungeon_only As above, but major items are preferentially placed", - " in dungeons locations first", - "district As above, but groups of locations are chosen randomly", - " from a pool of fixed locations designed to be interesting", - " and give major clues about the location of other", - " advancement items. These fixed groups will be documented." - ], - "shuffle": [ - "Select Entrance Shuffling Algorithm. (default: %(default)s)", - "Simple: Shuffle Dungeon Entrances/Exits between each other", - " and keep all 4-entrance dungeons confined to one", - " location. All caves outside of death mountain are", - " shuffled in pairs and matched by original type.", - "Restricted: Use Dungeons shuffling from Simple but freely", - " connect remaining entrances.", - "Full: Mix cave and dungeon entrances freely while limiting", - " multi-entrance caves to one world.", - "Lite: Beginner-friendly. Dungeons/connectors, dropdowns, and", - " item locations are shuffled in separate pools. Non-item", - " locations remain vanilla. Connectors are same-world.", - "Lean: Same as Lite, except connectors can travel cross worlds.", - "Crossed: Mix cave and dungeon entrances freely while allowing", - " caves to cross between worlds.", - "Insanity: Decouple entrances and exits from each other and", - " shuffle them freely. Caves that used to be single", - " entrance will still exit to the same location from", - " which they are entered.", - "Vanilla: All entrances are in the same locations they were", - " in the base game.", - "The dungeon variants only mix up dungeons and keep the rest of", - "the entrances vanilla." - ], - "ow_shuffle": [ - "This shuffles the layout of the overworld.", - "Vanilla: All overworld transitions are connected the same", - " way they were in the base game.", - "Parallel: Overworld transitions are shuffled, but both worlds", - " will have the same pattern/shape.", - "Full: Overworld transitions are shuffled, but both worlds", - " will have an independent map shape." - ], - "ow_terrain": [ - "With OW Layout Shuffle, this allows land and water edges to be connected." ], - "ow_crossed": [ - "This allows cross-world connections to occur on the overworld.", - "None: No transitions are cross-world connections.", - "Grouped: This ensures a two-plane separation so that you cannot", - " walk around and access the other plane version by walking.", - "Polar: Only used when Tile Flip is enabled. This retains original", - " connections even when overworld tiles are flipped.", - "Limited: Exactly nine transitions are randomly chosen as", - " cross-world connections (to emulate the nine portals).", - "Chaos: Every transition has a 50/50 chance to become a", - " crossworld connection." - ], - "ow_keepsimilar": [ - "This keeps similar edge transitions together. ie. the two west edges on", - "Sanctuary will be paired with another similar pair." ], - "ow_mixed": [ - "Overworld tiles are randomly chosen to become part of the opposite world." - ], - "ow_whirlpool": [ - "Whirlpools will be shuffled and paired together." - ], - "bonk_drops": [ - "Bonk drops from trees, rocks, and statues are shuffled with the item pool." - ], - "ow_fluteshuffle": [ - "This randomizes the flute spot destinations.", - "Vanilla: All flute spots remain unchanged.", - "Balanced: New flute spots will be generated but prevents flute", - " spots from being on any adjacent screen.", - "Random: New flute spots will be generated with minimal bias." - ], - "door_shuffle": [ - "Select Door Shuffling Algorithm. (default: %(default)s)", - "Basic: Doors are mixed within a single dungeon.", - "Partitioned Doors are mixed in 3 partitions: L1-3+HC+AT, D1-4, D5-8", - "Crossed: Doors are mixed between all dungeons.", - "Vanilla: All doors are connected the same way they were in the", - " base game." - ], - "intensity" : [ - "Door Shuffle Intensity Level (default: %(default)s)", - "1: Shuffles normal doors and spiral staircases", - "2: And shuffles open edges and both types of straight staircases", - "3: And shuffles dungeon lobbies", - "random: Picks one of those at random" - ], - "door_type_mode" : [ - "Door Types to Shuffle (default: %(default)s)", - "original: Shuffles key doors, bombable, and dashable doors", - "big: Adds big key doors", - "all: Adds traps doors (and any future supported door types)", - "chaos: Increases the number of door types in all dungeon pools" - ], - "trap_door_mode" : [ - "Trap Door Removal (default: %(default)s)", - "vanilla: No trap door removal", - "optional: Trap doors removed if blocking", - "boss: Also remove boss traps", - "oneway: Remove all annoying trap doors" - ], - "key_logic_algorithm": [ - "Key Logic Algorithm (default: %(default)s)", - "default: Balance between safety and randomization", - "partial: Partial protection when using certain minor glitches", - "strict: Ensure small keys are available" - ], - "decoupledoors" : [ "Door entrances and exits are decoupled" ], - "experimental": [ "Enable experimental features. (default: %(default)s)" ], - "dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ], - "crystals_ganon": [ - "How many crystals are needed to defeat ganon. Any other", - "requirements for ganon for the selected goal still apply.", - "This setting does not apply when the all dungeons goal is", - "selected. (default: %(default)s)", - "Random: Picks a random value between 0 and 7 (inclusive).", - "0-7: Number of crystals needed" - ], - "crystals_gt": [ - "How many crystals are needed to open GT. For inverted mode", - "this applies to the castle tower door instead. (default: %(default)s)", - "Random: Picks a random value between 0 and 7 (inclusive).", - "0-7: Number of crystals needed" - ], - "ganon_item": [ - "What item Ganon is vulnerable to while stunned in his final phase.", - "Default: The usual item (silver arrows except in bomb-only mode) will", - " damage stunned Ganon.", - "Random: Picks a random damaging item (but not a medallion if swordless)", - ": The specified item will damage stunned Ganon." - ], - "openpyramid": [ "Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. (default: %(default)s)" ], - "rom": [ - "Path to an ALttP JP (1.0) rom to use as a base." , - "(default: %(default)s)" - ], - "loglevel": [ "Select level of logging for output. (default: %(default)s)" ], - "seed": [ "Define seed number to generate." ], - "count": [ - "Use to batch generate multiple seeds with same settings.", - "If --seed is provided, it will be used for the first seed, then", - "used to derive the next seed (i.e. generating %(default)s seed(s) with", - "--seed given will produce the same %(default)s (different) rom(s) each", - "time)." - ], - "fastmenu": [ - "Select the rate at which the menu opens and closes. (default: %(default)s)" - ], - "quickswap": [ "Enable quick item swapping with L and R. (default: %(default)s)" ], - "disablemusic": [ "Disables game music including MSU-1. (default: %(default)s)" ], - "mapshuffle": [ "Maps are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ], - "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)" ], - "shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"], - "dropshuffle": [ "Keys dropped by enemies are shuffled and other items can take their place. (default: %(default)s)"], - "pottery": [ "Controls how items under pots are shuffled and if other items can take their place:", - "None: No pots are changed", - "Keys: Key pots are included in the location pool and other items can take their place", - "Cave: Only pots in houses and caves are included in the location pool", - "CaveKeys: Both pots in houses and caves and keys pots are included in the location pool", - "Reduced: Same as KeyCaves + 25%% of Pots in dungeons (dynamic mode)", - "Clustered: Same as KeyCaves + 50%% of Pots in dungeons, chosen by logical group (dynamic mode)", - "NonEmpty: All pots that are not originally empty are included in the location pool", - "Dungeon: Only pots in dungeons are included in the location pool", - "Lottery: All pots are part of the location pool" - ], - "colorizepots": ["All pots chosen to be in location pool by the pottery setting are different.", - "Forced on in dynamic modes. Forced off in lottery"], - "shufflepots": [ "Pots and switches are shuffled on the supertile (legacy potshuffle) (default: %(default)s)"], - "mixed_travel": [ - "How to handle potential traversal between dungeon in Crossed door shuffle", - "Prevent: Rails are placed to prevent bombs jump and hovering from changing dungeon except with glitched logic settings", - "Allow: Take the rails off, \"I know what I'm doing\"", - "Force: Force these troublesome connections to be in the same dungeon (but not in logic). No rails will appear" - ], - "standardize_palettes": [ - "In cross dungeon shuffle, we can keep the rooms original palette or attempt to standardize them", - "Standardize: Attempts to make the palette the same between dungeons", - "Original: Dungeons rooms retain original palettes" - ], - "retro": [ - "Keys are universal, shooting arrows costs rupees,", - "and a few other little things make this more like Zelda-1. (default: %(default)s)" - ], - "take_any": [ - "Take Any caves from Zelda 1 (default: %(default)s)", - "None: No take any caves", - "Random: Take any caves can replace a random set of un-interesting caves. See documentation for full list", - "Fixed: Take any caves will replace certain location. See documentation for full list" - ], - "pseudoboots": [ " Start with pseudo boots that allow dashing but no item checks (default: %(default)s)"], - "bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], - "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], - "usestartinventory": [ "Toggle usage of Starting Inventory." ], - "customizer": ["Path to a customizer file."], - "custom": [ "Not supported." ], - "customitemarray": [ "Not supported." ], - "accessibility": [ - "Select Item/Location Accessibility. (default: %(default)s)", - "Items: You can reach all unique inventory items. No guarantees about", - " reaching all locations or all keys.", - "Locations: You will be able to reach every location in the game.", - "None: You will be able to reach enough locations to beat the game." - ], - "restrict_boss_items": [ - "Select which dungeon are not allowed on bosses (default: %(default)s)", - "None: All items allowed", - "Mapcompass: Map and Compass are required before you defeat the boss.", - "Dungeon: Same as above and keys too cannot be on the boss. Small key shuffle", - " and big key shuffle override this behavior" - ], - "hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ], - "no_shuffleganon": [ - "Don't include the Ganon's Tower and Pyramid Hole in the", - "entrance shuffle pool. (default: %(default)s)" - ], - "shufflelinks": [ - "Include Link's House in the entrance shuffle pool. (default: %(default)s)" - ], - "shuffletavern": [ - "Include the back of the tavern in the entrance shuffle pool. (default: %(default)s)" - ], - "overworld_map": [ - "Control if and how the overworld map indicators show the locations of dungeons (default: %(default)s)" - ], - "heartbeep": [ - "Select the rate at which the heart beep sound is played at", - "low health. (default: %(default)s)" - ], - "heartcolor": [ "Select the color of Link\\'s heart meter. (default: %(default)s)" ], - "sprite": [ - "Path to a sprite sheet to use for Link. Needs to be in", - "binary format and have a length of 0x7000 (28672) bytes,", - "or 0x7078 (28792) bytes including palette data.", - "Alternatively, can be a ALttP Rom patched with a Link", - "sprite that will be extracted." - ], - "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], - "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], - "msu_resume": [ "Enable MSU Resume (default: %(default)s)" ], - "collection_rate": [ "Display collection rate (default: %(default)s)" ], - "create_rom": [ "Create an output rom file. (default: %(default)s)" ], - "gui": [ "Launch the GUI. (default: %(default)s)" ], - "jsonout": [ - "Output .json patch to stdout instead of a patched rom. Used", - "for VT site integration, do not use otherwise. (default: %(default)s)" - ] -} + "cli": { + "yes": "Yes", + "no": "No", + "app.title": "ALttP Door Randomizer Version %s : --seed %s --code %s", + "version": "Version", + "seed": "Seed", + "player": "Player", + "shuffling.overworld": "Shuffling overworld", + "shuffling.world": "Shuffling entrances", + "shuffling.prep": "Dungeon and Item prep", + "shuffling.dungeons": "Shuffling dungeons", + "shuffling.pots": "Shuffling pots", + "basic.traversal": "--Basic Traversal", + "generating.dungeon": "Generating dungeons", + "shuffling.keydoors": "Shuffling Key doors", + "lowering.keys.candidates": "Lowering key door count because not enough candidates", + "lowering.keys.layouts": "Lowering key door count because no valid layouts", + "keydoor.shuffle.time": "Key door shuffle time", + "keydoor.shuffle.time.crossed": "Cross Dungeon: Key door shuffle time", + "generating.itempool": "Generating Item Pool", + "calc.access.rules": "Calculating Access Rules", + "placing.dungeon.prizes": "Placing Dungeon Prizes", + "placing.dungeon.items": "Placing Dungeon Items", + "keylock.detected": "Keylock detected", + "fill.world": "Fill the world", + "balance.doors": "-Balancing Doors", + "re-balancing": "-Re-balancing", + "balancing": "--Balancing", + "splitting.up": "Splitting Up", + "balance.multiworld": "Balancing multiworld progression", + "cannot.beat.game": "Cannot beat game! Something went terribly wrong here!", + "cannot.reach.items": "The following items could not be reached: %s", + "cannot.reach.item": "%s (Player %d) at %s (Player %d)", + "check.item.location": "Checking if %s (Player %d) is required to beat the game.", + "check.item.location.true": "Yes, item is required.", + "check.item.location.false": "No, item is not required.", + "cannot.reach.progression": "Not all progression items reachable. Something went terribly wrong here.", + "cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.", + "patching.rom": "Patching ROM", + "patching.spoiler": "Creating Spoiler", + "create.meta": "Creating Meta Info", + "calc.playthrough": "Calculating Playthrough", + "made.rom": "Patched ROM: %s", + "made.playthrough": "Printed Playthrough: %s", + "made.spoiler": "Printed Spoiler: %s", + "used.enemizer": "Enemized: %s", + "done": "Done. Enjoy.", + "total.time": "Total Time: %s", + "finished.run": "Finished run", + "generation.failed": "Generation failed", + "generation.fail.rate": "Generation fail rate", + "generation.success.rate": "Generation success rate", + "enemizer.not.found": "Enemizer not found at", + "enemizer.nothing.applied": "No Enemizer options will be applied until this is resolved.", + "building.collection.spheres": "Building up collection spheres", + "building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.", + "building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.", + "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s" + }, + "help": { + "lang": [ "App Language, if available, defaults to English" ], + "create_spoiler": [ "Output a Spoiler File" ], + "bps": [ "Output BPS patches instead of ROMs"], + "logic": [ + "Select Enforcement of Item Requirements. (default: %(default)s)", + "No Glitches: No Glitch knowledge required.", + "Minor Glitches: May require Fake Flippers, Bunny Revival", + " and Dark Room Navigation.", + "No Logic: Distribute items without regard for", + " item requirements." + ], + "mode": [ + "Select game mode. (default: %(default)s)", + "Open: World starts with Zelda rescued.", + "Standard: Fixes Hyrule Castle Secret Entrance and Front Door", + " but may lead to weird rain state issues if you exit", + " through the Hyrule Castle side exits before rescuing", + " Zelda in a full shuffle.", + "Inverted: Starting locations are Dark Sanctuary in West Dark", + " World or at Link's House, which is shuffled freely.", + " Requires the moon pearl to be Link in the Light World", + " instead of a bunny.", + "Retro: Keys are universal, shooting arrows costs rupees,", + " and a few other little things make this more like Zelda-1." + ], + "swords": [ + "Select sword placement. (default: %(default)s)", + "Random: All swords placed randomly.", + "Assured: Start game with a sword already.", + "Swordless: No swords. Curtains in Skull Woods and Agahnim\\'s", + " Tower are removed, Agahnim\\'s Tower barrier can be", + " destroyed with hammer. Misery Mire and Turtle Rock", + " can be opened without a sword. Hammer damages Ganon.", + " Ether and Bombos Tablet can be activated with Hammer", + " (and Book). Bombos pads have been added in Ice", + " Palace, to allow for an alternative to firerod.", + "Vanilla: Swords are in vanilla locations." + ], + "goal": [ + "Select completion goal. (default: %(default)s)", + "Ganon: Collect all crystals, beat Agahnim 2 then", + " defeat Ganon.", + "Crystals: Collect all crystals then defeat Ganon.", + "Pedestal: Places the Triforce at the Master Sword Pedestal.", + "All Dungeons: Collect all crystals, pendants, beat both", + " Agahnim fights and then defeat Ganon.", + "Triforce Hunt: Places 30 Triforce Pieces in the world, collect", + " 20 of them to beat the game.", + "Trinity: Can beat the game by defeating Ganon, pulling", + " Pedestal, or delivering Triforce Pieces.", + "Ganon Hunt: Places 30 Triforce Pieces in the world, collect", + " 20 of them then defeat Ganon.", + "Completionist: Find everything then defeat Ganon." + ], + "difficulty": [ + "Select game difficulty. Affects available itempool. (default: %(default)s)", + "Normal: Normal difficulty.", + "Hard: A harder setting with less equipment and reduced health.", + "Expert: A harder yet setting with minimum equipment and health." + ], + "item_functionality": [ + "Select limits on item functionality to increase difficulty. (default: %(default)s)", + "Normal: Normal functionality.", + "Hard: Reduced functionality.", + "Expert: Greatly reduced functionality." + ], + "flute_mode": [ + "Determines if you need to wake up the bird or not on flute pickup (default: %(default)s)", + "Normal: Normal functionality.", + "Active: Flute is activated on pickup." + ], + "bow_mode": [ + "Determines how the bow acts in the pool (default: %(default)s)", + "Progressive: Two progressive bows placed. First picked up is the bow. Second is silvers.", + "Silvers Separate: Bow and silvers are completely separate items.", + "Retro: Z1 Bow where arrows cost money and the Single Arrow must be bought or found to shoot", + "Retro + Silvers: Bow and silvers are completely separate items." + ], + "timer": [ + "Select game timer setting. Affects available itempool. (default: %(default)s)", + "None: No timer.", + "Display: Displays a timer but does not affect", + " the itempool.", + "Timed: Starts with clock at zero. Green Clocks", + " subtract 4 minutes (Total: 20), Blue Clocks", + " subtract 2 minutes (Total: 10), Red Clocks add", + " 2 minutes (Total: 10). Winner is player with", + " lowest time at the end.", + "Timed OHKO: Starts clock at 10 minutes. Green Clocks add", + " 5 minutes (Total: 25). As long as clock is at 0,", + " Link will die in one hit.", + "OHKO: Like Timed OHKO, but no clock items are present", + " and the clock is permenantly at zero.", + "Timed Countdown:Starts with clock at 40 minutes. Same clocks as", + " Timed mode. If time runs out, you lose (but can", + " still keep playing)." + ], + "progressive": [ + "Select progressive equipment setting. Affects available itempool. (default: %(default)s)", + "On: Swords, Shields, Armor, and Gloves will", + " all be progressive equipment. Each subsequent", + " item of the same type the player finds will", + " upgrade that piece of equipment by one stage.", + "Off: Swords, Shields, Armor, and Gloves will not", + " be progressive equipment. Higher level items may", + " be found at any time. Downgrades are not possible.", + "Random: Swords, Shields, Armor, and Gloves will, per", + " category, be randomly progressive or not.", + " Link will die in one hit." + ], + "algorithm": [ + "Select item filling algorithm. (default: %(default)s)", + "balanced: vt26 derivative that aims to strike a balance between", + " the overworld heavy vt25 and the dungeon heavy vt26", + " algorithm.", + "restricted placements: these consider all major items to be special and attempts", + "to place items from fixed to semi-random locations. For purposes of these shuffles, all", + "Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and", + "1/2 magic are considered to be part of a major items pool. Big Keys are added to the pool", + "if shuffled. Same for small keys, compasses, maps, keydrops (if small keys are also shuffled),", + "1 of each capacity upgrade for shopsanity, the quiver item for retro+shopsanity, and", + "triforce pieces for Triforce Hunt. Future modes will add to these as appropriate.", + "vanilla_fill As above, but attempts to place items in their vanilla", + " location first. Major items that cannot be placed that way", + " will attempt to be placed in other failed locations first.", + " Also attempts to place all items in vanilla locations", + "major_only As above, but uses the major items' location preferentially", + " major item location are defined as the group of location where", + " the items are found in the vanilla game.", + "dungeon_only As above, but major items are preferentially placed", + " in dungeons locations first", + "district As above, but groups of locations are chosen randomly", + " from a pool of fixed locations designed to be interesting", + " and give major clues about the location of other", + " advancement items. These fixed groups will be documented." + ], + "shuffle": [ + "Select Entrance Shuffling Algorithm. (default: %(default)s)", + "Simple: Shuffle Dungeon Entrances/Exits between each other", + " and keep all 4-entrance dungeons confined to one", + " location. All caves outside of death mountain are", + " shuffled in pairs and matched by original type.", + "Restricted: Use Dungeons shuffling from Simple but freely", + " connect remaining entrances.", + "Full: Mix cave and dungeon entrances freely while limiting", + " multi-entrance caves to one world.", + "Lite: Beginner-friendly. Dungeons/connectors, dropdowns, and", + " item locations are shuffled in separate pools. Non-item", + " locations remain vanilla. Connectors are same-world.", + "Lean: Same as Lite, except connectors can travel cross worlds.", + "Crossed: Mix cave and dungeon entrances freely while allowing", + " caves to cross between worlds.", + "Swapped: Same as Crossed, but entrances switch places in pairs.", + "Insanity: Decouple entrances and exits from each other and", + " shuffle them freely. Caves that used to be single", + " entrance will still exit to the same location from", + " which they are entered.", + "Vanilla: All entrances are in the same locations they were", + " in the base game.", + "The dungeon variants only mix up dungeons and keep the rest of", + "the entrances vanilla." + ], + "ow_shuffle": [ + "This shuffles the layout of the overworld.", + "Vanilla: All overworld transitions are connected the same", + " way they were in the base game.", + "Parallel: Overworld transitions are shuffled, but both worlds", + " will have the same pattern/shape.", + "Full: Overworld transitions are shuffled, but both worlds", + " will have an independent map shape." + ], + "ow_terrain": [ + "With OW Layout Shuffle, this allows land and water edges to be connected." ], + "ow_crossed": [ + "This allows cross-world connections to occur on the overworld.", + "None: No transitions are cross-world connections.", + "Grouped: This ensures a two-plane separation so that you cannot", + " walk around and access the other plane version by walking.", + "Polar: Only used when Tile Flip is enabled. This retains original", + " connections even when overworld tiles are flipped.", + "Limited: Exactly nine transitions are randomly chosen as", + " cross-world connections (to emulate the nine portals).", + "Chaos: Every transition has a 50/50 chance to become a", + " crossworld connection." + ], + "ow_keepsimilar": [ + "This keeps similar edge transitions together. ie. the two west edges on", + "Sanctuary will be paired with another similar pair." ], + "ow_mixed": [ + "Overworld tiles are randomly chosen to become part of the opposite world." + ], + "ow_whirlpool": [ + "Whirlpools will be shuffled and paired together." + ], + "bonk_drops": [ + "Bonk drops from trees, rocks, and statues are shuffled with the item pool." + ], + "ow_fluteshuffle": [ + "This randomizes the flute spot destinations.", + "Vanilla: All flute spots remain unchanged.", + "Balanced: New flute spots will be generated but prevents flute", + " spots from being on any adjacent screen.", + "Random: New flute spots will be generated with minimal bias." + ], + "door_shuffle": [ + "Select Door Shuffling Algorithm. (default: %(default)s)", + "Basic: Doors are mixed within a single dungeon.", + "Paired Dungeon are paired (with one trio) and only mixed in those groups", + "Partitioned Doors are mixed in 3 partitions: L1-3+HC+AT, D1-4, D5-8", + "Crossed: Doors are mixed between all dungeons.", + "Vanilla: All doors are connected the same way they were in the", + " base game." + ], + "intensity" : [ + "Door Shuffle Intensity Level (default: %(default)s)", + "1: Shuffles normal doors and spiral staircases", + "2: And shuffles open edges and both types of straight staircases", + "3: And shuffles dungeon lobbies", + "random: Picks one of those at random" + ], + "door_type_mode" : [ + "Door Types to Shuffle (default: %(default)s)", + "original: Shuffles key doors, bombable, and dashable doors", + "big: Adds big key doors", + "all: Adds traps doors (and any future supported door types)", + "chaos: Increases the number of door types in all dungeon pools" + ], + "trap_door_mode" : [ + "Trap Door Removal (default: %(default)s)", + "vanilla: No trap door removal", + "optional: Trap doors removed if blocking", + "boss: Also remove boss traps", + "oneway: Remove all annoying trap doors" + ], + "key_logic_algorithm": [ + "Key Logic Algorithm (default: %(default)s)", + "default: Balance between safety and randomization", + "partial: Partial protection when using certain minor glitches", + "strict: Ensure small keys are available" + ], + "decoupledoors" : [ "Door entrances and exits are decoupled" ], + "door_self_loops" : [ "Spiral stairs are allowed to self-loop" ], + "experimental": [ "Enable experimental features. (default: %(default)s)" ], + "dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ], + "crystals_ganon": [ + "How many crystals are needed to defeat ganon. Any other", + "requirements for ganon for the selected goal still apply.", + "This setting does not apply when the all dungeons goal is", + "selected. (default: %(default)s)", + "Random: Picks a random value between 0 and 7 (inclusive).", + "0-7: Number of crystals needed" + ], + "crystals_gt": [ + "How many crystals are needed to open GT. For inverted mode", + "this applies to the castle tower door instead. (default: %(default)s)", + "Random: Picks a random value between 0 and 7 (inclusive).", + "0-7: Number of crystals needed" + ], + "openpyramid": [ "Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. (default: %(default)s)" ], + "rom": [ + "Path to an ALttP JP (1.0) rom to use as a base." , + "(default: %(default)s)" + ], + "loglevel": [ "Select level of logging for output. (default: %(default)s)" ], + "seed": [ "Define seed number to generate." ], + "count": [ + "Use to batch generate multiple seeds with same settings.", + "If --seed is provided, it will be used for the first seed, then", + "used to derive the next seed (i.e. generating %(default)s seed(s) with", + "--seed given will produce the same %(default)s (different) rom(s) each", + "time)." + ], + "fastmenu": [ + "Select the rate at which the menu opens and closes. (default: %(default)s)" + ], + "quickswap": [ "Enable quick item swapping with L and R. (default: %(default)s)" ], + "disablemusic": [ "Disables game music including MSU-1. (default: %(default)s)" ], + "mapshuffle": [ "Maps are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ], + "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)" ], + "shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"], + "dropshuffle": [ "Keys dropped by enemies are shuffled and other items can take their place. (default: %(default)s)"], + "pottery": [ "Controls how items under pots are shuffled and if other items can take their place:", + "None: No pots are changed", + "Keys: Key pots are included in the location pool and other items can take their place", + "Cave: Only pots in houses and caves are included in the location pool", + "CaveKeys: Both pots in houses and caves and keys pots are included in the location pool", + "Reduced: Same as KeyCaves + 25%% of Pots in dungeons (dynamic mode)", + "Clustered: Same as KeyCaves + 50%% of Pots in dungeons, chosen by logical group (dynamic mode)", + "NonEmpty: All pots that are not originally empty are included in the location pool", + "Dungeon: Only pots in dungeons are included in the location pool", + "Lottery: All pots are part of the location pool" + ], + "colorizepots": ["All pots chosen to be in location pool by the pottery setting are different.", + "Forced on in dynamic modes. Forced off in lottery"], + "shufflepots": [ "Pots and switches are shuffled on the supertile (legacy potshuffle) (default: %(default)s)"], + "mixed_travel": [ + "How to handle potential traversal between dungeon in Crossed door shuffle", + "Prevent: Rails are placed to prevent bombs jump and hovering from changing dungeon except with glitched logic settings", + "Allow: Take the rails off, \"I know what I'm doing\"", + "Force: Force these troublesome connections to be in the same dungeon (but not in logic). No rails will appear" + ], + "standardize_palettes": [ + "In cross dungeon shuffle, we can keep the rooms original palette or attempt to standardize them", + "Standardize: Attempts to make the palette the same between dungeons", + "Original: Dungeons rooms retain original palettes" + ], + "retro": [ + "Keys are universal, shooting arrows costs rupees,", + "and a few other little things make this more like Zelda-1. (default: %(default)s)" + ], + "take_any": [ + "Take Any caves from Zelda 1 (default: %(default)s)", + "None: No take any caves", + "Random: Take any caves can replace a random set of un-interesting caves. See documentation for full list", + "Fixed: Take any caves will replace certain location. See documentation for full list" + ], + "pseudoboots": [ " Start with pseudo boots that allow dashing but no item checks (default: %(default)s)"], + "bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], + "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], + "usestartinventory": [ "Toggle usage of Starting Inventory." ], + "customizer": ["Path to a customizer file."], + "custom": [ "Not supported." ], + "customitemarray": [ "Not supported." ], + "accessibility": [ + "Select Item/Location Accessibility. (default: %(default)s)", + "Items: You can reach all unique inventory items. No guarantees about", + " reaching all locations or all keys.", + "Locations: You will be able to reach every location in the game.", + "None: You will be able to reach enough locations to beat the game." + ], + "restrict_boss_items": [ + "Select which dungeon are not allowed on bosses (default: %(default)s)", + "None: All items allowed", + "Mapcompass: Map and Compass are required before you defeat the boss.", + "Dungeon: Same as above and keys too cannot be on the boss. Small key shuffle", + " and big key shuffle override this behavior" + ], + "hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ], + "no_shuffleganon": [ + "Don't include the Ganon's Tower and Pyramid Hole in the", + "entrance shuffle pool. (default: %(default)s)" + ], + "shufflelinks": [ + "Include Link's House in the entrance shuffle pool. (default: %(default)s)" + ], + "shuffletavern": [ + "Include the back of the tavern in the entrance shuffle pool. (default: %(default)s)" + ], + "overworld_map": [ + "Control if and how the overworld map indicators show the locations of dungeons (default: %(default)s)" + ], + "heartbeep": [ + "Select the rate at which the heart beep sound is played at", + "low health. (default: %(default)s)" + ], + "heartcolor": [ "Select the color of Link\\'s heart meter. (default: %(default)s)" ], + "sprite": [ + "Path to a sprite sheet to use for Link. Needs to be in", + "binary format and have a length of 0x7000 (28672) bytes,", + "or 0x7078 (28792) bytes including palette data.", + "Alternatively, can be a ALttP Rom patched with a Link", + "sprite that will be extracted." + ], + "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], + "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], + "msu_resume": [ "Enable MSU Resume (default: %(default)s)" ], + "collection_rate": [ "Display collection rate (default: %(default)s)" ], + "create_rom": [ "Create an output rom file. (default: %(default)s)" ], + "gui": [ "Launch the GUI. (default: %(default)s)" ], + "jsonout": [ + "Output .json patch to stdout instead of a patched rom. Used", + "for VT site integration, do not use otherwise. (default: %(default)s)" + ] + } } diff --git a/resources/app/gui/custom/overview/widgets.json b/resources/app/gui/custom/overview/widgets.json index ee7be421..03c7fbaf 100644 --- a/resources/app/gui/custom/overview/widgets.json +++ b/resources/app/gui/custom/overview/widgets.json @@ -1,4 +1,22 @@ { + "startHeader": { + "usestartinventory": { + "type": "checkbox", + "config": { + "padx": [10,0], + "pady": [10,10] + } + } + }, + "customHeader": { + "usecustompool": { + "type": "checkbox", + "config": { + "padx": [10,0], + "pady": [10,10] + } + } + }, "itemList1": { "bow": { "type": "textbox", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 3846113a..3398dd58 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -57,25 +57,13 @@ "randomizer.dungeon.smallkeyshuffle.wild": "Randomized", "randomizer.dungeon.smallkeyshuffle.universal": "Universal", "randomizer.dungeon.bigkeyshuffle": "Big Keys", - "randomizer.dungeon.keydropshuffle": "Key Drop Shuffle (Legacy)", "randomizer.dungeon.decoupledoors": "Decouple Doors", - "randomizer.dungeon.dropshuffle": "Shuffle Enemy Key Drops", - "randomizer.dungeon.potshuffle": "Pot Shuffle (Legacy)", - "randomizer.dungeon.pottery": "Pottery", - "randomizer.dungeon.pottery.none": "None", - "randomizer.dungeon.pottery.keys": "Key Pots", - "randomizer.dungeon.pottery.cave": "Cave Pots", - "randomizer.dungeon.pottery.cavekeys": "Cave+Key Pots", - "randomizer.dungeon.pottery.reduced": "Reduced Dungeon Pots (Dynamic)", - "randomizer.dungeon.pottery.clustered": "Clustered Dungeon Pots (Dynamic)", - "randomizer.dungeon.pottery.nonempty": "Excludes Empty Pots", - "randomizer.dungeon.pottery.dungeon": "Dungeon Pots", - "randomizer.dungeon.pottery.lottery": "Lottery (All Pots and Large Blocks)", - "randomizer.dungeon.colorizepots": "Colorize Randomized Pots", + "randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs", "randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle", "randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla", "randomizer.dungeon.dungeondoorshuffle.basic": "Basic", + "randomizer.dungeon.dungeondoorshuffle.paired": "Paired", "randomizer.dungeon.dungeondoorshuffle.partitioned": "Partitioned", "randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed", @@ -123,7 +111,7 @@ "randomizer.enemizer.enemyshuffle.none": "None", "randomizer.enemizer.enemyshuffle.shuffled": "Shuffled", "randomizer.enemizer.enemyshuffle.random": "Random", - "randomizer.enemizer.enemyshuffle.legacy": "Random (including Thieves)", + "randomizer.enemizer.enemyshuffle.legacy": "Random (50/50 Thieves)", "randomizer.enemizer.bossshuffle": "Boss Shuffle", "randomizer.enemizer.bossshuffle.none": "None", @@ -168,8 +156,6 @@ "randomizer.overworld.whirlpool": "Whirlpool Shuffle", - "randomizer.overworld.bonk_drops": "Bonk Drops", - "randomizer.overworld.overworldflute": "Flute Shuffle", "randomizer.overworld.overworldflute.vanilla": "Vanilla", "randomizer.overworld.overworldflute.balanced": "Balanced", @@ -194,16 +180,12 @@ "randomizer.entrance.entranceshuffle.restricted": "Restricted", "randomizer.entrance.entranceshuffle.full": "Full", "randomizer.entrance.entranceshuffle.lean": "Lean", + "randomizer.entrance.entranceshuffle.swapped": "Swapped", "randomizer.entrance.entranceshuffle.crossed": "Crossed", "randomizer.entrance.entranceshuffle.insanity": "Insanity", "randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full", "randomizer.entrance.entranceshuffle.dungeonssimple": "Dungeons + Simple", - "randomizer.entrance.take_any": "Take Any Caves", - "randomizer.entrance.take_any.none": "None", - "randomizer.entrance.take_any.random": "Random", - "randomizer.entrance.take_any.fixed": "Fixed", - "randomizer.gameoptions.nobgm": "Disable Music & MSU-1", "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", @@ -252,9 +234,6 @@ "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.print_custom_yaml": "Print Customizer File", - "randomizer.generation.usestartinventory": "Use Starting Inventory", - "randomizer.generation.usecustompool": "Use Custom Item Pool", - "randomizer.generation.race": "Generate \"Race\" ROM", "randomizer.generation.saveonexit": "Save Settings on Exit", "randomizer.generation.saveonexit.ask": "Ask Me", @@ -266,16 +245,17 @@ "randomizer.generation.rom.dialog.romfiles": "Rom Files", "randomizer.generation.rom.dialog.allfiles": "All Files", - "randomizer.item.hints": "Include Helpful Hints", + "randomizer.item.hints": "Hints", + "randomizer.item.race": "Generate \"Race\" ROM", "randomizer.item.retro": "Retro mode", - "randomizer.item.pseudoboots": "Start with Pseudo Boots", - "randomizer.item.bombbag": "Bombbag", + "randomizer.item.pseudoboots": "Pseudoboots", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", "randomizer.item.worldstate.open": "Open", "randomizer.item.worldstate.inverted": "Inverted", "randomizer.item.worldstate.retro": "Retro", + "randomizer.item.retro": "Enable Retro", "randomizer.item.logiclevel": "Logic Level", "randomizer.item.logiclevel.noglitches": "No Glitches", @@ -290,7 +270,7 @@ "randomizer.item.goal.triforcehunt": "Triforce Hunt", "randomizer.item.goal.trinity": "Trinity", "randomizer.item.goal.crystals": "Crystals", - "randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon", + "randomizer.item.goal.ganonhunt": "Ganonhunt", "randomizer.item.goal.completionist": "Completionist", "randomizer.item.crystals_gt": "Crystals to open GT", @@ -342,25 +322,66 @@ "randomizer.item.weapons.bombs": "Bomb-Only", "randomizer.item.weapons.vanilla": "Vanilla", - "randomizer.item.beemizer": "Beemizer", - "randomizer.item.beemizer.0": "No Bee Traps", - "randomizer.item.beemizer.1": "25% Bee Traps", - "randomizer.item.beemizer.2": "40% Traps, 20% Refills", - "randomizer.item.beemizer.3": "50% Traps, 50% Refills", - "randomizer.item.beemizer.4": "100% Bee Traps", + "randomizer.item.sortingalgo": "Item Fill", + "randomizer.item.sortingalgo.balanced": "Balanced", + "randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill", + "randomizer.item.sortingalgo.major_only": "Major Location Restriction", + "randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction", + "randomizer.item.sortingalgo.district": "District Restriction", - "randomizer.item.itempool": "Item Pool", - "randomizer.item.itempool.normal": "Normal", - "randomizer.item.itempool.hard": "Hard", - "randomizer.item.itempool.expert": "Expert", + "randomizer.item.accessibility": "Accessibility", + "randomizer.item.accessibility.items": "100% Inventory", + "randomizer.item.accessibility.locations": "100% Locations", + "randomizer.item.accessibility.none": "Beatable", - "randomizer.item.shopsanity": "Shopsanity", + "randomizer.item.restrict_boss_items": "Forbidden Boss Items", + "randomizer.item.restrict_boss_items.none": "None", + "randomizer.item.restrict_boss_items.mapcompass": "Map & Compass", + "randomizer.item.restrict_boss_items.dungeon": "Map & Compass & Keys", "randomizer.item.itemfunction": "Item Functionality", "randomizer.item.itemfunction.normal": "Normal", "randomizer.item.itemfunction.hard": "Hard", "randomizer.item.itemfunction.expert": "Expert", - + + "randomizer.item.timer": "Timer Setting", + "randomizer.item.timer.none": "No Timer", + "randomizer.item.timer.display": "Stopwatch", + "randomizer.item.timer.timed": "Timed", + "randomizer.item.timer.timed-ohko": "Timed OHKO", + "randomizer.item.timer.ohko": "OHKO", + "randomizer.item.timer.timed-countdown": "Timed Countdown", + + "randomizer.item.shopsanity": "Shopsanity", + + "randomizer.item.bonk_drops": "Bonk Drops", + + "randomizer.item.pottery": "Pottery", + "randomizer.item.pottery.none": "None", + "randomizer.item.pottery.keys": "Key Pots", + "randomizer.item.pottery.cave": "Cave Pots", + "randomizer.item.pottery.cavekeys": "Cave+Key Pots", + "randomizer.item.pottery.reduced": "Reduced Dungeon Pots (Dynamic)", + "randomizer.item.pottery.clustered": "Clustered Dungeon Pots (Dynamic)", + "randomizer.item.pottery.nonempty": "Excludes Empty Pots", + "randomizer.item.pottery.dungeon": "Dungeon Pots", + "randomizer.item.pottery.lottery": "Lottery (All Pots and Large Blocks)", + + "randomizer.item.colorizepots": "Colorize Randomized Pots", + "randomizer.item.potshuffle": "Pot Shuffle (Legacy)", + + "randomizer.item.dropshuffle": "Shuffle Enemy Key Drops", + "randomizer.item.keydropshuffle": "Enable Key Drop Shuffle (Legacy)", + + "randomizer.item.take_any": "Take Any Caves", + "randomizer.item.take_any.none": "None", + "randomizer.item.take_any.random": "Random", + "randomizer.item.take_any.fixed": "Fixed", + + "randomizer.item.itempool": "Item Pool", + "randomizer.item.itempool.normal": "Normal", + "randomizer.item.itempool.hard": "Hard", + "randomizer.item.itempool.expert": "Expert", "randomizer.item.flute_mode": "Flute Mode", "randomizer.item.flute_mode.normal": "Normal", "randomizer.item.flute_mode.active": "Pre-Activated", @@ -371,30 +392,17 @@ "randomizer.item.bow_mode.retro": "Retro (Progressive)", "randomizer.item.bow_mode.retro_silvers": "Retro + Silvers", - "randomizer.item.timer": "Timer Setting", - "randomizer.item.timer.none": "No Timer", - "randomizer.item.timer.display": "Stopwatch", - "randomizer.item.timer.timed": "Timed", - "randomizer.item.timer.timed-ohko": "Timed OHKO", - "randomizer.item.timer.ohko": "OHKO", - "randomizer.item.timer.timed-countdown": "Timed Countdown", + "randomizer.item.beemizer": "Beemizer", + "randomizer.item.beemizer.0": "No Bee Traps", + "randomizer.item.beemizer.1": "25% Bee Traps", + "randomizer.item.beemizer.2": "40% Traps, 20% Refills", + "randomizer.item.beemizer.3": "50% Traps, 50% Refills", + "randomizer.item.beemizer.4": "100% Bee Traps", - "randomizer.item.accessibility": "Accessibility", - "randomizer.item.accessibility.items": "100% Inventory", - "randomizer.item.accessibility.locations": "100% Locations", - "randomizer.item.accessibility.none": "Beatable", + "randomizer.item.bombbag": "Bombbag", - "randomizer.item.sortingalgo": "Item Sorting", - "randomizer.item.sortingalgo.balanced": "Balanced", - "randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill", - "randomizer.item.sortingalgo.major_only": "Major Location Restriction", - "randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction", - "randomizer.item.sortingalgo.district": "District Restriction", - - "randomizer.item.restrict_boss_items": "Forbidden Boss Items", - "randomizer.item.restrict_boss_items.none": "None", - "randomizer.item.restrict_boss_items.mapcompass": "Map & Compass", - "randomizer.item.restrict_boss_items.dungeon": "Map & Compass & Keys", + "startinventory.usestartinventory": "Use Starting Inventory", + "custom.usecustompool": "Use Custom Item Pool", "bottom.content.worlds": "Worlds", "bottom.content.names": "Player names", diff --git a/resources/app/gui/randomize/dungeon/keysanity.json b/resources/app/gui/randomize/dungeon/keysanity.json index ffd9bc92..5a2a8e60 100644 --- a/resources/app/gui/randomize/dungeon/keysanity.json +++ b/resources/app/gui/randomize/dungeon/keysanity.json @@ -1,5 +1,16 @@ { "keysanity": { + "smallkeyshuffle": { + "type": "selectbox", + "options": [ + "none", + "wild", + "universal" + ], + "config": { + "padx": [20,0] + } + }, "mapshuffle": { "type": "checkbox" }, "compassshuffle": { "type": "checkbox" }, "bigkeyshuffle": { "type": "checkbox" } diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json index e0cd2bdd..b02dfa6f 100644 --- a/resources/app/gui/randomize/dungeon/widgets.json +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -1,12 +1,17 @@ { "widgets": { - "smallkeyshuffle": { + "key_logic_algorithm": { "type": "selectbox", + "default": "default", "options": [ - "none", - "wild", - "universal" - ] + "default", + "partial", + "strict" + ], + "config": { + "padx": [20,0], + "pady": [0,20] + } }, "dungeondoorshuffle": { "type": "selectbox", @@ -28,7 +33,8 @@ "random" ], "config": { - "width": 45 + "width": 45, + "padx": [20,0] } }, "door_type_mode": { @@ -41,7 +47,8 @@ "chaos" ], "config": { - "width": 45 + "width": 45, + "padx": [20,0] } }, "trap_door_mode": { @@ -54,41 +61,23 @@ "oneway" ], "config": { - "width": 30 + "width": 30, + "padx": [20,0] } }, - "key_logic_algorithm": { - "type": "selectbox", - "default": "default", - "options": [ - "default", - "partial", - "strict" - ] - }, - "decoupledoors": { "type": "checkbox" }, - "keydropshuffle": { "type": "checkbox" }, - "pottery": { - "type": "selectbox", - "default": "none", - "options": [ - "none", - "keys", - "cave", - "cavekeys", - "reduced", - "clustered", - "nonempty", - "dungeon", - "lottery" - ], + "decoupledoors": { + "type": "checkbox", "config": { - "width": 35 + "padx": [20,0] + } + }, + "door_self_loops": { + "type": "checkbox", + "config": { + "padx": [20,0], + "pady": [0,20] } }, - "colorizepots": { "type": "checkbox" }, - "dropshuffle": { "type": "checkbox" }, - "potshuffle": { "type": "checkbox" }, "experimental": { "type": "checkbox" }, "dungeon_counters": { "type": "selectbox", diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index 41413ebd..c325ea26 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -1,6 +1,40 @@ { "widgets": { - "openpyramid": { + "entranceshuffle": { + "type": "selectbox", + "options": [ + "vanilla", + "simple", + "restricted", + "full", + "lite", + "lean", + "swapped", + "crossed", + "insanity", + "dungeonsfull", + "dungeonssimple" + ] + }, + "shuffleganon": { + "type": "checkbox", + "config": { + "padx": [20,0] + } + }, + "shufflelinks": { + "type": "checkbox", + "config": { + "padx": [20,0] + } + }, + "shuffletavern": { + "type": "checkbox", + "config": { + "padx": [20,0] + } + }, + "openpyramid": { "type": "selectbox", "options": [ "auto", @@ -8,20 +42,10 @@ "no" ], "config": { - "width": 10 + "width": 6, + "pady": [20,0] } }, - "take_any": { - "type": "selectbox", - "options": [ - "none", - "random", - "fixed" - ] - }, - "shuffleganon": { "type": "checkbox" }, - "shufflelinks": { "type": "checkbox" }, - "shuffletavern": { "type": "checkbox" }, "overworld_map": { "type": "selectbox", "options": [ @@ -32,21 +56,6 @@ "config": { "width": 45 } - }, - "entranceshuffle": { - "type": "selectbox", - "options": [ - "vanilla", - "simple", - "restricted", - "full", - "lite", - "lean", - "crossed", - "insanity", - "dungeonsfull", - "dungeonssimple" - ] } } } diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index b1ee4c84..6e377027 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,9 +4,6 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, - "print_custom_yaml": { "type": "checkbox" }, - "usestartinventory": { "type": "checkbox" }, - "usecustompool": { "type": "checkbox" }, - "race": { "type": "checkbox" } + "print_custom_yaml": { "type": "checkbox" } } } diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 113ab773..b6c8ec67 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -1,12 +1,8 @@ { "checkboxes": { - "retro": { "type": "checkbox" }, - "bombbag": { "type": "checkbox" }, - "shopsanity": { "type": "checkbox" }, - "hints": { - "type": "checkbox" - }, - "pseudoboots": { "type": "checkbox" } + "hints": { "type": "checkbox" }, + "pseudoboots": { "type": "checkbox" }, + "race": { "type": "checkbox" } }, "leftItemFrame": { "worldstate": { @@ -15,8 +11,7 @@ "options": [ "standard", "open", - "inverted", - "retro" + "inverted" ] }, "logiclevel": { @@ -75,20 +70,44 @@ "somaria", "byrna" ] - }, - "weapons": { - "type": "selectbox", - "options": [ - "random", - "assured", - "swordless", - "bombs", - "vanilla" - ] } }, "rightItemFrame": { - "itempool": { + "retro": { + "type": "button", + "config": { + "command": "retro" + } + }, + "sortingalgo": { + "type": "selectbox", + "default": "balanced", + "options": [ + "balanced", + "vanilla_fill", + "major_only", + "dungeon_only", + "district" + ] + }, + "accessibility": { + "type": "selectbox", + "options": [ + "items", + "locations", + "none" + ] + }, + "restrict_boss_items": { + "type": "selectbox", + "default": "none", + "options": [ + "none", + "mapcompass", + "dungeon" + ] + }, + "itemfunction": { "type": "selectbox", "options": [ "normal", @@ -96,7 +115,80 @@ "expert" ] }, - "itemfunction": { + "timer": { + "type": "selectbox", + "options": [ + "none", + "display", + "timed", + "timed-ohko", + "ohko", + "timed-countdown" + ] + } + }, + "leftPoolHeader": { + "shopsanity": { + "type": "checkbox" + }, + "bonk_drops": { + "type": "checkbox", + "default": false + } + }, + "leftPoolFrame": { + "pottery": { + "type": "selectbox", + "default": "none", + "options": [ + "none", + "keys", + "cave", + "cavekeys", + "reduced", + "clustered", + "nonempty", + "dungeon", + "lottery" + ], + "config": { + "width": 35 + } + }, + "colorizepots": { + "type": "checkbox", + "config": { + "padx": [50,0] + } + }, + "potshuffle": { + "type": "checkbox", + "config": { + "padx": [50,0] + } + }, + "dropshuffle": { + "type": "checkbox" + } + }, + "leftPoolFrame2": { + "keydropshuffle": { + "type": "button", + "config": { + "command": "keydropshuffle" + } + }, + "take_any": { + "type": "selectbox", + "options": [ + "none", + "random", + "fixed" + ] + } + }, + "rightPoolFrame": { + "itempool": { "type": "selectbox", "options": [ "normal", @@ -120,50 +212,17 @@ "retro_silvers" ] }, - "timer": { - "type": "selectbox", - "options": [ - "none", - "display", - "timed", - "timed-ohko", - "ohko", - "timed-countdown" - ] - }, - "accessibility": { - "type": "selectbox", - "options": [ - "items", - "locations", - "none" - ] - }, - "sortingalgo": { - "type": "selectbox", - "default": "balanced", - "options": [ - "balanced", - "vanilla_fill", - "major_only", - "dungeon_only", - "district" - ] - }, - "restrict_boss_items": { - "type": "selectbox", - "default": "none", - "options": [ - "none", - "mapcompass", - "dungeon" - ] - }, "beemizer": { "type": "selectbox", "options": [ "0", "1", "2", "3", "4" ] + }, + "bombbag": { + "type": "checkbox", + "config": { + "padx": [64,0] + } } } } diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index d349cfc0..c9438deb 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -1,10 +1,5 @@ { - "topOverworldFrame": { - "bonk_drops": { - "type": "checkbox", - "default": false - } - }, + "topOverworldFrame": {}, "leftOverworldFrame": { "overworldshuffle": { "type": "selectbox", @@ -28,11 +23,17 @@ }, "mixed": { "type": "checkbox", - "default": false + "default": false, + "config": { + "padx": [79,0] + } }, "whirlpool": { "type": "checkbox", - "default": false + "default": false, + "config": { + "padx": [79,0] + } }, "overworldflute": { "type": "selectbox", @@ -41,17 +42,26 @@ "vanilla", "balanced", "random" - ] + ], + "config": { + "pady": [20,0] + } } }, "rightOverworldFrame": { "terrain": { "type": "checkbox", - "default": false + "default": false, + "config": { + "pady": [3,0] + } }, "keepsimilar": { "type": "checkbox", - "default": false + "default": false, + "config": { + "pady": [6,0] + } } } } \ No newline at end of file diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index fd15253f..7eb8a31c 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -2,12 +2,15 @@ import os import urllib.request import urllib.parse import yaml +from typing import Any from yaml.representer import Representer +from Utils import HexInt, hex_representer from collections import defaultdict from pathlib import Path import RaceRandom as random from BaseClasses import LocationType, DoorType +from OverworldShuffle import default_flute_connections, flute_data from source.tools.MysteryUtils import roll_settings, get_weights @@ -46,8 +49,8 @@ class CustomSettings(object): return meta['players'] def adjust_args(self, args): - def get_setting(value, default): - if value: + def get_setting(value: Any, default): + if value or value == 0: if isinstance(value, dict): return random.choices(list(value.keys()), list(value.values()), k=1)[0] else: @@ -62,6 +65,7 @@ class CustomSettings(object): args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom) args.names = get_setting(meta['names'], args.names) args.race = get_setting(meta['race'], args.race) + args.notes = get_setting(meta['user_notes'], args.notes) self.player_range = range(1, args.multi + 1) if 'settings' in self.file_source: for p in self.player_range: @@ -72,6 +76,14 @@ class CustomSettings(object): args.mystery = True else: settings = defaultdict(lambda: None, player_setting) + args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p]) + args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p]) + args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p]) + args.ow_keepsimilar[p] = get_setting(settings['ow_keepsimilar'], args.ow_keepsimilar[p]) + args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p]) + args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p]) + args.ow_fluteshuffle[p] = get_setting(settings['ow_fluteshuffle'], args.ow_fluteshuffle[p]) + args.bonk_drops[p] = get_setting(settings['bonk_drops'], args.bonk_drops[p]) args.shuffle[p] = get_setting(settings['shuffle'], args.shuffle[p]) args.door_shuffle[p] = get_setting(settings['door_shuffle'], args.door_shuffle[p]) args.logic[p] = get_setting(settings['logic'], args.logic[p]) @@ -112,10 +124,12 @@ class CustomSettings(object): args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p]) args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p]) args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p]) + args.door_self_loops[p] = get_setting(settings['door_self_loops'], args.door_self_loops[p]) args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p]) args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p]) args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p]) 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.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p]) args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p]) @@ -129,8 +143,8 @@ class CustomSettings(object): args.mapshuffle[p] = True args.compassshuffle[p] = True - args.shufflebosses[p] = get_setting(settings['shufflebosses'], args.shufflebosses[p]) - args.shuffleenemies[p] = get_setting(settings['shuffleenemies'], args.shuffleenemies[p]) + args.shufflebosses[p] = get_setting(settings['boss_shuffle'], args.shufflebosses[p]) + args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p]) args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p]) args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p]) args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p]) @@ -142,6 +156,12 @@ class CustomSettings(object): args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p]) args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p]) args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p]) + args.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p]) + args.triforce_goal_max[p] = get_setting(settings['triforce_goal_max'], args.triforce_goal_max[p]) + args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p]) + args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p]) + args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p]) + args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p]) args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p]) # mystery usage @@ -176,6 +196,16 @@ class CustomSettings(object): return self.file_source['advanced_placements'] return None + def get_owtileflips(self): + if 'ow-tileflips' in self.file_source: + return self.file_source['ow-tileflips'] + return None + + def get_owflutespots(self): + if 'ow-flutespots' in self.file_source: + return self.file_source['ow-flutespots'] + return None + def get_entrances(self): if 'entrances' in self.file_source: return self.file_source['entrances'] @@ -206,17 +236,26 @@ class CustomSettings(object): return self.file_source['drops'] return None - def create_from_world(self, world, race): + def create_from_world(self, world, settings): self.player_range = range(1, world.players + 1) settings_dict, meta_dict = {}, {} self.world_rep['meta'] = meta_dict meta_dict['players'] = world.players meta_dict['algorithm'] = world.algorithm meta_dict['seed'] = world.seed - meta_dict['race'] = race + meta_dict['race'] = settings.race + meta_dict['user_notes'] = settings.notes self.world_rep['settings'] = settings_dict for p in self.player_range: settings_dict[p] = {} + settings_dict[p]['ow_shuffle'] = world.owShuffle[p] + settings_dict[p]['ow_terrain'] = world.owTerrain[p] + settings_dict[p]['ow_crossed'] = world.owCrossed[p] + settings_dict[p]['ow_keepsimilar'] = world.owKeepSimilar[p] + settings_dict[p]['ow_mixed'] = world.owMixed[p] + settings_dict[p]['ow_whirlpool'] = world.owWhirlpoolShuffle[p] + settings_dict[p]['ow_fluteshuffle'] = world.owFluteShuffle[p] + settings_dict[p]['bonk_drops'] = world.shuffle_bonk_drops[p] settings_dict[p]['shuffle'] = world.shuffle[p] settings_dict[p]['door_shuffle'] = world.doorShuffle[p] settings_dict[p]['intensity'] = world.intensity[p] @@ -224,6 +263,7 @@ class CustomSettings(object): settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p] settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p] settings_dict[p]['decoupledoors'] = world.decoupledoors[p] + settings_dict[p]['door_self_loops'] = world.door_self_loops[p] settings_dict[p]['logic'] = world.logic[p] settings_dict[p]['mode'] = world.mode[p] settings_dict[p]['swords'] = world.swords[p] @@ -244,6 +284,7 @@ class CustomSettings(object): settings_dict[p]['crystals_gt'] = world.crystals_gt_orig[p] settings_dict[p]['crystals_ganon'] = world.crystals_ganon_orig[p] 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]['bigkeyshuffle'] = world.bigkeyshuffle[p] settings_dict[p]['keyshuffle'] = world.keyshuffle[p] @@ -313,6 +354,23 @@ class CustomSettings(object): else: placements[location.player][location.name] = location.item.name + def record_overworld(self, world): + self.world_rep['ow-tileflips'] = flips = {} + for p in self.player_range: + if p in world.owswaps and len(world.owswaps[p][0]) > 0: + flips[p] = {} + flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) + flips[p]['force_flip'].sort() + flips[p]['undefined_chance'] = 0 + self.world_rep['ow-flutespots'] = flute = {} + for p in self.player_range: + flute[p] = {} + if p in world.owflutespots: + flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p])) + else: + flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections)) + flute[p]['forbid'] = [] + def record_entrances(self, world): self.world_rep['entrances'] = entrances = {} world.custom_entrances = {} @@ -375,6 +433,7 @@ class CustomSettings(object): def write_to_file(self, destination): yaml.add_representer(defaultdict, Representer.represent_dict) + yaml.add_representer(HexInt, hex_representer) with open(destination, 'w') as file: yaml.dump(self.world_rep, file) diff --git a/source/classes/constants.py b/source/classes/constants.py index ef71533b..45ec8987 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -56,10 +56,9 @@ SETTINGSTOPROCESS = { "randomizer": { "item": { "hints": "hints", - "retro": "retro", - "bombbag": "bombbag", - "shopsanity": "shopsanity", "pseudoboots": "pseudoboots", + "race": "race", + "worldstate": "mode", "logiclevel": "logic", "goal": "goal", @@ -67,15 +66,28 @@ SETTINGSTOPROCESS = { "crystals_ganon": "crystals_ganon", "ganon_item": "ganon_item", "weapons": "swords", - "itempool": "difficulty", + + "retro": "retro", + "sortingalgo": "algorithm", + "accessibility": "accessibility", + "restrict_boss_items": "restrict_boss_items", "itemfunction": "item_functionality", + "timer": "timer", + + "shopsanity": "shopsanity", + "bonk_drops": "bonk_drops", + "pottery": "pottery", + "colorizepots": "colorizepots", + "potshuffle": "shufflepots", + "dropshuffle": "dropshuffle", + "keydropshuffle": "keydropshuffle", + "take_any": "take_any", + + "itempool": "difficulty", "flute_mode": "flute_mode", "bow_mode": "bow_mode", - "timer": "timer", - "accessibility": "accessibility", - "sortingalgo": "algorithm", "beemizer": "beemizer", - "restrict_boss_items": "restrict_boss_items" + "bombbag": "bombbag" }, "overworld": { "overworldshuffle": "ow_shuffle", @@ -84,17 +96,32 @@ SETTINGSTOPROCESS = { "keepsimilar": "ow_keepsimilar", "mixed": "ow_mixed", "whirlpool": "ow_whirlpool", - "bonk_drops": "bonk_drops", "overworldflute": "ow_fluteshuffle" }, "entrance": { - "openpyramid": "openpyramid", + "entranceshuffle": "shuffle", "shuffleganon": "shuffleganon", "shufflelinks": "shufflelinks", "shuffletavern": "shuffletavern", - "entranceshuffle": "shuffle", + "openpyramid": "openpyramid", "overworld_map": "overworld_map", - "take_any": "take_any", + }, + "dungeon": { + "smallkeyshuffle": "keyshuffle", + "mapshuffle": "mapshuffle", + "compassshuffle": "compassshuffle", + "bigkeyshuffle": "bigkeyshuffle", + "key_logic_algorithm": "key_logic_algorithm", + "dungeondoorshuffle": "door_shuffle", + "dungeonintensity": "intensity", + "door_type_mode": "door_type_mode", + "trap_door_mode": "trap_door_mode", + "decoupledoors": "decoupledoors", + "door_self_loops": "door_self_loops", + "experimental": "experimental", + "dungeon_counters": "dungeon_counters", + "mixed_travel": "mixed_travel", + "standardize_palettes": "standardize_palettes", }, "enemizer": { "enemyshuffle": "shuffleenemies", @@ -102,27 +129,6 @@ SETTINGSTOPROCESS = { "enemydamage": "enemy_damage", "enemyhealth": "enemy_health" }, - "dungeon": { - "mapshuffle": "mapshuffle", - "compassshuffle": "compassshuffle", - "smallkeyshuffle": "keyshuffle", - "bigkeyshuffle": "bigkeyshuffle", - "dungeondoorshuffle": "door_shuffle", - "dungeonintensity": "intensity", - "door_type_mode": "door_type_mode", - "trap_door_mode": "trap_door_mode", - "key_logic_algorithm": "key_logic_algorithm", - "decoupledoors": "decoupledoors", - "keydropshuffle": "keydropshuffle", - "dropshuffle": "dropshuffle", - "pottery": "pottery", - "colorizepots": "colorizepots", - "potshuffle": "shufflepots", - "experimental": "experimental", - "dungeon_counters": "dungeon_counters", - "mixed_travel": "mixed_travel", - "standardize_palettes": "standardize_palettes" - }, "gameoptions": { "nobgm": "disablemusic", "quickswap": "quickswap", @@ -142,12 +148,15 @@ SETTINGSTOPROCESS = { "createrom": "create_rom", "calcplaythrough": "calc_playthrough", "print_custom_yaml": "print_custom_yaml", - "usestartinventory": "usestartinventory", - "usecustompool": "custom", - "race": "race", "saveonexit": "saveonexit" } }, + "startinventory": { + "usestartinventory": "usestartinventory" + }, + "custom": { + "usecustompool": "custom" + }, "bottom": { "content": { "names": "names", diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 1eac3fb1..504fc03b 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -22,7 +22,7 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe queue = collections.deque(proposed_map.items()) while len(queue) > 0: a, b = queue.popleft() - if world.decoupledoors[player]: + if a == b or world.decoupledoors[player]: connect_doors_one_way(a, b) else: connect_doors(a, b) @@ -128,14 +128,14 @@ def create_random_proposal(doors_to_connect, world, player): next_hook = random.choice(hooks_left) primary_door = random.choice(primary_bucket[next_hook]) opp_hook, secondary_door = type_map[next_hook], None - while (secondary_door is None or secondary_door == primary_door + while (secondary_door is None or (secondary_door == primary_door and not world.door_self_loops[player]) or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook], primary_door, secondary_door, world, player)): secondary_door = random.choice(secondary_bucket[opp_hook]) proposal[primary_door] = secondary_door primary_bucket[next_hook].remove(primary_door) secondary_bucket[opp_hook].remove(secondary_door) - if not world.decoupledoors[player]: + if primary_door != secondary_door and not world.decoupledoors[player]: proposal[secondary_door] = primary_door primary_bucket[opp_hook].remove(secondary_door) secondary_bucket[next_hook].remove(primary_door) @@ -200,11 +200,19 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se unvisted_bucket[opp_hook].sort(key=lambda d: d.name) new_door = random.choice(unvisted_bucket[opp_hook]) old_target = proposed_map[attempt] - proposed_map[attempt] = new_door if not world.decoupledoors[player]: old_attempt = proposed_map[new_door] else: old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door) + # ensure nothing gets messed up when something loops with itself + if attempt == old_target and old_attempt == new_door: + old_attempt = new_door + old_target = attempt + elif attempt == old_target: + old_target = old_attempt + elif old_attempt == new_door: + old_attempt = old_target + proposed_map[attempt] = new_door proposed_map[old_attempt] = old_target if not world.decoupledoors[player]: proposed_map[old_target] = old_attempt diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 27a22217..beb77a1f 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -203,13 +203,20 @@ def create_guiargs(parent): # Cycle through each page for mainpage in options: + subpage = None + _, v = next(iter(options[mainpage].items())) + if isinstance(v, str): + subpage = "" # Cycle through each subpage (in case of Item Randomizer) - for subpage in options[mainpage]: + for subpage in (options[mainpage] if subpage is None else [subpage]): # Cycle through each widget - for widget in options[mainpage][subpage]: + for widget in (options[mainpage][subpage] if subpage != "" else options[mainpage]): # Get the value and set it - arg = options[mainpage][subpage][widget] - setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get()) + arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget] + page = parent.pages[mainpage].pages[subpage] if subpage != "" else parent.pages[mainpage] + pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets + if hasattr(pagewidgets[widget], 'storageVar'): + setattr(guiargs, arg, pagewidgets[widget].storageVar.get()) # Get EnemizerCLI setting guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].widgets["enemizercli"].storageVar.get() @@ -226,7 +233,7 @@ def create_guiargs(parent): guiargs.customizer = customizer_value # Get if we're using the Custom Item Pool - guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get()) + guiargs.custom = bool(parent.pages["custom"].content.customWidgets["usecustompool"].storageVar.get()) # Get Seed ID guiargs.seed = None @@ -280,17 +287,4 @@ def create_guiargs(parent): guiargs = update_deprecated_args(guiargs) - # Key drop shuffle stuff - if guiargs.keydropshuffle: - guiargs.dropshuffle = 1 - guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery - - if guiargs.retro or guiargs.mode == 'retro': - if guiargs.bow_mode == 'progressive': - guiargs.bow_mode = 'retro' - elif guiargs.bow_mode == 'silvers': - guiargs.bow_mode = 'retro_silvers' - guiargs.take_any = 'random' if guiargs.take_any == 'none' else guiargs.take_any - guiargs.keyshuffle = 'universal' - return guiargs diff --git a/source/gui/custom/overview.py b/source/gui/custom/overview.py index c51c35be..b62bff71 100644 --- a/source/gui/custom/overview.py +++ b/source/gui/custom/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y +from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y import source.gui.widgets as widgets import json import os @@ -11,10 +11,10 @@ def custom_page(top,parent): # Create uniform list columns def create_list_frame(parent, framename): - parent.frames[framename] = Frame(parent) - parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) - parent.frames[framename].thisRow = 0 - parent.frames[framename].thisCol = 0 + self.frames[framename] = Frame(parent) + self.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) + self.frames[framename].thisRow = 0 + self.frames[framename].thisCol = 0 # Create a vertical rule to help with splitting columns visually def create_vertical_rule(num=1): @@ -34,6 +34,8 @@ def custom_page(top,parent): # Custom Item Pool option sections self.frames = {} + self.frames["customHeader"] = Frame(self) + self.frames["customHeader"].pack(side=TOP, anchor=W) # Create 5 columns with 2 vertical rules in between each create_list_frame(self, "itemList1") create_vertical_rule(2) @@ -50,9 +52,14 @@ def custom_page(top,parent): with open(os.path.join("resources", "app", "gui", "custom", "overview", "widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for framename,theseWidgets in myDict.items(): - dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) - for key in dictWidgets: - self.customWidgets[key] = dictWidgets[key] + if framename in self.frames: + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.customWidgets[key] = dictWidgets[key] + if framename == "customHeader": + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) + self.customWidgets[key].pack(packAttrs) # Load Custom Item Pool settings from settings file for key in CONST.CUSTOMITEMS: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index 0130f8ad..aec14a72 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -23,34 +23,44 @@ def loadcliargs(gui, args, settings=None): # Cycle through each page for mainpage in options: + subpage = None + _, v = next(iter(options[mainpage].items())) + if isinstance(v, str): + subpage = "" # Cycle through each subpage (in case of Item Randomizer) - for subpage in options[mainpage]: + for subpage in (options[mainpage] if subpage is None else [subpage]): # Cycle through each widget - for widget in options[mainpage][subpage]: - if widget in gui.pages[mainpage].pages[subpage].widgets: + for widget in (options[mainpage][subpage] if subpage != "" else options[mainpage]): + page = gui.pages[mainpage].pages[subpage] if subpage != "" else gui.pages[mainpage] + pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets + if widget in pagewidgets: thisType = "" # Get the value and set it - arg = options[mainpage][subpage][widget] + arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget] if args[arg] == None: args[arg] = "" - label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) - if hasattr(gui.pages[mainpage].pages[subpage].widgets[widget],"type"): - thisType = gui.pages[mainpage].pages[subpage].widgets[widget].type + label_ref = mainpage + ('.' + subpage if subpage != "" else '') + '.' + widget + label = fish.translate("gui","gui", label_ref) + if hasattr(pagewidgets[widget],"type"): + thisType = pagewidgets[widget].type if thisType == "checkbox": - gui.pages[mainpage].pages[subpage].widgets[widget].checkbox.configure(text=label) + pagewidgets[widget].checkbox.configure(text=label) elif thisType == "selectbox": - theseOptions = gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options - gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label) + theseOptions = pagewidgets[widget].selectbox.options + pagewidgets[widget].label.configure(text=label) i = 0 for value in theseOptions["values"]: - gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + '.' + str(value)) + pagewidgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui", label_ref + '.' + str(value)) i += 1 for i in range(0, len(theseOptions["values"])): - gui.pages[mainpage].pages[subpage].widgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i]) - gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options = theseOptions + pagewidgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i]) + pagewidgets[widget].selectbox.options = theseOptions elif thisType == "spinbox": - gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label) - gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg]) + pagewidgets[widget].label.configure(text=label) + elif thisType == 'button': + pagewidgets[widget].button.configure(text=label) + if hasattr(pagewidgets[widget], 'storageVar'): + pagewidgets[widget].storageVar.set(args[arg]) # If we're on the Game Options page and it's not about Hints if subpage == "gameoptions" and widget not in ["hints", "collection_rate"]: # Check if we've got settings diff --git a/source/gui/randomize/dungeon.py b/source/gui/randomize/dungeon.py index 01985982..7400dbe4 100644 --- a/source/gui/randomize/dungeon.py +++ b/source/gui/randomize/dungeon.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT +from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT, TOP import source.gui.widgets as widgets import json import os @@ -16,8 +16,8 @@ def dungeon_page(parent): self.frames["keysanity"].pack(anchor=W) ## Dungeon Item Shuffle - mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ") - mscbLabel.pack(side=LEFT) + mscbLabel = Label(self.frames["keysanity"], text="Dungeon Items: ") + mscbLabel.pack(side=TOP, anchor=W) # Load Dungeon Shuffle option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes @@ -28,7 +28,9 @@ def dungeon_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["keysanity"]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - self.widgets[key].pack(side=LEFT) + packAttrs = {"side":LEFT} + packAttrs = widgets.add_padding_from_config(packAttrs, myDict[key]) + self.widgets[key].pack(packAttrs) # These get split left & right self.frames["widgets"] = Frame(self) @@ -39,6 +41,8 @@ def dungeon_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - self.widgets[key].pack(anchor=W) + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, myDict[key]) + self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/randomize/entrando.py b/source/gui/randomize/entrando.py index bfc911b4..ba2bc365 100644 --- a/source/gui/randomize/entrando.py +++ b/source/gui/randomize/entrando.py @@ -26,9 +26,8 @@ def entrando_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - packAttrs = {"anchor":E} - if self.widgets[key].type == "checkbox" or key in ["openpyramid", "take_any"]: - packAttrs["anchor"] = W + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/randomize/item.py b/source/gui/randomize/item.py index 81c957ce..63a96997 100644 --- a/source/gui/randomize/item.py +++ b/source/gui/randomize/item.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, E, W, LEFT, RIGHT, Label +from tkinter import ttk, font, Frame, E, W, NW, TOP, LEFT, RIGHT, Y, Label import source.gui.widgets as widgets import json import os @@ -17,13 +17,42 @@ def item_page(parent): self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) - various_options = Label(self.frames["checkboxes"], text="") + various_options = Label(self.frames["checkboxes"], text="Options: ") various_options.pack(side=LEFT) - self.frames["leftItemFrame"] = Frame(self) - self.frames["rightItemFrame"] = Frame(self) + self.frames["mainFrame"] = Frame(self) + self.frames["mainFrame"].pack(side=TOP, pady=(20,0)) + + self.frames["poolFrame"] = Frame(self) + self.frames["poolFrame"].pack(fill=Y) + + self.frames["leftItemFrame"] = Frame(self.frames["mainFrame"]) self.frames["leftItemFrame"].pack(side=LEFT) + self.frames["rightItemFrame"] = Frame(self.frames["mainFrame"]) self.frames["rightItemFrame"].pack(side=RIGHT) + + self.frames["leftPoolContainer"] = Frame(self.frames["poolFrame"]) + self.frames["leftPoolContainer"].pack(side=LEFT, padx=(0,20)) + + base_font = font.nametofont('TkTextFont').actual() + underline_font = f'"{base_font["family"]}" {base_font["size"]} underline' + various_options = Label(self.frames["leftPoolContainer"], text="Pool Expansions", font=underline_font) + various_options.pack(side=TOP, pady=(20,0)) + + self.frames["leftPoolHeader"] = Frame(self.frames["leftPoolContainer"]) + self.frames["leftPoolHeader"].pack(side=TOP, anchor=W) + + self.frames["leftPoolFrame"] = Frame(self.frames["leftPoolContainer"]) + self.frames["leftPoolFrame"].pack(side=TOP, fill=Y) + + self.frames["leftPoolFrame2"] = Frame(self.frames["leftPoolContainer"]) + self.frames["leftPoolFrame2"].pack(side=LEFT, fill=Y) + + self.frames["rightPoolFrame"] = Frame(self.frames["poolFrame"]) + self.frames["rightPoolFrame"].pack(side=RIGHT) + + various_options = Label(self.frames["rightPoolFrame"], text="Pool Modifications", font=underline_font) + various_options.pack(side=TOP, pady=(20,0)) # Load Item Randomizer option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes @@ -36,8 +65,17 @@ def item_page(parent): for key in dictWidgets: self.widgets[key] = dictWidgets[key] packAttrs = {"anchor":E} - if self.widgets[key].type == "checkbox": + if key == "retro": + packAttrs["side"] = RIGHT + if self.widgets[key].type == "checkbox" or framename.startswith("leftPoolFrame"): + packAttrs["anchor"] = W + if framename == "checkboxes": packAttrs["side"] = LEFT + packAttrs["padx"] = (10, 0) + elif framename == "leftPoolHeader": + packAttrs["side"] = LEFT + packAttrs["padx"] = (0, 20) + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/randomize/overworld.py b/source/gui/randomize/overworld.py index 190a750a..54120417 100644 --- a/source/gui/randomize/overworld.py +++ b/source/gui/randomize/overworld.py @@ -15,33 +15,24 @@ def overworld_page(parent): # Load Overworld Shuffle option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes - self.frames["topOverworldFrame"] = Frame(self) self.frames["leftOverworldFrame"] = Frame(self) self.frames["rightOverworldFrame"] = Frame(self) - self.frames["topOverworldFrame"].pack(side=TOP, anchor=NW) self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y) self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y) - - shuffleLabel = Label(self.frames["topOverworldFrame"], text="Shuffle: ") - shuffleLabel.pack(side=LEFT) with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets: myDict = json.load(overworldWidgets) for framename,theseWidgets in myDict.items(): + if not theseWidgets: + continue dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - packAttrs = {"anchor":E} - if key == "terrain": - packAttrs = {"anchor":W, "pady":(3,0)} - elif key == "keepsimilar": - packAttrs = {"anchor":W, "pady":(6,0)} - elif key == "overworldflute": - packAttrs["pady"] = (20,0) - elif key in ["mixed", "whirlpool"]: - packAttrs = {"anchor":W, "padx":(79,0)} - + packAttrs = {"anchor":W} + if self.widgets[key].type != "checkbox": + packAttrs["anchor"] = E + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) self.widgets[key].pack(packAttrs) return self diff --git a/source/gui/startinventory/overview.py b/source/gui/startinventory/overview.py index fce40e5f..97784521 100644 --- a/source/gui/startinventory/overview.py +++ b/source/gui/startinventory/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y +from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y import source.gui.widgets as widgets import json import os @@ -11,10 +11,10 @@ def startinventory_page(top,parent): # Create uniform list columns def create_list_frame(parent, framename): - parent.frames[framename] = Frame(parent) - parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) - parent.frames[framename].thisRow = 0 - parent.frames[framename].thisCol = 0 + self.frames[framename] = Frame(parent) + self.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) + self.frames[framename].thisRow = 0 + self.frames[framename].thisCol = 0 # Create a vertical rule to help with splitting columns visually def create_vertical_rule(num=1): @@ -34,6 +34,8 @@ def startinventory_page(top,parent): # Starting Inventory option sections self.frames = {} + self.frames["startHeader"] = Frame(self) + self.frames["startHeader"].pack(side=TOP, anchor=W) # Create 5 columns with 2 vertical rules in between each create_list_frame(self,"itemList1") create_vertical_rule(2) @@ -55,9 +57,14 @@ def startinventory_page(top,parent): if key in myDict[thisList]: del myDict[thisList][key] for framename,theseWidgets in myDict.items(): - dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) - for key in dictWidgets: - self.startingWidgets[key] = dictWidgets[key] + if framename in self.frames: + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.startingWidgets[key] = dictWidgets[key] + if framename == "startHeader": + packAttrs = {"anchor":W} + packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key]) + self.startingWidgets[key].pack(packAttrs) # Load Custom Starting Inventory settings from settings file, ignoring ones to be excluded for key in CONST.CUSTOMITEMS: diff --git a/source/gui/widgets.py b/source/gui/widgets.py index aedb573d..67efe0ba 100644 --- a/source/gui/widgets.py +++ b/source/gui/widgets.py @@ -1,4 +1,5 @@ -from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X +from tkinter import messagebox, Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X +from tkinter import Button from source.classes.Empty import Empty # Override Spinbox to include mousewheel support for changing value @@ -16,7 +17,7 @@ class mySpinbox(Spinbox): self.invoke('buttonup') # Make a Checkbutton with a label -def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): +def make_checkbox(self, parent, label, storageVar, manager, managerAttrs, config): self = Frame(parent) self.storageVar = storageVar if managerAttrs is not None and "default" in managerAttrs: @@ -25,7 +26,10 @@ def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): elif managerAttrs["default"] == "false" or managerAttrs["default"] == False: self.storageVar.set(False) del managerAttrs["default"] - self.checkbox = Checkbutton(self, text=label, variable=self.storageVar) + options = {"text":label, "variable":self.storageVar} + if config and "command" in config: + options.update({"command":lambda m=config["command"]: widget_command(self, m)}) + self.checkbox = Checkbutton(self, options) if managerAttrs is not None: self.checkbox.pack(managerAttrs) else: @@ -43,7 +47,11 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt self.labelVar = StringVar() self.storageVar = storageVar - self.selectbox = OptionMenu(self, self.labelVar, *labels) + if config and "command" in config: + self.command = config["command"] + self.selectbox = OptionMenu(self, self.labelVar, *labels, command=lambda m: widget_command(self, self.command)) + else: + self.selectbox = OptionMenu(self, self.labelVar, *labels) self.selectbox.options = {} if isinstance(options,dict): @@ -96,7 +104,7 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt else: self.label.pack(side=LEFT) - self.selectbox.config(width=config['width'] if config and config['width'] else 20) + self.selectbox.config(width=config['width'] if config and 'width' in config else 20) idx = 0 default = self.selectbox.options["values"][idx] if managerAttrs is not None and "default" in managerAttrs: @@ -165,6 +173,22 @@ def make_textbox(self, parent, label, storageVar, manager, managerAttrs): widget.textbox.pack(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None) return widget + +def make_button(self, parent, label, manager, managerAttrs, config): + self = Frame(parent) + if config and "command" in config: + self.command = config["command"] + else: + self.command = lambda: None + + self.button = Button(parent, text=label, command=lambda: widget_command(self, self.command)) + if managerAttrs is not None: + self.button.pack(managerAttrs) + else: + self.button.pack(anchor='w') + return self + + # Make a generic widget def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(), options=None, config=None): @@ -181,7 +205,7 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage if type == "checkbox": if thisStorageVar is None: thisStorageVar = IntVar() - widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs) + widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs, config) elif type == "selectbox": if thisStorageVar is None: thisStorageVar = StringVar() @@ -194,6 +218,8 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage if thisStorageVar is None: thisStorageVar = StringVar() widget = make_textbox(self, parent, label, thisStorageVar, manager, managerAttrs) + elif type == 'button': + widget = make_button(self, parent, label, manager, managerAttrs, config) widget.type = type return widget @@ -221,3 +247,50 @@ def make_widgets_from_dict(self, defns, parent): for key,defn in defns.items(): widgets[key] = make_widget_from_dict(self, defn, parent) return widgets + +# Add padding to widget +def add_padding_from_config(packAttrs, defn): + if "config" in defn: + config = defn["config"] + if 'padx' in config: + packAttrs["padx"] = config['padx'] + if 'pady' in config: + packAttrs["pady"] = config['pady'] + return packAttrs + +# Callback when a widget issues a command +def widget_command(widget, command=""): + root = widget.winfo_toplevel() + text_output = "" + if command == "retro": + temp_widget = root.pages["randomizer"].pages["dungeon"].widgets["smallkeyshuffle"] + text_output += f'\n {temp_widget.label.cget("text")}' + temp_widget.storageVar.set('universal') + + temp_widget = root.pages["randomizer"].pages["item"].widgets["bow_mode"] + text_output += f'\n {temp_widget.label.cget("text")}' + if temp_widget.storageVar.get() == 'progressive': + temp_widget.storageVar.set('retro') + elif temp_widget.storageVar.get() == 'silvers': + temp_widget.storageVar.set('retro_silvers') + + temp_widget = root.pages["randomizer"].pages["item"].widgets["take_any"] + text_output += f'\n {temp_widget.label.cget("text")}' + if temp_widget.storageVar.get() == 'none': + temp_widget.storageVar.set('random') + + messagebox.showinfo('', f'The following settings were changed:{text_output}') + elif command == "keydropshuffle": + temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"] + text_output += f'\n {temp_widget.label.cget("text")}' + if temp_widget.storageVar.get() == 'none': + temp_widget.storageVar.set('keys') + + temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"] + text_output += f'\n {temp_widget.checkbox.cget("text")}' + if temp_widget.storageVar.get() == 0: + temp_widget.storageVar.set(1) + + if text_output: + messagebox.showinfo('', f'The following settings were changed:{text_output}') + diff --git a/source/item/District.py b/source/item/District.py index 3cd58a1f..baf46a82 100644 --- a/source/item/District.py +++ b/source/item/District.py @@ -119,7 +119,7 @@ def resolve_districts(world): if not location.item and location.real: district.locations.add(location.name) for ext in region.exits: - if ext.connected_region is not None and ext.connected_region not in visited: + if ext.connected_region and ext.connected_region not in visited: queue.appendleft(ext.connected_region) elif region.type == RegionType.Dungeon and region.dungeon: district.dungeons.add(region.dungeon.name) @@ -138,10 +138,10 @@ def find_reachable_locations(state, player): return check_set -inaccessible_regions_std = {'Desert Palace Stairs', 'Bumper Cave Ledge', 'Skull Woods Forest (West)', +inaccessible_regions_std = {'Desert Mouth', 'Bumper Cave Ledge', 'Skull Woods Forest (West)', 'Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge', - 'Dark Death Mountain Floating Island'} + 'Death Mountain Floating Island'} -inaccessible_regions_inv = {'Desert Palace Stairs', 'Maze Race Ledge', 'Desert Ledge', - 'Desert Palace Entrance (North) Spot', 'Hyrule Castle Ledge', 'Mountain Entry Ledge'} +inaccessible_regions_inv = {'Desert Mouth', 'Maze Race Ledge', 'Desert Ledge', + 'Desert Ledge Keep', 'Hyrule Castle Ledge', 'Mountain Pass Ledge'} diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 3e00d38a..1bdd8950 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -3,7 +3,7 @@ import logging from collections import defaultdict from source.item.District import resolve_districts -from BaseClasses import PotItem, PotFlags +from BaseClasses import PotItem, PotFlags, LocationType from DoorShuffle import validate_vanilla_reservation from Dungeons import dungeon_table from Items import item_table, ItemFactory @@ -82,8 +82,8 @@ def create_item_pool_config(world): if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]: item = pot_items[pot.item] descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}' - location = f'{pot.room} {descriptor}' - config.static_placement[player][item].append(location) + loc = f'{pot.room} {descriptor}' + config.static_placement[player][item].append(loc) if world.shopsanity[player]: for item, locs in shop_vanilla_mapping.items(): config.static_placement[player][item].extend(locs) @@ -151,9 +151,6 @@ def create_item_pool_config(world): config.item_pool[player] = determine_major_items(world, player) config.location_groups[0].locations = set(groups.locations) config.reserved_locations[player].update(groups.locations) - backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops'] - + mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops']) - config.location_groups[1].locations = set(backup) elif world.algorithm == 'dungeon_only': config.location_groups = [ LocationGroup('Dungeons'), @@ -164,12 +161,13 @@ def create_item_pool_config(world): mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] + mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] + mode_grouping['Pot Keys'] + mode_grouping['Big Key Drops']) + dungeon_set = set(dungeon_set) + for loc in world.get_locations(): + if loc.parent_region.dungeon and loc.type in [LocationType.Pot, LocationType.Drop]: + dungeon_set.add(loc.name) for player in range(1, world.players + 1): config.item_pool[player] = determine_major_items(world, player) config.location_groups[0].locations = set(dungeon_set) - backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major'] - + mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops']) - config.location_groups[1].locations = set(backup) def district_item_pool_config(world): @@ -217,7 +215,7 @@ def district_item_pool_config(world): scale_factors = defaultdict(int) scale_total = 0 for p in range(1, world.players + 1): - ent = 'Agahnims Tower' if world.is_atgt_swapped(player) else 'Ganons Tower' + ent = 'Agahnims Tower' if world.is_atgt_swapped(p) else 'Ganons Tower' dungeon = world.get_entrance(ent, p).connected_region.dungeon if dungeon: scale = world.crystals_needed_for_gt[p] @@ -359,7 +357,7 @@ def determine_major_items(world, player): major_item_set.add('Single Arrow') if world.keyshuffle[player] == 'universal': major_item_set.add('Small Key (Universal)') - if world.goal in ['triforcehunt', 'trinity']: + if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']: major_item_set.add('Triforce Piece') if world.bombbag[player]: major_item_set.add('Bomb Upgrade (+10)') @@ -415,14 +413,11 @@ 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: - restricted = config.location_groups[1].locations - filtered = [l for l in locations if l.name in restricted] - # bias toward certain location in overflow? (thinking about this for major_bias) - return filtered if len(filtered) > 0 else locations + return filtered if world.algorithm == 'district': config = world.item_pool_config - if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]: + if ((isinstance(item_to_place,str) and item_to_place == 'Placeholder') + or 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 and l.player in restricted[l.name]] return filtered if len(filtered) > 0 else locations @@ -816,7 +811,7 @@ trash_items = { 'Nothing': -1, 'Bee Trap': 0, 'Rupee (1)': 1, 'Rupees (5)': 1, 'Small Heart': 1, 'Bee': 1, 'Arrows (5)': 1, 'Chicken': 1, 'Single Bomb': 1, - 'Rupees (20)': 2, 'Small Magic': 2, + 'Rupees (20)': 2, 'Small Magic': 2, 'Good Bee': 2, 'Bombs (3)': 3, 'Arrows (10)': 3, 'Bombs (10)': 3, 'Apples': 3, 'Fairy': 4, 'Big Magic': 4, 'Red Potion': 4, 'Blue Shield': 4, 'Rupees (50)': 4, 'Rupees (100)': 4, 'Rupees (300)': 5, @@ -830,9 +825,10 @@ pot_items = { PotItem.OneRupee: 'Rupee (1)', PotItem.FiveRupees: 'Rupees (5)', PotItem.Heart: 'Small Heart', - PotItem.BigMagic: 'Big Magic', # fast fill + PotItem.BigMagic: 'Big Magic', PotItem.SmallMagic: 'Small Magic', - PotItem.Chicken: 'Chicken' # fast fill + PotItem.Chicken: 'Chicken', + PotItem.Fairy: 'Fairy' } valid_pot_items = {y: x for x, y in pot_items.items()} diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index fe3a2f89..87bcdf1d 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -12,6 +12,7 @@ class EntrancePool(object): self.exits = set() self.inverted = False self.coupled = True + self.swapped = False self.default_map = {} self.one_way_map = {} self.skull_handled = False @@ -62,10 +63,15 @@ def link_entrances_new(world, player): one_way_map = {} one_way_map.update(drop_map) one_way_map.update(single_entrance_map) + if avail_pool.world.is_atgt_swapped(avail_pool.player): + default_map['Ganons Tower'] = 'Agahnims Tower Exit' + default_map['Agahnims Tower'] = 'Ganons Tower Exit' avail_pool.default_map = default_map avail_pool.one_way_map = one_way_map global LW_Entrances, DW_Entrances + LW_Entrances = [] + DW_Entrances = [] for e in [e for e in avail_pool.entrances if e not in drop_map]: region = world.get_entrance(e, player).parent_region if region.type == RegionType.LightWorld: @@ -86,6 +92,7 @@ def link_entrances_new(world, player): if mode not in modes: raise RuntimeError(f'Shuffle mode {mode} is not yet supported') mode_cfg = copy.deepcopy(modes[mode]) + avail_pool.swapped = mode_cfg['undefined'] == 'swap' if avail_pool.is_standard(): do_standard_connections(avail_pool) pool_list = mode_cfg['pools'] if 'pools' in mode_cfg else {} @@ -93,7 +100,10 @@ def link_entrances_new(world, player): special_shuffle = pool['special'] if 'special' in pool else None if special_shuffle == 'drops': holes, targets = find_entrances_and_targets_drops(avail_pool, pool['entrances']) - connect_random(holes, targets, avail_pool) + if avail_pool.swapped: + connect_swapped(holes, targets, avail_pool) + else: + connect_random(holes, targets, avail_pool) elif special_shuffle == 'normal_drops': cross_world = mode_cfg['cross_world'] == 'on' if 'cross_world' in mode_cfg else False keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True @@ -126,7 +136,10 @@ def link_entrances_new(world, player): exits.remove('Skull Woods First Section Exit') connect_random(entrances, exits, avail_pool, True) entrances, exits = [rem_ent], ['Skull Woods First Section Exit'] - connect_random(entrances, exits, avail_pool, True) + if avail_pool.swapped: + connect_swapped(entrances, exits, avail_pool, True) + else: + connect_random(entrances, exits, avail_pool, True) avail_pool.skull_handled = True else: entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) @@ -134,7 +147,7 @@ def link_entrances_new(world, player): undefined_behavior = mode_cfg['undefined'] if undefined_behavior == 'vanilla': do_vanilla_connections(avail_pool) - elif undefined_behavior == 'shuffle': + elif undefined_behavior in ['shuffle', 'swap']: do_main_shuffle(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, mode_cfg) # afterward @@ -171,13 +184,16 @@ 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: + 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)) def do_main_shuffle(entrances, exits, avail, mode_def): - # drops and holes cross_world = mode_def['cross_world'] == 'on' if 'cross_world' in mode_def else False - keep_together = mode_def['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_def else True avail.coupled = mode_def['decoupled'] != 'on' if 'decoupled' in mode_def else True + # drops and holes + keep_together = mode_def['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_def else True do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_together) if not avail.coupled: @@ -193,6 +209,10 @@ def do_main_shuffle(entrances, exits, avail, mode_def): if not avail.coupled: avail.decoupled_entrances.remove('Agahnims Tower') avail.decoupled_exits.remove('Ganons Tower Exit') + if avail.swapped: + connect_swap('Agahnims Tower', 'Ganons Tower Exit', avail) + entrances.remove('Ganons Tower') + exits.remove('Agahnims Tower Exit') elif 'Ganons Tower' in entrances: connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail) entrances.remove('Ganons Tower') @@ -245,12 +265,19 @@ def do_main_shuffle(entrances, exits, avail, mode_def): else: # cross world mandantory entrance_list = list(entrances) + if avail.swapped: + forbidden = [e for e in Forbidden_Swap_Entrances if e in entrance_list] + entrance_list = [e for e in entrance_list if e not in forbidden] must_exit, multi_exit_caves = figure_out_must_exits_cross_world(entrances, exits, avail) do_mandatory_connections(avail, entrance_list, multi_exit_caves, must_exit) rem_entrances.update(entrance_list) + if avail.swapped: + rem_entrances.update(forbidden) rem_exits.update([x for item in multi_exit_caves for x in item if x in avail.exits]) rem_exits.update(exits) + if avail.swapped: + rem_exits = [x for x in rem_exits if x in avail.exits] # old man cave do_old_man_cave_exit(rem_entrances, rem_exits, avail, cross_world) @@ -265,16 +292,26 @@ def do_main_shuffle(entrances, exits, avail, mode_def): bomb_shop_options = [x for x in rem_entrances] if avail.world.is_tile_swapped(0x03, avail.player): bomb_shop_options = [x for x in bomb_shop_options if x not in ['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']] + if avail.swapped and len(bomb_shop_options) > 1: + bomb_shop_options = [x for x in bomb_shop_options if x != 'Big Bomb Shop'] + bomb_shop_choice = random.choice(bomb_shop_options) connect_entrance(bomb_shop_choice, bomb_shop, avail) rem_entrances.remove(bomb_shop_choice) + if avail.swapped and bomb_shop_choice != 'Big Bomb Shop': + swap_ent, swap_ext = connect_swap(bomb_shop_choice, bomb_shop, avail) + rem_exits.remove(swap_ext) + rem_entrances.remove(swap_ent) if not avail.coupled: avail.decoupled_exits.remove(bomb_shop) rem_exits.remove(bomb_shop) def bonk_fairy_exception(x): # (Bonk Fairy not eligible in standard) return not avail.is_standard() or x != 'Bonk Fairy (Light)' + + # old man S&Q cave if not cross_world: + #TODO: Add Swapped ER support for this # OM Cave entrance in lw/dw if cross_world off if 'Old Man Cave Exit (West)' in rem_exits: world_limiter = DW_Entrances if avail.inverted else LW_Entrances @@ -325,12 +362,17 @@ def do_main_shuffle(entrances, exits, avail, mode_def): rem_entrances = list(unused_entrances) rem_entrances.sort() rem_exits = list(rem_exits if avail.coupled else avail.decoupled_exits) + if avail.swapped: + rem_exits = [x for x in rem_exits if x in avail.exits] rem_exits.sort() random.shuffle(rem_entrances) random.shuffle(rem_exits) placing = min(len(rem_entrances), len(rem_exits)) - for door, target in zip(rem_entrances, rem_exits): - connect_entrance(door, target, avail) + if avail.swapped: + connect_swapped(rem_entrances, rem_exits, avail) + else: + for door, target in zip(rem_entrances, rem_exits): + connect_entrance(door, target, avail) rem_entrances[:] = rem_entrances[placing:] rem_exits[:] = rem_exits[placing:] if rem_entrances or rem_exits: @@ -346,11 +388,16 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world): region_name = 'West Dark Death Mountain (Top)' om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True)) om_cave_options = [e for e in om_cave_options if e in entrances and e != 'Old Man House (Bottom)'] + if avail.swapped: + om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances] assert len(om_cave_options), 'No available entrances left to place Old Man Cave' random.shuffle(om_cave_options) om_cave_choice = None while not om_cave_choice: - om_cave_choice = om_cave_options.pop() + if not len(om_cave_options) and 'Old Man Cave (East)' in entrances: + om_cave_choice = 'Old Man Cave (East)' + else: + om_cave_choice = om_cave_options.pop() choice_region = avail.world.get_entrance(om_cave_choice, avail.player).parent_region.name if 'West Death Mountain (Bottom)' not in build_accessible_region_list(avail.world, choice_region, avail.player, True, True): om_cave_choice = None @@ -360,6 +407,10 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world): else: connect_two_way(om_cave_choice, 'Old Man Cave Exit (East)', avail) entrances.remove(om_cave_choice) + if avail.swapped and om_cave_choice != 'Old Man Cave (East)': + swap_ent, swap_ext = connect_swap(om_cave_choice, 'Old Man Cave Exit (East)', avail) + entrances.remove(swap_ent) + exits.remove(swap_ext) exits.remove('Old Man Cave Exit (East)') @@ -384,10 +435,19 @@ def do_blacksmith(entrances, exits, avail): blacksmith_options = list(OrderedDict.fromkeys(blacksmith_options + list(get_accessible_entrances(sanc_region.name, avail, assumed_inventory, False, True, True)))) else: logging.getLogger('').warning('Blacksmith is unable to use Sanctuary S&Q as initial accessibility because Sanctuary Exit has not been placed yet') + + if avail.swapped: + blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances] blacksmith_options = [x for x in blacksmith_options if x in entrances] + + assert len(blacksmith_options), 'No available entrances left to place Blacksmith' blacksmith_choice = random.choice(blacksmith_options) connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail) entrances.remove(blacksmith_choice) + if avail.swapped and blacksmith_choice != 'Blacksmiths Hut': + swap_ent, swap_ext = connect_swap(blacksmith_choice, 'Blacksmiths Hut', avail) + entrances.remove(swap_ent) + exits.remove(swap_ext) if not avail.coupled: avail.decoupled_exits.remove('Blacksmiths Hut') exits.remove('Blacksmiths Hut') @@ -442,23 +502,57 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe hole_targets.append((target_exit, target_drop)) random.shuffle(hole_entrances) - if not cross_world and avail.is_sanc_forced_in_hc() and 'Sanctuary Grave' in holes_to_shuffle: - lw_entrance = next(entrance for entrance in hole_entrances if entrance[0] in LW_Entrances) - hole_entrances.remove(lw_entrance) - sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit') - hole_targets.remove(sanc_interior) - connect_two_way(lw_entrance[0], sanc_interior[0], avail) # two-way exit - connect_entrance(lw_entrance[1], sanc_interior[1], avail) # hole - remove_from_list(entrances, [lw_entrance[0], lw_entrance[1]]) - remove_from_list(exits, [sanc_interior[0], sanc_interior[1]]) + if not cross_world and 'Sanctuary Grave' in holes_to_shuffle: + hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player) + is_hc_in_dw = avail.world.mode[avail.player] == 'inverted' + if hc.connected_region: + is_hc_in_dw = hc.connected_region.type == RegionType.DarkWorld + chosen_entrance = None + if is_hc_in_dw: + if avail.swapped: + chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances and e[0] != 'Sanctuary') + if not chosen_entrance: + chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances) + if not chosen_entrance: + if avail.swapped: + chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances and e[0] != 'Sanctuary') + if not chosen_entrance: + chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances) + if chosen_entrance: + hole_entrances.remove(chosen_entrance) + sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit') + hole_targets.remove(sanc_interior) + connect_two_way(chosen_entrance[0], sanc_interior[0], avail) # two-way exit + connect_entrance(chosen_entrance[1], sanc_interior[1], avail) # hole + remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]]) + remove_from_list(exits, [sanc_interior[0], sanc_interior[1]]) + if avail.swapped and drop_map[chosen_entrance[1]] != sanc_interior[1]: + swap_ent, swap_ext = connect_swap(chosen_entrance[0], sanc_interior[0], avail) + swap_drop, swap_tgt = connect_swap(chosen_entrance[1], sanc_interior[1], avail) + hole_entrances.remove((swap_ent, swap_drop)) + hole_targets.remove((swap_ext, swap_tgt)) + remove_from_list(entrances, [swap_ent, swap_drop]) + remove_from_list(exits, [swap_ext, swap_tgt]) random.shuffle(hole_targets) - for entrance, drop in hole_entrances: - ext, target = hole_targets.pop() + while len(hole_entrances): + entrance, drop = hole_entrances.pop() + if avail.swapped and len(hole_targets) > 1: + ext, target = next((x, t) for x, t in hole_targets if x != entrance_map[entrance]) + hole_targets.remove((ext, target)) + else: + ext, target = hole_targets.pop() connect_two_way(entrance, ext, avail) connect_entrance(drop, target, avail) remove_from_list(entrances, [entrance, drop]) remove_from_list(exits, [ext, target]) + if avail.swapped and drop_map[drop] != target: + swap_ent, swap_ext = connect_swap(entrance, ext, avail) + swap_drop, swap_tgt = connect_swap(drop, target, avail) + hole_entrances.remove((swap_ent, swap_drop)) + hole_targets.remove((swap_ext, swap_tgt)) + remove_from_list(entrances, [swap_ent, swap_drop]) + remove_from_list(exits, [swap_ext, swap_tgt]) def do_dark_sanc(entrances, exits, avail): @@ -468,10 +562,16 @@ def do_dark_sanc(entrances, exits, avail): forbidden = list(Isolated_LH_Doors) if not avail.world.is_tile_swapped(0x05, avail.player): forbidden.append('Mimic Cave') + if avail.swapped: + forbidden.append('Dark Sanctuary Hint') + forbidden.extend(Forbidden_Swap_Entrances) + if not avail.world.is_bombshop_start(avail.player): + forbidden.append('Links House') if avail.world.owShuffle[avail.player] == 'vanilla': choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances] else: choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances] + choice = random.choice(choices) entrances.remove(choice) exits.remove('Dark Sanctuary Hint') @@ -479,6 +579,10 @@ def do_dark_sanc(entrances, exits, avail): ext.connect(avail.world.get_entrance(choice, avail.player).parent_region) if not avail.coupled: avail.decoupled_entrances.remove(choice) + if avail.swapped and choice != 'Dark Sanctuary Hint': + swap_ent, swap_ext = connect_swap(choice, 'Dark Sanctuary Hint', avail) + entrances.remove(swap_ent) + exits.remove(swap_ext) elif not ext.connected_region: # default to output to vanilla area, assume vanilla connection ext.connect(avail.world.get_region('Dark Chapel Area', avail.player)) @@ -487,12 +591,10 @@ def do_dark_sanc(entrances, exits, avail): def do_links_house(entrances, exits, avail, cross_world): lh_exit = 'Big Bomb Shop' if avail.world.is_bombshop_start(avail.player) else 'Links House Exit' if lh_exit in exits: + links_house_vanilla = 'Big Bomb Shop' if avail.world.is_bombshop_start(avail.player) else 'Links House' if not avail.world.shufflelinks[avail.player]: - links_house = 'Big Bomb Shop' if avail.world.is_bombshop_start(avail.player) else 'Links House' + links_house = links_house_vanilla else: - # 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] == 'crossed' - and avail.world.intensity[avail.player] >= 3) entrance_pool = entrances if avail.coupled else avail.decoupled_entrances forbidden = list(Isolated_LH_Doors) @@ -503,10 +605,17 @@ def do_links_house(entrances, exits, avail, cross_world): if avail.inverted: 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: + if (avail.world.doorShuffle[avail.player] != 'vanilla' and avail.world.intensity[avail.player] > 2 + and not avail.world.is_tile_swapped(0x1b, avail.player)): + forbidden.append('Hyrule Castle Entrance (South)') + if avail.swapped: + forbidden.append(links_house_vanilla) + forbidden.extend(Forbidden_Swap_Entrances) + shuffle_mode = avail.world.shuffle[avail.player] if avail.world.owShuffle[avail.player] == 'vanilla': # simple shuffle - - if avail.world.shuffle[avail.player] == 'simple': + if shuffle_mode == 'simple': avail.links_on_mountain = True # taken care of by the logic below if avail.world.is_tile_swapped(0x03, avail.player): # in inverted, links house cannot be on the mountain forbidden.extend(['Spike Cave', 'Dark Death Mountain Fairy', 'Hookshot Fairy']) @@ -519,11 +628,21 @@ def do_links_house(entrances, exits, avail, cross_world): # can't have links house on eddm in restricted because Inverted Aga Tower isn't available # todo: inverted full may have the same problem if both links house and a mandatory connector is chosen # from the 3 inverted options - if avail.world.shuffle[avail.player] in ['restricted'] and avail.world.is_tile_swapped(0x03, avail.player): + if shuffle_mode == 'restricted' and avail.world.is_tile_swapped(0x03, avail.player): avail.links_on_mountain = True forbidden.extend(['Spike Cave', 'Dark Death Mountain Fairy']) + + if shuffle_mode in ['lite', 'lean']: + forbidden.extend(['Spike Cave', 'Mire Shed']) + if avail.world.is_tile_swapped(0x05, avail.player): + avail.links_on_mountain = True + forbidden.extend(['Dark Death Mountain Shop']) else: 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'] + and avail.world.intensity[avail.player] >= 3) if cross_world and not sanc_spawn_can_be_dark: possible = [e for e in entrance_pool if e not in forbidden] @@ -543,35 +662,54 @@ def do_links_house(entrances, exits, avail, cross_world): if not avail.coupled: avail.decoupled_entrances.remove(links_house) avail.decoupled_exits.remove(lh_exit) + if avail.swapped and links_house != links_house_vanilla: + swap_ent, swap_ext = connect_swap(links_house, lh_exit, avail) + entrances.remove(swap_ent) + exits.remove(swap_ext) # links on dm dm_spots = LH_DM_Connector_List.union(LH_DM_Exit_Forbidden) if links_house in dm_spots and avail.world.owShuffle[avail.player] == 'vanilla': if avail.links_on_mountain: return # connector is fine - multi_exit_caves = figure_out_connectors(exits) - entrance_pool = entrances if avail.coupled else avail.decoupled_entrances - if cross_world: - possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List] - possible_exits = [e for e in entrance_pool if e not in dm_spots] + logging.getLogger('').warning(f'Links House is placed in tight area and is now unhandled. Report any errors that occur from here.') + return + if avail.world.shuffle[avail.player] in ['lite', 'lean']: + rem_exits = [e for e in avail.exits if e in Connector_Exit_Set and e not in Dungeon_Exit_Set] + multi_exit_caves = figure_out_connectors(rem_exits) + if cross_world: + possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List] + possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots] + else: + world_list = LW_Entrances if not avail.inverted else DW_Entrances + possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List and e in world_list] + possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots and e in world_list] else: - world_list = LW_Entrances if not avail.inverted else DW_Entrances - possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List and e in world_list] - possible_exits = [e for e in entrance_pool if e not in dm_spots and e in world_list] + multi_exit_caves = figure_out_connectors(exits) + entrance_pool = entrances if avail.coupled else avail.decoupled_entrances + if cross_world: + possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List] + possible_exits = [e for e in entrance_pool if e not in dm_spots] + else: + world_list = LW_Entrances if not avail.inverted else DW_Entrances + possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List and e in world_list] + possible_exits = [e for e in entrance_pool if e not in dm_spots and e in world_list] chosen_cave = random.choice(multi_exit_caves) shuffle_connector_exits(chosen_cave) possible_dm_exits.sort() possible_exits.sort() chosen_dm_escape = random.choice(possible_dm_exits) chosen_landing = random.choice(possible_exits) + chosen_exit_start = chosen_cave.pop(0) + chosen_exit_end = chosen_cave.pop() if avail.coupled: - connect_two_way(chosen_dm_escape, chosen_cave.pop(0), avail) - connect_two_way(chosen_landing, chosen_cave.pop(), avail) + connect_two_way(chosen_dm_escape, chosen_exit_start, avail) + connect_two_way(chosen_landing, chosen_exit_end, avail) entrances.remove(chosen_dm_escape) entrances.remove(chosen_landing) else: - connect_entrance(chosen_dm_escape, chosen_cave.pop(0), avail) - connect_exit(chosen_cave.pop(), chosen_landing, avail) + connect_entrance(chosen_dm_escape, chosen_exit_start, avail) + connect_exit(chosen_exit_end, chosen_landing, avail) entrances.remove(chosen_dm_escape) avail.decoupled_entrances.remove(chosen_landing) if len(chosen_cave): @@ -721,8 +859,8 @@ def must_exits_helper(avail): inaccessible_regions = list(avail.world.inaccessible_regions[avail.player]) # find OW regions that don't have a multi-entrance dungeon exit connected - glitch_regions = ['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs', 'Hyrule Castle Water', - 'Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs', 'Pyramid Water'] + glitch_regions = ['Central Cliffs', 'Eastern Cliff', 'Desert Northern Cliffs', 'Hyrule Castle Water', + 'Dark Central Cliffs', 'Darkness Cliff', 'Mire Northern Cliffs', 'Pyramid Water'] multi_dungeon_exits = { 'Hyrule Castle South Portal', 'Hyrule Castle West Portal', 'Hyrule Castle East Portal', 'Sanctuary Portal', 'Desert South Portal', 'Desert West Portal', @@ -932,21 +1070,44 @@ def do_cross_world_connectors(entrances, caves, avail): cave_candidate = (None, 0) for i, cave in enumerate(caves): if isinstance(cave, str): - cave = (cave,) + cave = [cave] if len(cave) > cave_candidate[1]: cave_candidate = (i, len(cave)) cave = caves.pop(cave_candidate[0]) if isinstance(cave, str): - cave = (cave,) + cave = [cave] - for ext in cave: + while len(cave): + ext = cave.pop() if not avail.coupled: choice = random.choice(avail.decoupled_entrances) connect_exit(ext, choice, avail) avail.decoupled_entrances.remove(choice) else: - connect_two_way(entrances.pop(), ext, avail) + if avail.swapped and len(entrances) > 1: + chosen_entrance = next(e for e in entrances if combine_map[e] != ext) + entrances.remove(chosen_entrance) + else: + chosen_entrance = entrances.pop() + connect_two_way(chosen_entrance, ext, avail) + if avail.swapped: + swap_ent, swap_ext = connect_swap(chosen_entrance, ext, avail) + if swap_ent: + entrances.remove(swap_ent) + if chosen_entrance not in single_entrance_map: + if swap_ext in cave: + cave.remove(swap_ext) + else: + for c in caves: + if swap_ext == c: + caves.remove(swap_ext) + break + if not isinstance(c, str) and swap_ext in c: + c.remove(swap_ext) + if len(c) == 0: + caves.remove(c) + break def do_fixed_shuffle(avail, entrance_list): @@ -974,7 +1135,7 @@ def do_fixed_shuffle(avail, entrance_list): choice = choices[i] elif rules.must_exit_to_lw: lw_exits = set() - for e, x in {**entrance_map, **single_entrance_map, **drop_map}.items(): + for e, x in combine_map.items(): if x in avail.exits: region = avail.world.get_entrance(e, avail.player).parent_region if region.type == RegionType.LightWorld: @@ -1128,6 +1289,9 @@ def do_vanilla_connect(pool_def, avail): elif pool_def['condition'] == 'takeany': if avail.world.take_any[avail.player] == 'fixed': return + elif pool_def['condition'] == 'bonk': + if avail.world.shuffle_bonk_drops[avail.player]: + return defaults = {**default_connections, **(inverted_default_connections if avail.inverted != avail.world.is_tile_swapped(0x1b, avail.player) else open_default_connections)} for entrance in pool_def['entrances']: if entrance in avail.entrances: @@ -1156,7 +1320,12 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): invalid_connections[entrance] = set() if entrance in must_exit: must_exit.remove(entrance) - entrances.append(entrance) + if entrance not in entrances: + entrances.append(entrance) + if avail.swapped: + swap_forbidden = [e for e in entrances if combine_map[e] in must_exit] + for e in swap_forbidden: + entrances.remove(e) entrances.sort() # sort these for consistency random.shuffle(entrances) random.shuffle(cave_options) @@ -1169,6 +1338,19 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): invalid_connections[ext] = invalid_connections[ext].union({'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'}) break + def connect_cave_swap(entrance, exit, current_cave): + swap_entrance, swap_exit = connect_swap(entrance, exit, avail) + if swap_entrance and entrance not in single_entrance_map: + for option in cave_options: + if swap_exit in option and option == current_cave: + x=0 + if swap_exit in option and option != current_cave: + option.remove(swap_exit) + if len(option) == 0: + cave_options.remove(option) + break + return swap_entrance, swap_exit + used_caves = [] required_entrances = 0 # Number of entrances reserved for used_caves while must_exit: @@ -1176,9 +1358,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): # find multi exit cave candidates = [] for candidate in cave_options: - if not isinstance(candidate, str) and (candidate in used_caves + if not isinstance(candidate, str) and len(candidate) > 1 and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances): - candidates.append(candidate) + if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped + candidates.append(candidate) cave = random.choice(candidates) if cave is None: raise RuntimeError('No more caves left. Should not happen!') @@ -1186,13 +1369,23 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): # all caves are sorted so that the last exit is always reachable rnd_cave = list(cave) shuffle_connector_exits(rnd_cave) # should be the same as unbiasing some entrances... - entrances.remove(exit) + if avail.swapped and exit in swap_forbidden: + swap_forbidden.remove(exit) + else: + entrances.remove(exit) connect_two_way(exit, rnd_cave[-1], avail) + if avail.swapped: + swap_ent, _ = connect_cave_swap(exit, rnd_cave[-1], cave) + entrances.remove(swap_ent) 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)] and e not in must_exit) + and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit + and (not avail.swapped or rnd_cave[0] != combine_map[e])) entrances.remove(entrance) connect_two_way(entrance, rnd_cave[0], avail) + if avail.swapped and combine_map[entrance] != rnd_cave[0]: + swap_ent, _ = connect_cave_swap(entrance, rnd_cave[0], cave) + entrances.remove(swap_ent) if cave in used_caves: required_entrances -= 2 used_caves.remove(cave) @@ -1202,10 +1395,18 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit cave_entrances = [] for cave_exit in rnd_cave[:-1]: - entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit) - cave_entrances.append(entrance) - entrances.remove(entrance) - connect_two_way(entrance, cave_exit, avail) + if avail.swapped and cave_exit not in avail.exits: + entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name + cave_entrances.append(entrance) + else: + entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit + and (not avail.swapped or cave_exit != combine_map[e])) + cave_entrances.append(entrance) + entrances.remove(entrance) + connect_two_way(entrance, cave_exit, avail) + if avail.swapped and combine_map[entrance] != cave_exit: + swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) + entrances.remove(swap_ent) if entrance not in invalid_connections: invalid_connections[exit] = set() if all(entrance in invalid_connections for entrance in cave_entrances): @@ -1226,11 +1427,20 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): for cave in used_caves: if cave in cave_options: # 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(entrance, cave_exit, avail) + if avail.swapped and cave_exit not in avail.exits: + continue + else: + entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)] + and (not avail.swapped or cave_exit != combine_map[e])) + invalid_cave_connections[tuple(cave)] = set() + entrances.remove(entrance) + connect_two_way(entrance, cave_exit, avail) + if avail.swapped and combine_map[entrance] != cave_exit: + swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) + entrances.remove(swap_ent) cave_options.remove(cave) + if avail.swapped: + entrances.extend(swap_forbidden) def do_mandatory_connections_decoupled(avail, cave_options, must_exit): @@ -1322,8 +1532,7 @@ inverted_sub_table = { 'Pyramid Entrance': 'Inverted Pyramid Entrance' } -inverted_exit_sub_table = { -} +inverted_exit_sub_table = { } def inverted_substitution(avail_pool, collection, is_entrance, is_set=False): @@ -1342,6 +1551,48 @@ def inverted_substitution(avail_pool, collection, is_entrance, is_set=False): pass +def connect_swapped(entrancelist, targetlist, avail, two_way=False): + random.shuffle(entrancelist) + sorted_targets = list() + for ent in entrancelist: + if ent in combine_map: + if combine_map[ent] not in targetlist: + logging.getLogger('').error(f'{combine_map[ent]} not in target list, cannot swap entrance') + raise Exception(f'{combine_map[ent]} not in target list, cannot swap entrance') + sorted_targets.append(combine_map[ent]) + if len(sorted_targets): + targetlist = list(sorted_targets) + else: + targetlist = list(targetlist) + indexlist = list(range(len(targetlist))) + random.shuffle(indexlist) + + while len(indexlist) > 1: + index1 = indexlist.pop() + index2 = indexlist.pop() + targetlist[index1], targetlist[index2] = targetlist[index2], targetlist[index1] + + for exit, target in zip(entrancelist, targetlist): + if two_way: + connect_two_way(exit, target, avail) + else: + connect_entrance(exit, target, avail) + + +def connect_swap(entrance, exit, avail): + swap_exit = combine_map[entrance] + if swap_exit != exit: + swap_entrance = next(e for e, x in combine_map.items() if x == exit) + if swap_entrance in ['Pyramid Entrance', 'Pyramid Hole'] and avail.world.is_tile_swapped(0x1b, avail.player): + swap_entrance = 'Inverted ' + swap_entrance + if entrance in entrance_map: + connect_two_way(swap_entrance, swap_exit, avail) + else: + connect_entrance(swap_entrance, swap_exit, avail) + return swap_entrance, swap_exit + return None, None + + def connect_random(exitlist, targetlist, avail, two_way=False): targetlist = list(targetlist) random.shuffle(targetlist) @@ -1542,18 +1793,18 @@ modes = { 'fixed_non_items': { 'special': 'vanilla', 'condition': '', - 'entrances': ['Dark Desert Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', + 'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Shop', - 'East Dark World Hint', 'Kakariko Gamble Game', 'Good Bee Cave', 'Long Fairy Cave', + 'East Dark World Hint', 'Kakariko Gamble Game', 'Long Fairy Cave', 'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', - 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)', 'Inverted Dark Sanctuary'], + 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)'], }, 'fixed_shops': { 'special': 'vanilla', 'condition': 'shopsanity', - 'entrances': ['Cave Shop (Dark Death Mountain)', 'Dark World Potion Shop', 'Dark World Lumberjack Shop', + 'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', - 'Cave Shop (Lake Hylia)'], + 'Lake Hylia Shop'], }, 'fixed_takeanys': { 'special': 'vanilla', @@ -1567,22 +1818,26 @@ modes = { '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', - 'Dark Desert Hint'] - + 'Mire Hint'] + }, + 'fixed_bonk': { + 'special': 'vanilla', + 'condition': 'bonk', + 'entrances': ['Good Bee Cave'] }, 'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps - 'entrances': ['Mimic Cave', 'Spike Cave', 'Mire Shed', 'Dark World Hammer Peg Cave', 'Chest Game', + 'entrances': ['Mimic Cave', 'Spike Cave', 'Mire Shed', 'Hammer Peg Cave', 'Chest Game', 'C-Shaped House', 'Brewery', 'Hype Cave', 'Big Bomb Shop', 'Pyramid Fairy', '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', 'Cave Shop (Dark Death Mountain)', - 'Dark World Potion Shop', 'Dark World Lumberjack Shop', 'Dark World Shop', - 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Cave Shop (Lake Hylia)', + 'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Good Bee Cave', + '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', - 'Dark Desert Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', + 'Mire Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)', 'Links House', 'Tavern North'] }, @@ -1629,18 +1884,18 @@ modes = { 'fixed_non_items': { 'special': 'vanilla', 'condition': '', - 'entrances': ['Dark Desert Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', + 'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Shop', - 'East Dark World Hint', 'Kakariko Gamble Game', 'Good Bee Cave', 'Long Fairy Cave', + 'East Dark World Hint', 'Kakariko Gamble Game', 'Long Fairy Cave', 'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble', - 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)', 'Inverted Dark Sanctuary'], + 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)'], }, 'fixed_shops': { 'special': 'vanilla', 'condition': 'shopsanity', - 'entrances': ['Cave Shop (Dark Death Mountain)', 'Dark World Potion Shop', 'Dark World Lumberjack Shop', + 'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', - 'Cave Shop (Lake Hylia)'], + 'Lake Hylia Shop'], }, 'fixed_takeanys': { 'special': 'vanilla', @@ -1654,22 +1909,26 @@ modes = { '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', - 'Dark Desert Hint'] - + 'Mire Hint'] + }, + 'fixed_bonk': { + 'special': 'vanilla', + 'condition': 'bonk', + 'entrances': ['Good Bee Cave'] }, 'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps - 'entrances': ['Mimic Cave', 'Spike Cave', 'Mire Shed', 'Dark World Hammer Peg Cave', 'Chest Game', + 'entrances': ['Mimic Cave', 'Spike Cave', 'Mire Shed', 'Hammer Peg Cave', 'Chest Game', 'C-Shaped House', 'Brewery', 'Hype Cave', 'Big Bomb Shop', 'Pyramid Fairy', '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', 'Cave Shop (Dark Death Mountain)', - 'Dark World Potion Shop', 'Dark World Lumberjack Shop', 'Dark World Shop', - 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Cave Shop (Lake Hylia)', + 'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Good Bee Cave', + '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', - 'Dark Desert Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', + 'Mire Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)', 'Links House', 'Tavern North'] # inverted links house gets substituted } @@ -1797,6 +2056,23 @@ modes = { }, } }, + 'swapped': { + 'undefined': 'swap', + 'keep_drops_together': 'on', + 'cross_world': 'on', + 'pools': { + 'skull_drops': { + 'special': 'drops', + 'entrances': ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] + }, + 'skull_doors': { + 'special': 'skull', + 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)'] + }, + } + }, 'crossed': { 'undefined': 'shuffle', 'keep_drops_together': 'on', @@ -1927,12 +2203,12 @@ entrance_map = { single_entrance_map = { 'Mimic Cave': 'Mimic Cave', 'Dark Death Mountain Fairy': 'Dark Death Mountain Healer Fairy', - 'Cave Shop (Dark Death Mountain)': 'Cave Shop (Dark Death Mountain)', 'Spike Cave': 'Spike Cave', - 'Dark Desert Fairy': 'Dark Desert Healer Fairy', 'Dark Desert Hint': 'Dark Desert Hint', 'Mire Shed': 'Mire Shed', - 'Archery Game': 'Archery Game', 'Dark World Potion Shop': 'Dark World Potion Shop', - 'Dark World Lumberjack Shop': 'Dark World Lumberjack Shop', 'Dark World Shop': 'Village of Outcasts Shop', + 'Dark Death Mountain Shop': 'Dark Death Mountain Shop', 'Spike Cave': 'Spike Cave', + 'Mire Fairy': 'Mire Healer Fairy', 'Mire Hint': 'Mire Hint', 'Mire Shed': 'Mire Shed', + 'Archery Game': 'Archery Game', 'Dark Potion Shop': 'Dark Potion Shop', + 'Dark Lumberjack Shop': 'Dark Lumberjack Shop', 'Dark World Shop': 'Village of Outcasts Shop', 'Fortune Teller (Dark)': 'Fortune Teller (Dark)', 'Dark Sanctuary Hint': 'Dark Sanctuary Hint', - 'Red Shield Shop': 'Red Shield Shop', 'Dark World Hammer Peg Cave': 'Dark World Hammer Peg Cave', + 'Red Shield Shop': 'Red Shield Shop', 'Hammer Peg Cave': 'Hammer Peg Cave', 'Chest Game': 'Chest Game', 'C-Shaped House': 'C-Shaped House', 'Brewery': 'Brewery', 'Bonk Fairy (Dark)': 'Bonk Fairy (Dark)', 'Hype Cave': 'Hype Cave', 'Dark Lake Hylia Ledge Hint': 'Dark Lake Hylia Ledge Hint', @@ -1951,23 +2227,25 @@ single_entrance_map = { 'Snitch Lady (West)': 'Snitch Lady (West)', 'Snitch Lady (East)': 'Snitch Lady (East)', 'Fortune Teller (Light)': 'Fortune Teller (Light)', 'Lost Woods Gamble': 'Lost Woods Gamble', 'Sick Kids House': 'Sick Kids House', 'Blacksmiths Hut': 'Blacksmiths Hut', 'Capacity Upgrade': 'Capacity Upgrade', - 'Cave Shop (Lake Hylia)': 'Cave Shop (Lake Hylia)', 'Sahasrahlas Hut': 'Sahasrahlas Hut', + 'Lake Hylia Shop': 'Lake Hylia Shop', 'Sahasrahlas Hut': 'Sahasrahlas Hut', 'Aginahs Cave': 'Aginahs Cave', 'Chicken House': 'Chicken House', 'Tavern North': 'Tavern', - 'Kings Grave': 'Kings Grave', 'Desert Fairy': 'Desert Healer Fairy', 'Light Hype Fairy': 'Swamp Healer Fairy', + 'Kings Grave': 'Kings Grave', 'Desert Fairy': 'Desert Healer Fairy', 'Light Hype Fairy': 'Light Hype Fairy', 'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy', 'Bonk Fairy (Light)': 'Bonk Fairy (Light)', 'Lumberjack House': 'Lumberjack House', 'Dam': 'Dam', 'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing' } +combine_map = {**entrance_map, **single_entrance_map, **drop_map} + LW_Entrances = [] DW_Entrances = [] Isolated_LH_Doors = ['Kings Grave', 'Waterfall of Wishing', 'Desert Palace Entrance (South)', 'Desert Palace Entrance (North)', 'Capacity Upgrade', 'Ice Palace', 'Skull Woods Final Section', 'Skull Woods Second Section Door (West)', - 'Dark World Hammer Peg Cave', 'Turtle Rock Isolated Ledge Entrance', + 'Hammer Peg Cave', 'Turtle Rock Isolated Ledge Entrance', 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', - 'Dark World Shop', 'Dark World Potion Shop'] + 'Dark World Shop', 'Dark Potion Shop'] # inverted doesn't like really like - Paradox Top or Tower of Hera LH_DM_Connector_List = { @@ -1976,14 +2254,14 @@ LH_DM_Connector_List = { 'Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Hookshot Fairy', 'Spike Cave', 'Dark Death Mountain Fairy', 'Ganons Tower', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)', - 'Hookshot Cave', 'Cave Shop (Dark Death Mountain)', 'Turtle Rock'} + 'Hookshot Cave', 'Dark Death Mountain Shop', 'Turtle Rock'} LH_DM_Exit_Forbidden = { 'Turtle Rock Isolated Ledge Entrance', 'Mimic Cave', 'Hookshot Cave Back Entrance', 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Desert Palace Entrance (South)', - 'Ice Palace', 'Waterfall of Wishing', 'Kings Grave', 'Dark World Hammer Peg Cave', 'Capacity Upgrade', + 'Ice Palace', 'Waterfall of Wishing', 'Kings Grave', 'Hammer Peg Cave', 'Capacity Upgrade', 'Skull Woods Final Section', 'Skull Woods Second Section Door (West)' -} # omissions from Isolated Starts: 'Desert Palace Entrance (North)', 'Dark World Shop', 'Dark World Potion Shop' +} # omissions from Isolated Starts: 'Desert Palace Entrance (North)', 'Dark World Shop', 'Dark Potion Shop' Connector_List = [['Elder House Exit (East)', 'Elder House Exit (West)'], ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)'], @@ -2016,200 +2294,218 @@ Connector_Exit_Set = { 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)' } +Dungeon_Exit_Set = { + 'Eastern Palace Exit', + 'Tower of Hera Exit', + 'Agahnims Tower Exit', + 'Palace of Darkness Exit', + 'Swamp Palace Exit', + 'Skull Woods Final Section Exit', + 'Thieves Town Exit', + 'Ice Palace Exit', + 'Misery Mire Exit', + 'Ganons Tower Exit', + 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', + 'Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', + 'Desert Palace Exit (South)', 'Desert Palace Exit (East)', 'Desert Palace Exit (West)', + 'Turtle Rock Exit (Front)', 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)' +} + # Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups Must_Exit_Invalid_Connections = defaultdict(set) Simple_DM_Non_Connectors = {'Old Man Cave Ledge', 'Spiral Cave (Top)', 'Superbunny Cave (Bottom)', 'Spectacle Rock Cave (Peak)', 'Spectacle Rock Cave (Top)'} +Forbidden_Swap_Entrances = {'Old Man Cave (East)', 'Blacksmiths Hut', 'Big Bomb Shop'} + # these are connections that cannot be shuffled and always exist. # They link together underworld regions mandatory_connections = [('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'), ('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'), - ('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)'), - ('Sewer Drop', 'Sewers Rat Path'), - ('Old Man Cave Dropdown', 'Old Man Cave'), - ('Old Man House Front to Back', 'Old Man House Back'), - ('Old Man House Back to Front', 'Old Man House'), - ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'), - ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'), ('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'), ('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'), + ('Old Man Cave Dropdown', 'Old Man Cave'), + ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'), + ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'), + ('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)'), - ('Light World Death Mountain Shop', 'Light World Death Mountain Shop'), ('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)'), + ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), + ('Kakariko Well (top to back)', 'Kakariko Well (back)'), + ('Blinds Hideout N', 'Blinds Hideout (Top)'), + ('Sewer Drop', 'Sewers Rat Path'), ('Missing Smith', 'Missing Smith'), - ('Bumper Cave Bottom to Top', 'Bumper Cave (top)'), - ('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'), - ('Superbunny Cave Climb', 'Superbunny Cave (Top)'), + ('Bat Cave Door', 'Bat Cave (left)'), + ('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 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 = {'Links House': 'Links House', - 'Links House Exit': 'Links House Area', - 'Waterfall of Wishing': 'Waterfall of Wishing', - 'Blinds Hideout': 'Blinds Hideout', - 'Dam': 'Dam', - 'Lumberjack House': 'Lumberjack House', - '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', - 'Bonk Fairy (Light)': 'Bonk Fairy (Light)', - 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy', - 'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', - 'Light Hype Fairy': 'Swamp Healer Fairy', - 'Desert Fairy': 'Desert Healer Fairy', - 'Kings Grave': 'Kings Grave', - 'Tavern North': 'Tavern', - 'Chicken House': 'Chicken House', - 'Aginahs Cave': 'Aginahs Cave', - 'Sahasrahlas Hut': 'Sahasrahlas Hut', - 'Cave Shop (Lake Hylia)': 'Cave Shop (Lake Hylia)', - 'Capacity Upgrade': 'Capacity Upgrade', - 'Kakariko Well Drop': 'Kakariko Well (top)', - 'Kakariko Well Cave': 'Kakariko Well (bottom)', - 'Kakariko Well Exit': 'Kakariko Area', - 'Blacksmiths Hut': 'Blacksmiths Hut', - 'Bat Cave Drop': 'Bat Cave (right)', - 'Bat Cave Cave': 'Bat Cave (left)', - 'Bat Cave Exit': 'Blacksmith Area', - 'Sick Kids House': 'Sick Kids House', - 'Elder House (East)': 'Elder House', - 'Elder House (West)': 'Elder House', - 'Elder House Exit (East)': 'Kakariko Area', - 'Elder House Exit (West)': 'Kakariko Area', - 'North Fairy Cave Drop': 'North Fairy Cave', - 'North Fairy Cave': 'North Fairy Cave', - 'North Fairy Cave Exit': 'River Bend Area', - 'Lost Woods Gamble': 'Lost Woods Gamble', - 'Fortune Teller (Light)': 'Fortune Teller (Light)', - 'Snitch Lady (East)': 'Snitch Lady (East)', - 'Snitch Lady (West)': 'Snitch Lady (West)', - 'Bush Covered House': 'Bush Covered House', - 'Tavern (Front)': 'Tavern (Front)', - 'Light World Bomb Hut': 'Light World Bomb Hut', - 'Kakariko Shop': 'Kakariko Shop', +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', - 'Cave 45': 'Cave 45', - 'Graveyard Cave': 'Graveyard Cave', - 'Checkerboard Cave': 'Checkerboard Cave', - 'Mini Moldorm Cave': 'Mini Moldorm Cave', - 'Long Fairy Cave': 'Long Fairy Cave', # near East Light World Teleporter - 'Good Bee Cave': 'Good Bee Cave', - '20 Rupee Cave': '20 Rupee Cave', - '50 Rupee Cave': '50 Rupee Cave', - 'Ice Rod Cave': 'Ice Rod Cave', - 'Bonk Rock Cave': 'Bonk Rock Cave', - 'Library': 'Library', - 'Kakariko Gamble Game': 'Kakariko Gamble Game', - 'Potion Shop': 'Potion Shop', - 'Two Brothers House (East)': 'Two Brothers House', - 'Two Brothers House (West)': 'Two Brothers House', - 'Two Brothers House Exit (East)': 'Kakariko Suburb Area', - 'Two Brothers House Exit (West)': 'Maze Race Ledge', - - 'Sanctuary': 'Sanctuary Portal', - 'Sanctuary Grave': 'Sewer Drop', - 'Sanctuary Exit': 'Sanctuary Area', - - 'Old Man Cave (West)': 'Old Man Cave Ledge', + 'Death Mountain Return Cave (East)': 'Death Mountain Return Cave (right)', + 'Death Mountain Return Cave Exit (East)': 'West Death Mountain (Bottom)', 'Old Man Cave (East)': 'Old Man Cave', - 'Old Man Cave Exit (West)': 'Mountain Entry Entrance', 'Old Man Cave Exit (East)': 'West Death Mountain (Bottom)', + 'Spectacle Rock Cave': 'Spectacle Rock Cave (Top)', + 'Spectacle Rock Cave Exit (Top)': 'West Death Mountain (Bottom)', + 'Spectacle Rock Cave Peak': 'Spectacle Rock Cave (Peak)', + 'Spectacle Rock Cave Exit (Peak)': 'West Death Mountain (Bottom)', + 'Spectacle Rock Cave (Bottom)': 'Spectacle Rock Cave (Bottom)', + 'Spectacle Rock Cave Exit': '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)', - 'Death Mountain Return Cave (East)': 'Death Mountain Return Cave (right)', - 'Death Mountain Return Cave (West)': 'Death Mountain Return Cave (left)', - 'Death Mountain Return Cave Exit (West)': 'Mountain Entry Ledge', - '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)', - '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)', - 'Hookshot Fairy': 'Hookshot Fairy', - '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', 'Spiral Cave': 'Spiral Cave (Top)', + 'Spiral Cave Exit (Top)': 'Spiral Cave Ledge', '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 (Top)': 'Fairy Ascension Cave (Top)', + 'Fairy Ascension Cave Exit (Top)': 'Fairy Ascension Ledge', + 'Fairy Ascension Cave (Bottom)': 'Fairy Ascension Cave (Bottom)', + 'Fairy Ascension Cave Exit (Bottom)': 'Fairy Ascension Plateau', + 'Hookshot Fairy': 'Hookshot Fairy', + 'Paradox Cave (Top)': 'Paradox Cave', + 'Paradox Cave Exit (Top)': 'East Death Mountain (Top East)', + 'Paradox Cave (Middle)': 'Paradox Cave', + 'Paradox Cave Exit (Middle)': 'East Death Mountain (Bottom)', + 'Paradox Cave (Bottom)': 'Paradox Cave Front', + 'Paradox Cave Exit (Bottom)': 'East Death Mountain (Bottom)', + 'Death Mountain Return Cave (West)': 'Death Mountain Return Cave (left)', + 'Death Mountain Return Cave Exit (West)': 'Mountain Pass Ledge', + 'Old Man Cave (West)': 'Old Man Cave Ledge', + 'Old Man Cave Exit (West)': 'Mountain Pass Entry', + 'Waterfall of Wishing': 'Waterfall of Wishing', + 'Fortune Teller (Light)': 'Fortune Teller (Light)', + 'Bonk Rock Cave': 'Bonk Rock Cave', + 'Sanctuary': 'Sanctuary Portal', + 'Sanctuary Grave': 'Sewer Drop', + 'Sanctuary Exit': 'Sanctuary Area', + '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 Exit (West)': 'Kakariko Village', + 'Elder House (East)': 'Elder House', + 'Elder House Exit (East)': 'Kakariko Village', + 'Snitch Lady (West)': 'Snitch Lady (West)', + 'Snitch Lady (East)': 'Snitch Lady (East)', + 'Chicken House': 'Chicken House', + 'Sick Kids House': 'Sick Kids House', + 'Bush Covered House': 'Bush Covered 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', + '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)', + 'Links House': 'Links House', + 'Links House Exit': 'Links House Area', + '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', - 'Pyramid Fairy': 'Pyramid Fairy', - 'East Dark World Hint': 'East Dark World Hint', - 'Palace of Darkness Hint': 'Palace of Darkness Hint', - 'Big Bomb Shop': 'Big Bomb Shop', - 'Dark Lake Hylia Shop': 'Dark Lake Hylia Shop', - 'Dark Lake Hylia Fairy': 'Dark Lake Hylia Healer Fairy', - 'Dark Lake Hylia Ledge Fairy': 'Dark Lake Hylia Ledge Healer Fairy', - 'Dark Lake Hylia Ledge Spike Cave': 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Lake Hylia Ledge Hint': 'Dark Lake Hylia Ledge Hint', - 'Hype Cave': 'Hype Cave', - 'Bonk Fairy (Dark)': 'Bonk Fairy (Dark)', - 'Brewery': 'Brewery', - 'C-Shaped House': 'C-Shaped House', - 'Chest Game': 'Chest Game', - 'Dark World Hammer Peg Cave': 'Dark World Hammer Peg Cave', - 'Bumper Cave (Bottom)': 'Bumper Cave (bottom)', - 'Bumper Cave (Top)': 'Bumper Cave (top)', - 'Red Shield Shop': 'Red Shield Shop', - 'Dark Sanctuary Hint': 'Dark Sanctuary Hint', - 'Fortune Teller (Dark)': 'Fortune Teller (Dark)', - 'Dark World Shop': 'Village of Outcasts Shop', - 'Dark World Lumberjack Shop': 'Dark World Lumberjack Shop', - 'Dark World Potion Shop': 'Dark World Potion Shop', - 'Archery Game': 'Archery Game', - 'Bumper Cave Exit (Top)': 'Bumper Cave Ledge', - 'Bumper Cave Exit (Bottom)': 'Bumper Cave Entrance', - 'Mire Shed': 'Mire Shed', - 'Dark Desert Hint': 'Dark Desert Hint', - 'Dark Desert Fairy': 'Dark Desert Healer Fairy', - 'Spike Cave': 'Spike Cave', - 'Hookshot Cave': 'Hookshot Cave (Front)', - 'Superbunny Cave (Top)': 'Superbunny Cave (Top)', - 'Cave Shop (Dark Death Mountain)': 'Cave Shop (Dark Death Mountain)', + 'Dark Lumberjack Shop': 'Dark Lumberjack Shop', 'Dark Death Mountain Fairy': 'Dark Death Mountain Healer Fairy', - 'Superbunny Cave (Bottom)': 'Superbunny Cave (Bottom)', - 'Superbunny Cave Exit (Top)': 'East Dark Death Mountain (Top)', - 'Superbunny Cave Exit (Bottom)': 'East Dark Death Mountain (Bottom)', - 'Hookshot Cave Front Exit': 'East Dark Death Mountain (Top)', - 'Hookshot Cave Back Exit': 'Dark Death Mountain Floating Island', + 'Spike Cave': 'Spike Cave', 'Hookshot Cave Back Entrance': 'Hookshot Cave (Back)', - 'Mimic Cave': 'Mimic Cave'} + 'Hookshot Cave Back Exit': 'Dark Death Mountain Floating Island', + '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', + 'Bumper Cave (Top)': 'Bumper Cave (top)', + 'Bumper Cave Exit (Top)': 'Bumper Cave Ledge', + 'Bumper Cave (Bottom)': 'Bumper Cave (bottom)', + 'Bumper Cave Exit (Bottom)': 'Bumper Cave Entry', + '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', + 'Dark World Shop': 'Village of Outcasts Shop', + 'Brewery': 'Brewery', + 'Red Shield Shop': 'Red Shield Shop', + 'Pyramid Fairy': 'Pyramid Fairy', + 'Palace of Darkness Hint': 'Palace of Darkness Hint', + 'Hammer Peg Cave': 'Hammer Peg Cave', + 'Archery Game': 'Archery Game', + 'Bonk Fairy (Dark)': 'Bonk Fairy (Dark)', + 'Big Bomb Shop': 'Big Bomb Shop', + 'Dark Lake Hylia Fairy': 'Dark Lake Hylia Healer Fairy', + 'East Dark World Hint': 'East Dark World Hint', + 'Mire Shed': 'Mire Shed', + 'Mire Hint': 'Mire Hint', + 'Mire Fairy': 'Mire Healer Fairy', + '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 = {'Pyramid Hole': 'Pyramid', 'Pyramid Exit': 'Pyramid Ledge', @@ -2305,7 +2601,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c '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), - 'Cave Shop (Lake Hylia)': (0x57, (0x0112, 0x35, 0x0022, 0x0c00, 0x0b1a, 0x0c26, 0x0b98, 0x0c6d, 0x0b9f, 0x00, 0x00, 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), @@ -2348,19 +2644,19 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c '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), - 'Dark World Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000), 0x20), + '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 World Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), - 'Dark World Potion Shop': (0x6E, (0x010f, 0x56, 0x080e, 0x04f4, 0x0c66, 0x0548, 0x0cd8, 0x0563, 0x0ce3, 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), - 'Dark Desert Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000), 0x00), - 'Dark Desert Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 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), - 'Cave Shop (Dark Death Mountain)': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 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), @@ -2440,11 +2736,11 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Bonk Fairy (Light)': 0x71, 'Bonk Fairy (Dark)': 0x71, 'Lake Hylia Healer Fairy': 0x5E, - 'Swamp Healer Fairy': 0x5E, + 'Light Hype Fairy': 0x5E, 'Desert Healer Fairy': 0x5E, 'Dark Lake Hylia Healer Fairy': 0x5E, 'Dark Lake Hylia Ledge Healer Fairy': 0x5E, - 'Dark Desert Healer Fairy': 0x5E, + 'Mire Healer Fairy': 0x5E, 'Dark Death Mountain Healer Fairy': 0x5E, 'Fortune Teller (Light)': 0x65, 'Lake Hylia Fortune Teller': 0x65, @@ -2453,8 +2749,8 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Chicken House': 0x4B, 'Aginahs Cave': 0x4D, 'Sahasrahlas Hut': 0x45, - 'Cave Shop (Lake Hylia)': 0x58, - 'Cave Shop (Dark Death Mountain)': 0x58, + 'Lake Hylia Shop': 0x58, + 'Dark Death Mountain Shop': 0x58, 'Capacity Upgrade': 0x5D, 'Blacksmiths Hut': 0x64, 'Sick Kids House': 0x40, @@ -2485,21 +2781,21 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Big Bomb Shop': 0x53, 'Village of Outcasts Shop': 0x60, 'Dark Lake Hylia Shop': 0x60, - 'Dark World Lumberjack Shop': 0x60, - 'Dark World Potion 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, - 'Dark World Hammer Peg Cave': 0x83, + 'Hammer Peg Cave': 0x83, 'Red Shield Shop': 0x57, 'Dark Sanctuary Hint': 0x5A, 'Fortune Teller (Dark)': 0x66, 'Archery Game': 0x59, 'Mire Shed': 0x5F, - 'Dark Desert Hint': 0x62, + 'Mire Hint': 0x62, 'Spike Cave': 0x41, 'Mimic Cave': 0x4F, 'Kakariko Well (top)': 0x80, diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 63cccfda..ce8831a7 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -98,6 +98,7 @@ def roll_settings(weights): ret.trap_door_mode = get_choice('trap_door_mode') ret.key_logic_algorithm = get_choice('key_logic_algorithm') ret.decoupledoors = get_choice('decoupledoors') == 'on' + ret.door_self_loops = get_choice('door_self_loops') == 'on' ret.experimental = get_choice('experimental') == 'on' ret.collection_rate = get_choice('collection_rate') == 'on' @@ -111,7 +112,7 @@ def roll_settings(weights): ret.dropshuffle = get_choice('dropshuffle') == 'on' or keydropshuffle ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none' ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery - ret.colorizepots = get_choice('colorizepots') == 'on' + ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on' ret.shufflepots = get_choice('pot_shuffle') == 'on' ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent' ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights @@ -142,15 +143,14 @@ def roll_settings(weights): ganon_item = get_choice('ganon_item') ret.ganon_item = ganon_item if ganon_item != 'none' else 'default' - from ItemList import set_default_triforce - default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0) - goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal) - goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal) - pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool) - pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool) - ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) - min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal)) - ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) + ret.triforce_pool = get_choice_default('triforce_pool', default=0) + ret.triforce_goal = get_choice_default('triforce_goal', default=0) + ret.triforce_pool_min = get_choice_default('triforce_pool_min', default=0) + ret.triforce_pool_max = get_choice_default('triforce_pool_max', default=0) + ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0) + ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0) + ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0) + ret.triforce_max_difference = get_choice_default('triforce_max_difference', default=10000) ret.mode = get_choice('world_state') if ret.mode == 'retro': diff --git a/test/dungeons/trap_test.yaml b/test/dungeons/trap_test.yaml new file mode 100644 index 00000000..d82a8ade --- /dev/null +++ b/test/dungeons/trap_test.yaml @@ -0,0 +1,125 @@ +meta: + players: 1 +settings: + 1: + door_shuffle: basic + intensity: 3 + door_type_mode: all +doors: + 1: + doors: + PoD Mimics 2 SW: + type: Trap Door +# TR Twin Pokeys NW: # not possible due to trap flags +# type: Trap Door + Thieves Blocked Entry SW: + type: Trap Door + Hyrule Dungeon Armory Interior Key Door N: + type: Trap Door + Desert Compass Key Door WN: + type: Trap Door + TR Tile Room SE: + type: Trap Door +# Mire Cross SW: # not possible due to trap flags +# type: Trap Door + Tower Circle of Pots ES: + type: Trap Door + Eastern Single Eyegore ES: + type: Trap Door + Eastern Duo Eyegores SE: + type: Trap Door + Swamp Push Statue S: + type: Trap Door +# Skull 2 East Lobby WS: # currently not possible due to trap flags +# type: Trap Door + GT Hope Room WN : + type: Trap Door + +# Eastern Courtyard Ledge S: # currently not possible due to trap flags +# type: Trap Door + Ice Switch Room ES : + type: Trap Door + Ice Switch Room NE : + type: Trap Door + Skull Torch Room WS : + type: Trap Door + GT Speed Torch NE : + type: Trap Door + GT Speed Torch WS : + type: Trap Door + GT Torch Cross WN : + type: Trap Door + Mire Tile Room SW : + type: Trap Door + Mire Tile Room ES : + type: Trap Door + TR Torches WN : + type: Trap Door + PoD Lobby N: + type: Trap Door + PoD Middle Cage S: + type: Trap Door + Ice Bomb Jump NW: + type: Trap Door + GT Hidden Spikes SE: + type: Trap Door + Ice Tall Hint EN: + type: Trap Door + GT Conveyor Cross EN: + type: Trap Door + Eastern Pot Switch WN: + type: Trap Door + Thieves Conveyor Maze WN: + type: Trap Door +# Thieves Conveyor Maze SW: #not possible due to 4 door limit +# type: Trap Door + Eastern Dark Square Key Door WN: + type: Trap Door + Eastern Lobby NW: + type: Trap Door + Eastern Lobby NE: + type: Trap Door +# Ice Cross Bottom SE: # not possible due to trap flags +# type: Trap Door + Desert Back Lobby S: + type: Trap Door +# Desert West S: need enough lobbies for basic, should otherwise work +# type: Trap Door + Desert West Lobby ES: + type: Trap Door +# Mire Hidden Shooters SE: # not possible due to trap flags +# type: Trap Door +# Mire Hidden Shooters ES: # not possible due to trap flags +# type: Trap Door + Mire Hidden Shooters WS: + type: Trap Door + Tower Dark Pits EN: + type: Trap Door + Tower Dark Maze ES: + type: Trap Door + TR Tongue Pull WS: + type: Trap Door + +# Lower layer: not valid + # Sewers Pull Switch N: + # type: Trap Door + # PoD Sexy Statue W: # not possible due to trap flags and low layer too, so likely not an exception + # type: Trap Door + +# Not valid due to disappearing somaria block +# Mire Dark Shooters SE: +# type: Trap Door + + # These triggers don't open doors +# Ice Compass Room NE: +# type: Trap Door +# Hera Torches NE: +# type: Trap Door +# Mire Spikes WS: +# type: Trap Door +# Mire Spikes SW: +# type: Trap Door +# Mire Spikes NW: +# type: Trap Door +# Tower Room 03 WN: +# type: Trap Door diff --git a/test/inverted/TestInverted.py b/test/inverted/TestInverted.py index 3097f4b7..f4f6e3d9 100644 --- a/test/inverted/TestInverted.py +++ b/test/inverted/TestInverted.py @@ -3,10 +3,10 @@ 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_inverted_entrances +from EntranceShuffle import link_entrances from ItemList import generate_itempool, difficulties from Items import ItemFactory -from Regions import create_regions, mark_light_world_regions, create_dungeon_regions, create_shops +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 test.TestBase import TestBase @@ -25,7 +25,7 @@ class TestInverted(TestBase): create_rooms(self.world, 1) create_dungeons(self.world, 1) link_overworld(self.world, 1) - link_inverted_entrances(self.world, 1) + link_entrances(self.world, 1) link_doors(self.world, 1) generate_itempool(self.world, 1) self.world.required_medallions[1] = ['Ether', 'Quake'] @@ -33,5 +33,5 @@ class TestInverted(TestBase): self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) self.world.get_location('Agahnim 1', 1).item = None self.world.get_location('Agahnim 2', 1).item = None - mark_light_world_regions(self.world, 1) + mark_light_dark_world_regions(self.world, 1) set_rules(self.world, 1) diff --git a/test/inverted/TestInvertedBombRules.py b/test/inverted/TestInvertedBombRules.py index acd32157..974ae797 100644 --- a/test/inverted/TestInvertedBombRules.py +++ b/test/inverted/TestInvertedBombRules.py @@ -4,6 +4,7 @@ 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, \ 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 from Rules import set_inverted_big_bomb_rules from test.inverted.TestInverted import TestInverted @@ -20,7 +21,7 @@ class TestInvertedBombRules(TestInverted): entrance = self.world.get_entrance(entrance_name, 1) entrance.connected_region = None self.world.get_region('Big Bomb Shop', 1).entrances = [] - connect_entrance(self.world, entrance, 'Links House', 1) + connect_entrance(self.world, entrance, 'Big Bomb Shop', 1) set_inverted_big_bomb_rules(self.world, 1) entrance.connected_region.entrances.remove(entrance) entrance.connected_region = None @@ -35,7 +36,7 @@ class TestInvertedBombRules(TestInverted): for entrance_name in ['Desert Palace Entrance (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']: entrance = self.world.get_entrance(entrance_name, 1) self.world.get_region('Big Bomb Shop', 1).entrances = [] - connect_entrance(self.world, entrance, 'Links House', 1) + connect_entrance(self.world, entrance, 'Big Bomb Shop', 1) with self.assertRaises(Exception): set_inverted_big_bomb_rules(self.world, 1) entrance.connected_region.entrances.remove(entrance) diff --git a/test/inverted/TestInvertedEntrances.py b/test/inverted/TestInvertedEntrances.py index b0b20479..a4c2b1d7 100644 --- a/test/inverted/TestInvertedEntrances.py +++ b/test/inverted/TestInvertedEntrances.py @@ -49,16 +49,15 @@ class TestEntrances(TestInverted): ["Tower of Hera", True, ["Moon Pearl", "Hammer", "Hookshot", "Progressive Glove", "Ocarina"]], ["Tower of Hera", True, ["Moon Pearl", "Hammer", "Beat Agahnim 1", "Ocarina", "Hookshot"]], - # Agahnim's Tower (Inverted) - ["Ganons Tower", False, []], - ["Ganons Tower", False, [], ["Ocarina", "Lamp"]], - ["Ganons Tower", False, [], ["Ocarina", "Progressive Glove"]], - ["Ganons Tower", False, [], ["Moon Pearl", "Lamp"]], - ["Ganons Tower", False, [], ["Moon Pearl", "Progressive Glove"]], - ["Ganons Tower", True, ["Lamp", "Progressive Glove"]], - ["Ganons Tower", True, ["Ocarina", "Beat Agahnim 1", "Moon Pearl"]], - ["Ganons Tower", True, ["Ocarina", "Progressive Glove", "Progressive Glove", "Moon Pearl"]], - ["Ganons Tower", True, ["Ocarina", "Progressive Glove", "Hammer", "Moon Pearl"]], + ["Agahnims Tower", False, []], + ["Agahnims Tower", False, [], ["Ocarina", "Lamp"]], + ["Agahnims Tower", False, [], ["Ocarina", "Progressive Glove"]], + ["Agahnims Tower", False, [], ["Moon Pearl", "Lamp"]], + ["Agahnims Tower", False, [], ["Moon Pearl", "Progressive Glove"]], + ["Agahnims Tower", True, ["Lamp", "Progressive Glove"]], + ["Agahnims Tower", True, ["Ocarina", "Beat Agahnim 1", "Moon Pearl"]], + ["Agahnims Tower", True, ["Ocarina", "Progressive Glove", "Progressive Glove", "Moon Pearl"]], + ["Agahnims Tower", True, ["Ocarina", "Progressive Glove", "Hammer", "Moon Pearl"]], ["Palace of Darkness", False, []], ["Palace of Darkness", False, [], ["Hammer", "Flippers", "Magic Mirror", "Ocarina"]], @@ -105,16 +104,15 @@ class TestEntrances(TestInverted): ["Turtle Rock", True, ["Quake", "Progressive Sword", "Progressive Glove", "Hammer", "Moon Pearl", "Ocarina"]], ["Turtle Rock", True, ["Quake", "Progressive Sword", "Beat Agahnim 1", "Moon Pearl", "Ocarina"]], - # Ganon's Tower (Inverted) - ["Agahnims Tower", False, []], - ["Agahnims Tower", False, [], ["Crystal 1"]], - ["Agahnims Tower", False, [], ["Crystal 2"]], - ["Agahnims Tower", False, [], ["Crystal 3"]], - ["Agahnims Tower", False, [], ["Crystal 4"]], - ["Agahnims Tower", False, [], ["Crystal 5"]], - ["Agahnims Tower", False, [], ["Crystal 6"]], - ["Agahnims Tower", False, [], ["Crystal 7"]], - ["Agahnims Tower", True, ["Beat Agahnim 1", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], - ["Agahnims Tower", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], - ["Agahnims Tower", True, ["Moon Pearl", "Hammer", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Ganons Tower", False, []], + ["Ganons Tower", False, [], ["Crystal 1"]], + ["Ganons Tower", False, [], ["Crystal 2"]], + ["Ganons Tower", False, [], ["Crystal 3"]], + ["Ganons Tower", False, [], ["Crystal 4"]], + ["Ganons Tower", False, [], ["Crystal 5"]], + ["Ganons Tower", False, [], ["Crystal 6"]], + ["Ganons Tower", False, [], ["Crystal 7"]], + ["Ganons Tower", True, ["Beat Agahnim 1", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Ganons Tower", True, ["Moon Pearl", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], + ["Ganons Tower", True, ["Moon Pearl", "Hammer", "Progressive Glove", "Progressive Glove", "Crystal 1", "Crystal 2", "Crystal 3", "Crystal 4", "Crystal 5", "Crystal 6", "Crystal 7"]], ]) \ No newline at end of file diff --git a/test/inverted_owg/TestInvertedOWG.py b/test/inverted_owg/TestInvertedOWG.py index a82a092a..cfdf3a3d 100644 --- a/test/inverted_owg/TestInvertedOWG.py +++ b/test/inverted_owg/TestInvertedOWG.py @@ -3,11 +3,11 @@ 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_inverted_entrances +from EntranceShuffle import link_entrances from ItemList import generate_itempool, difficulties from Items import ItemFactory from OverworldGlitchRules import create_owg_connections -from Regions import create_regions, mark_light_world_regions, create_dungeon_regions, create_shops +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 test.TestBase import TestBase @@ -27,7 +27,7 @@ class TestInvertedOWG(TestBase): create_dungeons(self.world, 1) link_overworld(self.world, 1) create_owg_connections(self.world, 1) - link_inverted_entrances(self.world, 1) + link_entrances(self.world, 1) link_doors(self.world, 1) generate_itempool(self.world, 1) self.world.required_medallions[1] = ['Ether', 'Quake'] @@ -37,5 +37,5 @@ class TestInvertedOWG(TestBase): self.world.get_location('Agahnim 2', 1).item = None self.world.precollected_items.clear() self.world.itempool.append(ItemFactory('Pegasus Boots', 1)) - mark_light_world_regions(self.world, 1) + mark_light_dark_world_regions(self.world, 1) set_rules(self.world, 1) diff --git a/test/owg/TestVanillaOWG.py b/test/owg/TestVanillaOWG.py index 9b1ad9f1..c114f24b 100644 --- a/test/owg/TestVanillaOWG.py +++ b/test/owg/TestVanillaOWG.py @@ -7,7 +7,7 @@ from EntranceShuffle import link_entrances from ItemList import difficulties, generate_itempool from Items import ItemFactory from OverworldGlitchRules import create_owg_connections -from Regions import create_regions, create_dungeon_regions, create_shops, mark_dark_world_regions +from Regions import create_regions, create_dungeon_regions, create_shops, mark_light_dark_world_regions from RoomData import create_rooms from Rules import set_rules from test.TestBase import TestBase @@ -37,5 +37,5 @@ class TestVanillaOWG(TestBase): self.world.get_location('Agahnim 2', 1).item = None self.world.precollected_items.clear() self.world.itempool.append(ItemFactory('Pegasus Boots', 1)) - mark_dark_world_regions(self.world, 1) + mark_light_dark_world_regions(self.world, 1) set_rules(self.world, 1) \ No newline at end of file diff --git a/test/stats/EntranceShuffleStats.py b/test/stats/EntranceShuffleStats.py index 130cdb8b..9217a6ba 100644 --- a/test/stats/EntranceShuffleStats.py +++ b/test/stats/EntranceShuffleStats.py @@ -104,6 +104,7 @@ def test_loop(tests, entrance_set, exit_set, ctr, shuffle_mode, main_mode, links {}, {}, {}, {}, {}, True, {}, [], {}) world.customizer = False world.shufflelinks = {1: links} + world.shuffletavern = {1: False} create_regions(world, 1) create_dungeon_regions(world, 1) # print(f'Linking seed {seed}') diff --git a/test/vanilla/TestVanilla.py b/test/vanilla/TestVanilla.py index 56afbd53..6ed6e611 100644 --- a/test/vanilla/TestVanilla.py +++ b/test/vanilla/TestVanilla.py @@ -6,7 +6,7 @@ from OverworldShuffle import link_overworld from EntranceShuffle import link_entrances from ItemList import difficulties, generate_itempool from Items import ItemFactory -from Regions import create_regions, create_dungeon_regions, create_shops, mark_dark_world_regions +from Regions import create_regions, create_dungeon_regions, create_shops, mark_light_dark_world_regions from RoomData import create_rooms from Rules import set_rules from test.TestBase import TestBase @@ -33,5 +33,5 @@ class TestVanilla(TestBase): self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) self.world.get_location('Agahnim 1', 1).item = None self.world.get_location('Agahnim 2', 1).item = None - mark_dark_world_regions(self.world, 1) + mark_light_dark_world_regions(self.world, 1) set_rules(self.world, 1)