From 18f12750509a732506cb8cea1112e432ff0ffffd Mon Sep 17 00:00:00 2001 From: cassidoxa <43386495+cassidoxa@users.noreply.github.com> Date: Sat, 27 Jul 2019 09:13:13 -0400 Subject: [PATCH] Inverted Mode (#5) Merging in Cassidoxa's inverted mode. I've still not fully reviewed the logic used in this mode, so it should be considered experimental, pending an in depth review by either myself or AA. --- BaseClasses.py | 30 +- Dungeons.py | 9 +- EntranceRandomizer.py | 6 +- EntranceShuffle.py | 1628 +++++++++++++++++++++++++++++++++++++++-- Gui.py | 2 +- InvertedRegions.py | 642 ++++++++++++++++ ItemList.py | 4 +- Main.py | 42 +- README.md | 12 +- Rom.py | 278 ++++++- Rules.py | 680 ++++++++++++++++- Text.py | 3 +- 12 files changed, 3223 insertions(+), 113 deletions(-) create mode 100644 InvertedRegions.py diff --git a/BaseClasses.py b/BaseClasses.py index 29b33a88..2466a3b7 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -431,7 +431,7 @@ class CollectionState(object): self.has('Bug Catching Net', player) and (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and cave.can_reach(self) and - (cave.is_light_world or self.has_Pearl(player)) + self.is_not_bunny(cave, player) ) def has_sword(self, player): @@ -455,6 +455,22 @@ class CollectionState(object): def has_fire_source(self, player): return self.has('Fire Rod', player) or self.has('Lamp', player) + def can_flute(self, player): + lw = self.world.get_region('Light World', player) + return self.has('Ocarina', player) and lw.can_reach(self) and self.is_not_bunny(lw, player) + + def can_melt_things(self, player): + return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) + + def can_avoid_lasers(self, player): + return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player) + + def is_not_bunny(self, region, player): + if self.has_Pearl(player): + return True + + return region.is_light_world if self.world.mode != 'inverted' else region.is_dark_world + def has_misery_mire_medallion(self, player): return self.has(self.world.required_medallions[player][0], player) @@ -933,9 +949,15 @@ class Spoiler(object): self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name - self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name - self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name - self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name + if self.world.mode != 'inverted': + self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name + self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name + self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name + else: + self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name + self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name + self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name + self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2" self.bosses[str(player)]["Ganon"] = "Ganon" diff --git a/Dungeons.py b/Dungeons.py index f3e13dd4..12592af7 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -19,7 +19,6 @@ def create_dungeons(world, player): EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'], ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player)) DP = make_dungeon('Desert Palace', 'Lanmolas', ['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)', 'Desert Palace East'], ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player)) ToH = make_dungeon('Tower of Hera', 'Moldorm', ['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'], ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player)) - AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', ['Palace of Darkness (Entrance)', 'Palace of Darkness (Center)', 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness (Bonk Section)', 'Palace of Darkness (North)', 'Palace of Darkness (Maze)', 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness (Final Section)'], ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player)) TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player)) SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Second Section (Drop)', 'Skull Woods Final Section (Mothula)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'], ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 2, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player)) @@ -27,7 +26,13 @@ def create_dungeons(world, player): IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player)) MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player)) TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player)) - GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) + + if world.mode != 'inverted': + AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) + GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) + else: + AT = make_dungeon('Inverted Agahnims Tower', 'Agahnim', ['Inverted Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) + GT = make_dungeon('Inverted Ganons Tower', 'Agahnim2', ['Inverted Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) GT.bosses['bottom'] = BossFactory('Armos Knights', player) GT.bosses['middle'] = BossFactory('Lanmolas', player) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index d87358e6..48127426 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -28,7 +28,7 @@ def start(): No Logic: Distribute items without regard for item requirements. ''') - parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'], + parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless', 'inverted'], help='''\ Select game mode. (default: %(default)s) Open: World starts with Zelda rescued. @@ -42,6 +42,10 @@ def start(): 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). + 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. ''') parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'], help='''\ diff --git a/EntranceShuffle.py b/EntranceShuffle.py index da460244..5718dd86 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1059,6 +1059,697 @@ def link_entrances(world, player): if world.get_entrance('Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)': world.ganonstower_vanilla[player] = False +def link_inverted_entrances(world, player): + # Link's house shuffled freely, Houlihan set in mandatory_connections + + Dungeon_Exits = Inverted_Dungeon_Exits_Base.copy() + Cave_Exits = Cave_Exits_Base.copy() + Old_Man_House = Old_Man_House_Base.copy() + Cave_Three_Exits = Cave_Three_Exits_Base.copy() + + unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits) + + # setup mandatory connections + for exitname, regionname in inverted_mandatory_connections: + connect_simple(world, exitname, regionname, player) + + # if we do not shuffle, set default connections + if world.shuffle == 'vanilla': + for exitname, regionname in inverted_default_connections: + connect_simple(world, exitname, regionname, player) + for exitname, regionname in inverted_default_dungeon_connections: + connect_simple(world, exitname, regionname, player) + elif world.shuffle == 'dungeonssimple': + for exitname, regionname in inverted_default_connections: + connect_simple(world, exitname, regionname, player) + + simple_shuffle_dungeons(world, player) + elif world.shuffle == 'dungeonsfull': + for exitname, regionname in inverted_default_connections: + connect_simple(world, exitname, regionname, player) + + skull_woods_shuffle(world, player) + + dungeon_exits = list(Dungeon_Exits) + lw_entrances = list(Inverted_LW_Dungeon_Entrances) + lw_dungeon_entrances_must_exit = list(Inverted_LW_Dungeon_Entrances_Must_Exit) + dw_entrances = list(Inverted_DW_Dungeon_Entrances) + + # randomize which desert ledge door is a must-exit + if random.randint(0, 1) == 0: + lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') + dp_must_exit = 'Desert Palace Entrance (North)' + lw_entrances.append('Desert Palace Entrance (West)') + else: + lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)') + dp_must_exit = 'Desert Palace Entrance (West)' + lw_entrances.append('Desert Palace Entrance (North)') + + dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) + lw_entrances.append('Hyrule Castle Entrance (South)') + + if not world.shuffle_ganon: + connect_two_way(world, 'Inverted Ganons Tower', 'Inverted Ganons Tower Exit', player) + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] + else: + lw_entrances.append('Inverted Ganons Tower') + dungeon_exits.append('Inverted Ganons Tower Exit') + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower'] + + # shuffle aga door first. If it's on HC ledge, remaining HC ledge door must be must-exit + all_entrances_aga = lw_entrances + dw_entrances + aga_doors = [i for i in all_entrances_aga] + random.shuffle(aga_doors) + aga_door = aga_doors.pop() + + if aga_door in hc_ledge_entrances: + lw_entrances.remove(aga_door) + hc_ledge_entrances.remove(aga_door) + + random.shuffle(hc_ledge_entrances) + hc_ledge_must_exit = hc_ledge_entrances.pop() + lw_entrances.remove(hc_ledge_must_exit) + lw_dungeon_entrances_must_exit.append(hc_ledge_must_exit) + + if aga_door in lw_entrances: + lw_entrances.remove(aga_door) + elif aga_door in dw_entrances: + dw_entrances.remove(aga_door) + + connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) + dungeon_exits.remove('Inverted Agahnims Tower Exit') + + all_dungeon_entrances = dw_entrances + lw_entrances + connect_mandatory_exits(world, all_dungeon_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player, dp_must_exit) + + remaining_dw_entrances = [i for i in all_dungeon_entrances if i in dw_entrances] + remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances] + connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, player) + + elif world.shuffle == 'simple': + simple_shuffle_dungeons(world, player) + + old_man_entrances = list(Inverted_Old_Man_Entrances) + caves = list(Cave_Exits) + three_exit_caves = list(Cave_Three_Exits) + + single_doors = list(Single_Cave_Doors) + bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors) + door_targets = list(Inverted_Single_Cave_Targets) + + # we shuffle all 2 entrance caves as pairs as a start + # start with the ones that need to be directed + two_door_caves = list(Inverted_Two_Door_Caves_Directional) + random.shuffle(two_door_caves) + random.shuffle(caves) + while two_door_caves: + entrance1, entrance2 = two_door_caves.pop() + exit1, exit2 = caves.pop() + connect_two_way(world, entrance1, exit1, player) + connect_two_way(world, entrance2, exit2, player) + + # now the remaining pairs + two_door_caves = list(Inverted_Two_Door_Caves) + random.shuffle(two_door_caves) + while two_door_caves: + entrance1, entrance2 = two_door_caves.pop() + exit1, exit2 = caves.pop() + connect_two_way(world, entrance1, exit1, player) + connect_two_way(world, entrance2, exit2, player) + + # place links house + links_house = random.choice(list(bomb_shop_doors + blacksmith_doors)) + connect_two_way(world, links_house, 'Inverted Links House Exit', player) + if links_house in bomb_shop_doors: + bomb_shop_doors.remove(links_house) + if links_house in blacksmith_doors: + blacksmith_doors.remove(links_house) + + # place dark sanc + sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors] + sanc_door = random.choice(sanc_doors) + bomb_shop_doors.remove(sanc_door) + connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) + + lw_dm_entrances = ['Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Old Man House (Bottom)', + 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave (Bottom)', 'Old Man Cave (East)', + 'Death Mountain Return Cave (East)', 'Spiral Cave', 'Old Man House (Top)', 'Spectacle Rock Cave', + 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)'] + + # place old man, bumper cave bottom to DDM entrances not in east bottom + + random.shuffle(old_man_entrances) + old_man_exit = old_man_entrances.pop() + connect_two_way(world, 'Bumper Cave (Bottom)', 'Old Man Cave Exit (West)', player) + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) + if old_man_exit == 'Spike Cave': + bomb_shop_doors.remove('Spike Cave') + bomb_shop_doors.extend(old_man_entrances) + + # add old man house to ensure it is always somewhere on light death mountain + caves.extend(list(Old_Man_House)) + caves.extend(list(three_exit_caves)) + + # connect rest + connect_caves(world, lw_dm_entrances, [], caves, player) + + # scramble holes + scramble_inverted_holes(world, player) + + # place blacksmith, has limited options + blacksmith_doors = [door for door in blacksmith_doors[:]] + random.shuffle(blacksmith_doors) + blacksmith_hut = blacksmith_doors.pop() + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) + bomb_shop_doors.extend(blacksmith_doors) + + # place bomb shop, has limited options + bomb_shop_doors = [door for door in bomb_shop_doors[:]] + random.shuffle(bomb_shop_doors) + bomb_shop = bomb_shop_doors.pop() + connect_entrance(world, bomb_shop, 'Inverted Big Bomb Shop', player) + single_doors.extend(bomb_shop_doors) + + # tavern back door cannot be shuffled yet + connect_doors(world, ['Tavern North'], ['Tavern'], player) + + # place remaining doors + connect_doors(world, single_doors, door_targets, player) + + elif world.shuffle == 'restricted': + simple_shuffle_dungeons(world, player) + + lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors) + dw_entrances = list(Inverted_DW_Entrances + Inverted_DW_Single_Cave_Doors + Inverted_Old_Man_Entrances) + lw_must_exits = list(Inverted_LW_Entrances_Must_Exit) + old_man_entrances = list(Inverted_Old_Man_Entrances) + caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House) + single_doors = list(Single_Cave_Doors) + bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + door_targets = list(Inverted_Single_Cave_Targets) + + # place links house + links_house = random.choice(list(lw_entrances + dw_entrances + lw_must_exits)) + connect_two_way(world, links_house, 'Inverted Links House Exit', player) + if links_house in lw_entrances: + lw_entrances.remove(links_house) + elif links_house in dw_entrances: + dw_entrances.remove(links_house) + elif links_house in lw_must_exits: + lw_must_exits.remove(links_house) + + # place dark sanc + sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances] + sanc_door = random.choice(sanc_doors) + dw_entrances.remove(sanc_door) + connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) + + # tavern back door cannot be shuffled yet + connect_doors(world, ['Tavern North'], ['Tavern'], player) + + # place must exits + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) + + # place old man, has limited options + # exit has to come from specific set of doors, the entrance is free to move about + old_man_entrances = [door for door in old_man_entrances if door in dw_entrances] + random.shuffle(old_man_entrances) + old_man_exit = old_man_entrances.pop() + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) + dw_entrances.remove(old_man_exit) + + # place blacksmith, has limited options + all_entrances = lw_entrances + dw_entrances + # cannot place it anywhere already taken (or that are otherwise not eligible for placement) + blacksmith_doors = [door for door in blacksmith_doors if door in all_entrances] + random.shuffle(blacksmith_doors) + blacksmith_hut = blacksmith_doors.pop() + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) + if blacksmith_hut in lw_entrances: + lw_entrances.remove(blacksmith_hut) + if blacksmith_hut in dw_entrances: + dw_entrances.remove(blacksmith_hut) + bomb_shop_doors.extend(blacksmith_doors) + + # place bomb shop, has limited options + all_entrances = lw_entrances + dw_entrances + # cannot place it anywhere already taken (or that are otherwise not eligible for placement) + bomb_shop_doors = [door for door in bomb_shop_doors if door in all_entrances] + random.shuffle(bomb_shop_doors) + bomb_shop = bomb_shop_doors.pop() + connect_entrance(world, bomb_shop, 'Inverted Big Bomb Shop', player) + if bomb_shop in lw_entrances: + lw_entrances.remove(bomb_shop) + if bomb_shop in dw_entrances: + dw_entrances.remove(bomb_shop) + + # place the old man cave's entrance somewhere in the dark world + random.shuffle(dw_entrances) + old_man_entrance = dw_entrances.pop() + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + + # now scramble the rest + connect_caves(world, lw_entrances, dw_entrances, caves, player) + + # scramble holes + scramble_inverted_holes(world, player) + + doors = lw_entrances + dw_entrances + # place remaining doors + connect_doors(world, doors, door_targets, player) + elif world.shuffle == 'full': + skull_woods_shuffle(world, player) + + lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors) + dw_entrances = list(Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors + Inverted_Old_Man_Entrances) + lw_must_exits = list(Inverted_LW_Dungeon_Entrances_Must_Exit + Inverted_LW_Entrances_Must_Exit) + old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera']) + caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues + bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + door_targets = list(Inverted_Single_Cave_Targets) + old_man_house = list(Old_Man_House) + + # randomize which desert ledge door is a must-exit + if random.randint(0, 1) == 0: + lw_must_exits.append('Desert Palace Entrance (North)') + dp_must_exit = 'Desert Palace Entrance (North)' + lw_entrances.append('Desert Palace Entrance (West)') + else: + lw_must_exits.append('Desert Palace Entrance (West)') + dp_must_exit = 'Desert Palace Entrance (West)' + lw_entrances.append('Desert Palace Entrance (North)') + + # tavern back door cannot be shuffled yet + connect_doors(world, ['Tavern North'], ['Tavern'], player) + + caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) + lw_entrances.append('Hyrule Castle Entrance (South)') + + + if not world.shuffle_ganon: + connect_two_way(world, 'Inverted Ganons Tower', 'Inverted Ganons Tower Exit', player) + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] + else: + lw_entrances.append('Inverted Ganons Tower') + caves.append('Inverted Ganons Tower Exit') + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower'] + + # shuffle aga door first. if it's on hc ledge, then one other hc ledge door has to be must_exit + all_entrances_aga = lw_entrances + dw_entrances + aga_doors = [i for i in all_entrances_aga] + random.shuffle(aga_doors) + aga_door = aga_doors.pop() + + if aga_door in hc_ledge_entrances: + lw_entrances.remove(aga_door) + hc_ledge_entrances.remove(aga_door) + + random.shuffle(hc_ledge_entrances) + hc_ledge_must_exit = hc_ledge_entrances.pop() + lw_entrances.remove(hc_ledge_must_exit) + lw_must_exits.append(hc_ledge_must_exit) + + if aga_door in lw_entrances: + lw_entrances.remove(aga_door) + elif aga_door in dw_entrances: + dw_entrances.remove(aga_door) + + connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) + caves.remove('Inverted Agahnims Tower Exit') + + # place links house + links_house_doors = [door for door in lw_entrances + dw_entrances + lw_must_exits] + links_house = random.choice(links_house_doors) + connect_two_way(world, links_house, 'Inverted Links House Exit', player) + if links_house in lw_entrances: + lw_entrances.remove(links_house) + if links_house in dw_entrances: + dw_entrances.remove(links_house) + if links_house in lw_must_exits: + lw_must_exits.remove(links_house) + + # place dark sanc + sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances] + sanc_door = random.choice(sanc_doors) + dw_entrances.remove(sanc_door) + connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) + + # place old man house + # no dw must exits in inverted, but we randomize whether cave is in light or dark world + if random.randint(0, 1) == 0: + caves += old_man_house + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player, dp_must_exit) + try: + caves.remove(old_man_house[0]) + except ValueError: + pass + else: #if the cave wasn't placed we get here + connect_caves(world, lw_entrances, [], old_man_house, player) + else: + connect_caves(world, dw_entrances, [], old_man_house, player) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player, dp_must_exit) + + # place old man, has limited options + # exit has to come from specific set of doors, the entrance is free to move about + old_man_entrances = [door for door in old_man_entrances if door in dw_entrances + lw_entrances] + random.shuffle(old_man_entrances) + old_man_exit = old_man_entrances.pop() + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) + if old_man_exit in dw_entrances: + dw_entrances.remove(old_man_exit) + old_man_world = 'dark' + elif old_man_exit in lw_entrances: + lw_entrances.remove(old_man_exit) + old_man_world = 'light' + + # place blacksmith, has limited options + all_entrances = lw_entrances + dw_entrances + # cannot place it anywhere already taken (or that are otherwise not eligible for placement) + blacksmith_doors = [door for door in blacksmith_doors if door in all_entrances] + random.shuffle(blacksmith_doors) + blacksmith_hut = blacksmith_doors.pop() + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) + if blacksmith_hut in lw_entrances: + lw_entrances.remove(blacksmith_hut) + if blacksmith_hut in dw_entrances: + dw_entrances.remove(blacksmith_hut) + bomb_shop_doors.extend(blacksmith_doors) + + # place bomb shop, has limited options + all_entrances = lw_entrances + dw_entrances + # cannot place it anywhere already taken (or that are otherwise not eligible for placement) + bomb_shop_doors = [door for door in bomb_shop_doors if door in all_entrances] + random.shuffle(bomb_shop_doors) + bomb_shop = bomb_shop_doors.pop() + connect_entrance(world, bomb_shop, 'Inverted Big Bomb Shop', player) + if bomb_shop in lw_entrances: + lw_entrances.remove(bomb_shop) + if bomb_shop in dw_entrances: + dw_entrances.remove(bomb_shop) + + # place the old man cave's entrance somewhere in the same world he'll exit from + if old_man_world == 'light': + random.shuffle(lw_entrances) + old_man_entrance = lw_entrances.pop() + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + elif old_man_world == 'dark': + random.shuffle(dw_entrances) + old_man_entrance = dw_entrances.pop() + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + + # now scramble the rest + connect_caves(world, lw_entrances, dw_entrances, caves, player) + + # scramble holes + scramble_inverted_holes(world, player) + + doors = lw_entrances + dw_entrances + + # place remaining doors + connect_doors(world, doors, door_targets, player) + elif world.shuffle == 'crossed': + skull_woods_shuffle(world, player) + + entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors) + must_exits = list(Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit) + + old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera']) + caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues + bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + door_targets = list(Inverted_Single_Cave_Targets) + + # randomize which desert ledge door is a must-exit + if random.randint(0, 1) == 0: + must_exits.append('Desert Palace Entrance (North)') + dp_must_exit = 'Desert Palace Entrance (North)' + entrances.append('Desert Palace Entrance (West)') + else: + must_exits.append('Desert Palace Entrance (West)') + dp_must_exit = 'Desert Palace Entrance (West)' + entrances.append('Desert Palace Entrance (North)') + + caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) + entrances.append('Hyrule Castle Entrance (South)') + + if not world.shuffle_ganon: + connect_two_way(world, 'Inverted Ganons Tower', 'Inverted Ganons Tower Exit', player) + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] + else: + entrances.append('Inverted Ganons Tower') + caves.append('Inverted Ganons Tower Exit') + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower'] + + # shuffle aga door. if it's on hc ledge, then one other hc ledge door has to be must_exit + aga_door = random.choice(list(entrances)) + + if aga_door in hc_ledge_entrances: + hc_ledge_entrances.remove(aga_door) + + random.shuffle(hc_ledge_entrances) + hc_ledge_must_exit = hc_ledge_entrances.pop() + entrances.remove(hc_ledge_must_exit) + must_exits.append(hc_ledge_must_exit) + + entrances.remove(aga_door) + connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) + caves.remove('Inverted Agahnims Tower Exit') + + + # place links house + links_house = random.choice(list(entrances + must_exits)) + connect_two_way(world, links_house, 'Inverted Links House Exit', player) + if links_house in entrances: + entrances.remove(links_house) + elif links_house in must_exits: + must_exits.remove(links_house) + + # place dark sanc + sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances] + sanc_door = random.choice(sanc_doors) + entrances.remove(sanc_door) + connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) + + # tavern back door cannot be shuffled yet + connect_doors(world, ['Tavern North'], ['Tavern'], player) + + + #place must-exit caves + connect_mandatory_exits(world, entrances, caves, must_exits, player, dp_must_exit) + + + # place old man, has limited options + # exit has to come from specific set of doors, the entrance is free to move about + old_man_entrances = [door for door in old_man_entrances if door in entrances] + random.shuffle(old_man_entrances) + old_man_exit = old_man_entrances.pop() + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) + entrances.remove(old_man_exit) + + # place blacksmith, has limited options + # cannot place it anywhere already taken (or that are otherwise not eligible for placement) + blacksmith_doors = [door for door in blacksmith_doors if door in entrances] + random.shuffle(blacksmith_doors) + blacksmith_hut = blacksmith_doors.pop() + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) + entrances.remove(blacksmith_hut) + + # place bomb shop, has limited options + + # cannot place it anywhere already taken (or that are otherwise not eligible for placement) + bomb_shop_doors = [door for door in bomb_shop_doors if door in entrances] + random.shuffle(bomb_shop_doors) + bomb_shop = bomb_shop_doors.pop() + connect_entrance(world, bomb_shop, 'Inverted Big Bomb Shop', player) + entrances.remove(bomb_shop) + + # place the old man cave's entrance somewhere + random.shuffle(entrances) + old_man_entrance = entrances.pop() + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + + # now scramble the rest + connect_caves(world, entrances, [], caves, player) + + # scramble holes + scramble_inverted_holes(world, player) + + # place remaining doors + connect_doors(world, entrances, door_targets, player) + elif world.shuffle == 'insanity': + # beware ye who enter here + + entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)'] + entrances_must_exits = Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit + + doors = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Secret Entrance Stairs'] + Inverted_Old_Man_Entrances +\ + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] +\ + Inverted_LW_Single_Cave_Doors + Inverted_DW_Single_Cave_Doors + ['Desert Palace Entrance (West)', 'Desert Palace Entrance (North)'] + + # randomize which desert ledge door is a must-exit + if random.randint(0, 1) == 0: + entrances_must_exits.append('Desert Palace Entrance (North)') + entrances.append('Desert Palace Entrance (West)') + else: + entrances_must_exits.append('Desert Palace Entrance (West)') + entrances.append('Desert Palace Entrance (North)') + + # TODO: there are other possible entrances we could support here by way of exiting from a connector, + # and rentering to find bomb shop. However appended list here is all those that we currently have + # bomb shop logic for. + # Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits + bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance']) + blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors) + door_targets = list(Inverted_Single_Cave_Targets) + + random.shuffle(doors) + + old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances) + ['Tower of Hera', 'Inverted Agahnims Tower'] + + caves = Cave_Exits + Dungeon_Exits + Cave_Three_Exits + ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', + 'Kakariko Well Exit', 'Bat Cave Exit', 'North Fairy Cave Exit', 'Lost Woods Hideout Exit', 'Lumberjack Tree Exit', 'Sanctuary Exit'] + + + # shuffle up holes + hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', + 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] + + hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)', + 'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)'] + + # tavern back door cannot be shuffled yet + connect_doors(world, ['Tavern North'], ['Tavern'], player) + + hole_entrances.append('Hyrule Castle Secret Entrance Drop') + hole_targets.append('Hyrule Castle Secret Entrance') + entrances.append('Hyrule Castle Secret Entrance Stairs') + caves.append('Hyrule Castle Secret Entrance Exit') + + if not world.shuffle_ganon: + connect_two_way(world, 'Inverted Ganons Tower', 'Inverted Ganons Tower Exit', player) + connect_two_way(world, 'Inverted Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Inverted Pyramid Hole', 'Pyramid', player) + else: + entrances.append('Inverted Ganons Tower') + caves.extend(['Inverted Ganons Tower Exit', 'Pyramid Exit']) + hole_entrances.append('Inverted Pyramid Hole') + hole_targets.append('Pyramid') + doors.extend(['Inverted Ganons Tower', 'Inverted Pyramid Entrance']) + + random.shuffle(hole_entrances) + random.shuffle(hole_targets) + random.shuffle(entrances) + + # fill up holes + for hole in hole_entrances: + connect_entrance(world, hole, hole_targets.pop(), player) + + doors.append('Hyrule Castle Entrance (South)') + caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) + + # place links house and dark sanc + links_house = random.choice(list(entrances + entrances_must_exits)) + connect_two_way(world, links_house, 'Inverted Links House Exit', player) + if links_house in entrances: + entrances.remove(links_house) + elif links_house in entrances_must_exits: + entrances_must_exits.remove(links_house) + doors.remove(links_house) + + sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances] + sanc_door = random.choice(sanc_doors) + entrances.remove(sanc_door) + doors.remove(sanc_door) + connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) + + # now let's deal with mandatory reachable stuff + def extract_reachable_exit(cavelist): + random.shuffle(cavelist) + candidate = None + for cave in cavelist: + if isinstance(cave, tuple) and len(cave) > 1: + # special handling: TRock has two entries that we should consider entrance only + # ToDo this should be handled in a more sensible manner + if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2: + continue + candidate = cave + break + if candidate is None: + raise RuntimeError('No suitable cave.') + cavelist.remove(candidate) + return candidate + + def connect_reachable_exit(entrance, caves, doors): + cave = extract_reachable_exit(caves) + + exit = cave[-1] + cave = cave[:-1] + connect_exit(world, exit, entrance, player) + connect_entrance(world, doors.pop(), exit, player) + # rest of cave now is forced to be in this world + caves.append(cave) + + # connect mandatory exits + for entrance in entrances_must_exits: + connect_reachable_exit(entrance, caves, doors) + + # place old man, has limited options + # exit has to come from specific set of doors, the entrance is free to move about + old_man_entrances = [entrance for entrance in old_man_entrances if entrance in entrances] + random.shuffle(old_man_entrances) + old_man_exit = old_man_entrances.pop() + entrances.remove(old_man_exit) + + connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) + connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player) + caves.append('Old Man Cave Exit (West)') + + # place blacksmith, has limited options + blacksmith_doors = [door for door in blacksmith_doors if door in doors] + random.shuffle(blacksmith_doors) + blacksmith_hut = blacksmith_doors.pop() + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) + doors.remove(blacksmith_hut) + + # place dam and pyramid fairy, have limited options + bomb_shop_doors = [door for door in bomb_shop_doors if door in doors] + random.shuffle(bomb_shop_doors) + bomb_shop = bomb_shop_doors.pop() + connect_entrance(world, bomb_shop, 'Inverted Big Bomb Shop', player) + doors.remove(bomb_shop) + + # handle remaining caves + for cave in caves: + if isinstance(cave, str): + cave = (cave,) + + for exit in cave: + connect_exit(world, exit, entrances.pop(), player) + connect_entrance(world, doors.pop(), exit, player) + + # place remaining doors + connect_doors(world, doors, door_targets, player) + else: + raise NotImplementedError('Shuffling not supported yet') + + # patch swamp drain + if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': + world.swamp_patch_required[player] = True + + # check for potion shop location + if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop': + world.powder_patch_required[player] = True + + # check for ganon location + if world.get_entrance('Inverted Pyramid Hole', player).connected_region.name != 'Hyrule Castle Ledge': + world.ganon_at_pyramid[player] = False + + # check for Ganon's Tower location + if world.get_entrance('Inverted Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)': + world.ganonstower_vanilla[player] = False def connect_simple(world, exitname, regionname, player): world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) @@ -1084,7 +1775,6 @@ def connect_entrance(world, entrancename, exitname, player): entrance.connect(region, addresses, target) world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) - def connect_exit(world, exitname, entrancename, player): entrance = world.get_entrance(entrancename, player) exit = world.get_entrance(exitname, player) @@ -1158,6 +1848,47 @@ def scramble_holes(world, player): connect_entrance(world, drop, target, player) +def scramble_inverted_holes(world, player): + hole_entrances = [('Kakariko Well Cave', 'Kakariko Well Drop'), + ('Bat Cave Cave', 'Bat Cave Drop'), + ('North Fairy Cave', 'North Fairy Cave Drop'), + ('Lost Woods Hideout Stump', 'Lost Woods Hideout Drop'), + ('Lumberjack Tree Cave', 'Lumberjack Tree Tree'), + ('Sanctuary', 'Sanctuary Grave')] + + hole_targets = [('Kakariko Well Exit', 'Kakariko Well (top)'), + ('Bat Cave Exit', 'Bat Cave (right)'), + ('North Fairy Cave Exit', 'North Fairy Cave'), + ('Lost Woods Hideout Exit', 'Lost Woods Hideout (top)'), + ('Lumberjack Tree Exit', 'Lumberjack Tree (top)')] + + if not world.shuffle_ganon: + connect_two_way(world, 'Inverted Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Inverted Pyramid Hole', 'Pyramid', player) + else: + hole_targets.append(('Pyramid Exit', 'Pyramid')) + + + hole_entrances.append(('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Drop')) + hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) + + # do not shuffle sanctuary into pyramid hole unless shuffle is crossed + if world.shuffle == 'crossed': + hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) + if world.shuffle_ganon: + random.shuffle(hole_targets) + exit, target = hole_targets.pop() + connect_two_way(world, 'Inverted Pyramid Entrance', exit, player) + connect_entrance(world, 'Inverted Pyramid Hole', target, player) + if world.shuffle != 'crossed': + hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) + + random.shuffle(hole_targets) + for entrance, drop in hole_entrances: + exit, target = hole_targets.pop() + connect_two_way(world, entrance, exit, player) + connect_entrance(world, drop, target, player) + def connect_random(world, exitlist, targetlist, player, two_way=False): targetlist = list(targetlist) random.shuffle(targetlist) @@ -1169,7 +1900,7 @@ def connect_random(world, exitlist, targetlist, player, two_way=False): connect_entrance(world, exit, target, player) -def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): +def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_must_exit=None): """This works inplace""" random.shuffle(entrances) random.shuffle(caves) @@ -1190,8 +1921,12 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): connect_two_way(world, exit, cave[-1], player) if len(cave) == 2: entrance = entrances.pop() - # ToDo Better solution, this is a hot fix. Do not connect both sides of trock ledge only to each other - if entrance == 'Dark Death Mountain Ledge (West)': + # ToDo Better solution, this is a hot fix. Do not connect both sides of trock/desert ledge only to each other + if world.mode != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)': + new_entrance = entrances.pop() + entrances.append(entrance) + entrance = new_entrance + if world.mode == 'inverted' and entrance == dp_must_exit: new_entrance = entrances.pop() entrances.append(entrance) entrance = new_entrance @@ -1262,77 +1997,131 @@ def simple_shuffle_dungeons(world, player): dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace'] dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit'] - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) + if world.mode != 'inverted': + if not world.shuffle_ganon: + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) + else: + dungeon_entrances.append('Ganons Tower') + dungeon_exits.append('Ganons Tower Exit') else: - dungeon_entrances.append('Ganons Tower') - dungeon_exits.append('Ganons Tower Exit') + dungeon_entrances.append('Inverted Agahnims Tower') + dungeon_exits.append('Inverted Agahnims Tower Exit') # shuffle up single entrance dungeons connect_random(world, dungeon_entrances, dungeon_exits, player, True) # mix up 4 door dungeons multi_dungeons = ['Desert', 'Turtle Rock'] - if world.mode == 'open': + if world.mode == 'open' or (world.mode == 'inverted' and world.shuffle_ganon): multi_dungeons.append('Hyrule Castle') random.shuffle(multi_dungeons) dp_target = multi_dungeons[0] tr_target = multi_dungeons[1] - if world.mode != 'open': + if world.mode not in ['open', 'inverted'] or (world.mode == 'inverted' and world.shuffle_ganon is False): # place hyrule castle as intended hc_target = 'Hyrule Castle' else: hc_target = multi_dungeons[2] # ToDo improve this? - if hc_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)', player) - connect_two_way(world, 'Agahnims Tower', 'Agahnims Tower Exit', player) - elif hc_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_two_way(world, 'Desert Palace Entrance (East)', 'Hyrule Castle Exit (East)', player) - connect_two_way(world, 'Desert Palace Entrance (West)', 'Hyrule Castle Exit (West)', player) - connect_two_way(world, 'Desert Palace Entrance (North)', 'Agahnims Tower Exit', player) - elif hc_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Hyrule Castle Exit (South)', player) - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Hyrule Castle Exit (East)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Hyrule Castle Exit (West)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Agahnims Tower Exit', player) - if dp_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Desert Palace Exit (South)', player) - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Desert Palace Exit (East)', player) - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Desert Palace Exit (West)', player) - connect_two_way(world, 'Agahnims Tower', 'Desert Palace Exit (North)', player) - elif dp_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Desert Palace Exit (South)', player) - connect_two_way(world, 'Desert Palace Entrance (East)', 'Desert Palace Exit (East)', player) - connect_two_way(world, 'Desert Palace Entrance (West)', 'Desert Palace Exit (West)', player) - connect_two_way(world, 'Desert Palace Entrance (North)', 'Desert Palace Exit (North)', player) - elif dp_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Desert Palace Exit (South)', player) - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Desert Palace Exit (East)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Desert Palace Exit (West)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Desert Palace Exit (North)', player) + if world.mode != 'inverted': + if hc_target == 'Hyrule Castle': + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Agahnims Tower', 'Agahnims Tower Exit', player) + elif hc_target == 'Desert': + connect_two_way(world, 'Desert Palace Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Agahnims Tower Exit', player) + elif hc_target == 'Turtle Rock': + connect_two_way(world, 'Turtle Rock', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Agahnims Tower Exit', player) - if tr_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Turtle Rock Exit (Front)', player) - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Turtle Rock Ledge Exit (East)', player) - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) - connect_two_way(world, 'Agahnims Tower', 'Turtle Rock Isolated Ledge Exit', player) - elif tr_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Turtle Rock Exit (Front)', player) - connect_two_way(world, 'Desert Palace Entrance (North)', 'Turtle Rock Ledge Exit (East)', player) - connect_two_way(world, 'Desert Palace Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) - connect_two_way(world, 'Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Exit', player) - elif tr_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Turtle Rock Exit (Front)', player) - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit', player) - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) + if dp_target == 'Hyrule Castle': + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Agahnims Tower', 'Desert Palace Exit (North)', player) + elif dp_target == 'Desert': + connect_two_way(world, 'Desert Palace Entrance (South)', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Desert Palace Exit (North)', player) + elif dp_target == 'Turtle Rock': + connect_two_way(world, 'Turtle Rock', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Desert Palace Exit (North)', player) + + if tr_target == 'Hyrule Castle': + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Turtle Rock Ledge Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Agahnims Tower', 'Turtle Rock Isolated Ledge Exit', player) + elif tr_target == 'Desert': + connect_two_way(world, 'Desert Palace Entrance (South)', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Turtle Rock Ledge Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Exit', player) + elif tr_target == 'Turtle Rock': + connect_two_way(world, 'Turtle Rock', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) + else: + if hc_target == 'Hyrule Castle': + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Inverted Ganons Tower', 'Inverted Ganons Tower Exit', player) + elif hc_target == 'Desert': + connect_two_way(world, 'Desert Palace Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Inverted Ganons Tower Exit', player) + elif hc_target == 'Turtle Rock': + connect_two_way(world, 'Turtle Rock', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Inverted Ganons Tower Exit', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Hyrule Castle Exit (East)', player) + + if dp_target == 'Hyrule Castle': + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Inverted Ganons Tower', 'Desert Palace Exit (North)', player) + elif dp_target == 'Desert': + connect_two_way(world, 'Desert Palace Entrance (South)', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Desert Palace Exit (North)', player) + elif dp_target == 'Turtle Rock': + connect_two_way(world, 'Turtle Rock', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Desert Palace Exit (North)', player) + + if tr_target == 'Hyrule Castle': + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Turtle Rock Ledge Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Inverted Ganons Tower', 'Turtle Rock Isolated Ledge Exit', player) + elif tr_target == 'Desert': + connect_two_way(world, 'Desert Palace Entrance (South)', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Turtle Rock Ledge Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Exit', player) + elif tr_target == 'Turtle Rock': + connect_two_way(world, 'Turtle Rock', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) def unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits): def shuffle_lists_in_list(ls): @@ -1731,6 +2520,308 @@ Single_Cave_Targets = ['Blinds Hideout', 'Kakariko Gamble Game', 'Dam'] +Inverted_LW_Dungeon_Entrances = ['Desert Palace Entrance (South)', + 'Eastern Palace', + 'Tower of Hera', + 'Hyrule Castle Entrance (West)', + 'Hyrule Castle Entrance (East)'] + +Inverted_DW_Dungeon_Entrances = ['Thieves Town', + 'Skull Woods Final Section', + 'Ice Palace', + 'Misery Mire', + 'Palace of Darkness', + 'Swamp Palace', + 'Turtle Rock', + 'Dark Death Mountain Ledge (West)', + 'Dark Death Mountain Ledge (East)', + 'Turtle Rock Isolated Ledge Entrance', + 'Inverted Agahnims Tower'] + +Inverted_LW_Dungeon_Entrances_Must_Exit = ['Desert Palace Entrance (East)'] + +Inverted_Dungeon_Exits_Base = [['Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)'], + 'Desert Palace Exit (North)', + 'Eastern Palace Exit', + 'Tower of Hera Exit', + 'Thieves Town Exit', + 'Skull Woods Final Section Exit', + 'Ice Palace Exit', + 'Misery Mire Exit', + 'Palace of Darkness Exit', + 'Swamp Palace Exit', + 'Inverted Agahnims Tower Exit', + ['Turtle Rock Ledge Exit (East)', + 'Turtle Rock Exit (Front)', 'Turtle Rock Ledge Exit (West)', 'Turtle Rock Isolated Ledge Exit']] + +Inverted_LW_Entrances_Must_Exit = ['Death Mountain Return Cave (West)', + 'Two Brothers House (West)'] + +Inverted_Two_Door_Caves_Directional = [('Old Man Cave (West)', 'Death Mountain Return Cave (West)'), + ('Two Brothers House (East)', 'Two Brothers House (West)')] + + +Inverted_Two_Door_Caves = [('Elder House (East)', 'Elder House (West)'), + ('Superbunny Cave (Bottom)', 'Superbunny Cave (Top)'), + ('Hookshot Cave', 'Hookshot Cave Back Entrance')] + + + +Inverted_Old_Man_Entrances = ['Dark Death Mountain Fairy', + 'Spike Cave'] + +Inverted_LW_Entrances = ['Elder House (East)', + 'Elder House (West)', + 'Two Brothers House (East)', + 'Old Man Cave (East)', + 'Old Man Cave (West)', + 'Old Man House (Bottom)', + 'Old Man House (Top)', + 'Death Mountain Return Cave (East)', + 'Paradox Cave (Bottom)', + 'Paradox Cave (Middle)', + 'Paradox Cave (Top)', + 'Spectacle Rock Cave', + 'Spectacle Rock Cave Peak', + 'Spectacle Rock Cave (Bottom)', + 'Fairy Ascension Cave (Bottom)', + 'Fairy Ascension Cave (Top)', + 'Spiral Cave', + 'Spiral Cave (Bottom)'] + + +Inverted_DW_Entrances = ['Bumper Cave (Bottom)', + 'Superbunny Cave (Top)', + 'Superbunny Cave (Bottom)', + 'Hookshot Cave', + 'Hookshot Cave Back Entrance'] + +Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)', + 'Misery Mire', + 'Thieves Town', + 'Bumper Cave (Bottom)', + 'Swamp Palace', + 'Hyrule Castle Secret Entrance Stairs', + 'Skull Woods First Section Door', + 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)', + 'Skull Woods Final Section', + 'Ice Palace', + 'Turtle Rock', + 'Dark Death Mountain Ledge (West)', + 'Dark Death Mountain Ledge (East)', + 'Superbunny Cave (Top)', + 'Superbunny Cave (Bottom)', + 'Hookshot Cave', + 'Inverted Agahnims Tower', + 'Desert Palace Entrance (South)', + 'Tower of Hera', + 'Two Brothers House (West)', + 'Old Man Cave (East)', + 'Old Man House (Bottom)', + 'Old Man House (Top)', + 'Death Mountain Return Cave (East)', + 'Death Mountain Return Cave (West)', + 'Spectacle Rock Cave Peak', + 'Spectacle Rock Cave', + 'Spectacle Rock Cave (Bottom)', + 'Paradox Cave (Bottom)', + 'Paradox Cave (Middle)', + 'Paradox Cave (Top)', + 'Fairy Ascension Cave (Bottom)', + 'Fairy Ascension Cave (Top)', + 'Spiral Cave', + 'Spiral Cave (Bottom)', + 'Palace of Darkness', + 'Hyrule Castle Entrance (West)', + 'Hyrule Castle Entrance (East)', + 'Inverted Ganons Tower', + 'Desert Palace Entrance (West)', + 'Desert Palace Entrance (North)'] + +Inverted_Blacksmith_Multi_Cave_Doors = [] # same as non-inverted + +Inverted_LW_Single_Cave_Doors = LW_Single_Cave_Doors + ['Inverted Big Bomb Shop'] + +Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)', + 'Inverted Dark Sanctuary', + 'Inverted Links House', + 'Dark Lake Hylia Fairy', + 'C-Shaped House', + 'Bumper Cave (Top)', + 'Dark Lake Hylia Shop', + 'Dark World Shop', + 'Red Shield Shop', + 'Mire Shed', + 'East Dark World Hint', + 'Dark Desert Hint', + 'Palace of Darkness Hint', + 'Dark Lake Hylia Ledge Spike Cave', + 'Cave Shop (Dark Death Mountain)', + 'Dark World Potion Shop', + 'Pyramid Fairy', + 'Archery Game', + 'Dark World Lumberjack Shop', + 'Hype Cave', + 'Brewery', + 'Dark Lake Hylia Ledge Hint', + 'Chest Game', + 'Dark Desert Fairy', + 'Dark Lake Hylia Ledge Fairy', + 'Fortune Teller (Dark)', + 'Dark World Hammer Peg Cave'] + + +Inverted_Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing', + 'Capacity Upgrade', + 'Bonk Rock Cave', + 'Graveyard Cave', + 'Checkerboard Cave', + 'Cave 45', + 'Kings Grave', + 'Bonk Fairy (Light)', + 'Hookshot Fairy', + 'East Dark World Hint', + 'Palace of Darkness Hint', + 'Dark Lake Hylia Fairy', + 'Dark Lake Hylia Ledge Fairy', + 'Dark Lake Hylia Ledge Spike Cave', + 'Dark Lake Hylia Ledge Hint', + 'Hype Cave', + 'Bonk Fairy (Dark)', + 'Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Hammer Peg Cave', + 'Red Shield Shop', + 'Inverted Dark Sanctuary', + 'Fortune Teller (Dark)', + 'Dark World Shop', + 'Dark World Lumberjack Shop', + 'Dark World Potion Shop', + 'Archery Game', + 'Mire Shed', + 'Dark Desert Hint', + 'Dark Desert Fairy', + 'Spike Cave', + 'Cave Shop (Dark Death Mountain)', + 'Bumper Cave (Top)', + 'Mimic Cave', + 'Dark Lake Hylia Shop', + 'Inverted Links House'] + +Inverted_Blacksmith_Single_Cave_Doors = ['Blinds Hideout', + 'Lake Hylia Fairy', + 'Light Hype Fairy', + 'Desert Fairy', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Blacksmiths Hut', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Fortune Teller (Light)', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Library', + 'Potion Shop', + 'Dam', + 'Lumberjack House', + 'Lake Hylia Fortune Teller', + 'Kakariko Gamble Game', + 'Inverted Big Bomb Shop'] + + +Inverted_Single_Cave_Targets = ['Blinds Hideout', + 'Bonk Fairy (Light)', + 'Lake Hylia Healer Fairy', + 'Swamp Healer Fairy', + 'Desert Healer Fairy', + 'Kings Grave', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Fortune Teller (Light)', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Cave 45', + 'Graveyard Cave', + 'Checkerboard Cave', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Bonk Rock Cave', + 'Library', + 'Potion Shop', + 'Hookshot Fairy', + 'Waterfall of Wishing', + 'Capacity Upgrade', + 'Pyramid Fairy', + 'East Dark World Hint', + 'Palace of Darkness Hint', + 'Dark Lake Hylia Healer Fairy', + 'Dark Lake Hylia Ledge Healer Fairy', + 'Dark Lake Hylia Ledge Spike Cave', + 'Dark Lake Hylia Ledge Hint', + 'Hype Cave', + 'Bonk Fairy (Dark)', + 'Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Hammer Peg Cave', + 'Red Shield Shop', + 'Fortune Teller (Dark)', + 'Village of Outcasts Shop', + 'Dark Lake Hylia Shop', + 'Dark World Lumberjack Shop', + 'Archery Game', + 'Mire Shed', + 'Dark Desert Hint', + 'Dark Desert Healer Fairy', + 'Spike Cave', + 'Cave Shop (Dark Death Mountain)', + 'Dark Death Mountain Healer Fairy', + 'Mimic Cave', + 'Dark World Potion Shop', + 'Lumberjack House', + 'Lake Hylia Fortune Teller', + 'Kakariko Gamble Game', + 'Dam'] + +# in inverted we put dark sanctuary in west dark world for now +Inverted_Dark_Sanctuary_Doors = ['Inverted Dark Sanctuary', + 'Fortune Teller (Dark)', + 'Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Lumberjack Shop', + 'Red Shield Shop', + 'Bumper Cave (Bottom)', + 'Bumper Cave (Top)', + 'Skull Woods Final Section', + 'Thieves Town'] + # 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 = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'), @@ -1909,6 +3000,221 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central ('Pyramid Drop', 'East Dark World') ] +inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), + ('Zoras River', 'Zoras River'), + ('Kings Grave Outer Rocks', 'Kings Grave Area'), + ('Kings Grave Inner Rocks', 'Light World'), + ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), + ('Master Sword Meadow', 'Master Sword Meadow'), + ('Hobo Bridge', 'Hobo Bridge'), + ('Desert Palace East Wing', 'Desert Palace East'), + ('Bat Cave Drop Ledge', 'Bat Cave Drop Ledge'), + ('Bat Cave Door', 'Bat Cave (left)'), + ('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'), + ('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'), + ('Desert Palace Stairs', 'Desert Palace Stairs'), + ('Desert Palace Stairs Drop', 'Light World'), + ('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'), + ('Desert Ledge Return Rocks', 'Desert Ledge'), + ('Throne Room', 'Sewers (Dark)'), ('Sewers Door', 'Sewers'), + ('Sanctuary Push Door', 'Sanctuary'), + ('Sewer Drop', 'Sewers'), + ('Sewers Back Door', 'Sewers (Dark)'), + ('Agahnim 1', 'Agahnim 1'), + ('Death Mountain Entrance Rock', 'Death Mountain Entrance'), + ('Death Mountain Entrance Drop', 'Light World'), + ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'), + ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'), + ('Death Mountain Return Ledge Drop', 'Light World'), + ('Old Man House Front to Back', 'Old Man House Back'), + ('Old Man House Back to Front', 'Old Man House'), + ('Broken Bridge (West)', 'East Death Mountain (Bottom)'), + ('Broken Bridge (East)', 'Death Mountain'), + ('East Death Mountain Drop', 'East Death Mountain (Bottom)'), + ('Spiral Cave Ledge Access', 'Spiral Cave Ledge'), + ('Spiral Cave Ledge Drop', 'East Death Mountain (Bottom)'), + ('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'), + ('East Death Mountain (Top)', 'East Death Mountain (Top)'), + ('Death Mountain (Top)', 'Death Mountain (Top)'), + ('Death Mountain Drop', 'Death Mountain'), + ('Tower of Hera Small Key Door', 'Tower of Hera (Basement)'), + ('Tower of Hera Big Key Door', 'Tower of Hera (Top)'), + ('Dark Lake Hylia Drop (East)', 'Dark Lake Hylia'), + ('Dark Lake Hylia Drop (South)', 'Dark Lake Hylia'), + ('Dark Lake Hylia Teleporter', 'Dark Lake Hylia'), + ('Dark Lake Hylia Ledge Pier', 'Dark Lake Hylia Ledge'), + ('Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia'), + ('East Dark World Pier', 'East Dark World'), + ('South Dark World Bridge', 'South Dark World'), + ('East Dark World Bridge', 'East Dark World'), + ('Village of Outcasts Heavy Rock', 'West Dark World'), + ('Village of Outcasts Drop', 'South Dark World'), + ('Village of Outcasts Eastern Rocks', 'Hammer Peg Area'), + ('Village of Outcasts Pegs', 'Dark Grassy Lawn'), + ('Peg Area Rocks', 'West Dark World'), + ('Grassy Lawn Pegs', 'West Dark World'), + ('East Dark World River Pier', 'Northeast Dark World'), + ('West Dark World Gap', 'West Dark World'), + ('East Dark World Broken Bridge Pass', 'East Dark World'), + ('Northeast Dark World Broken Bridge Pass', 'Northeast Dark World'), + ('Bumper Cave Entrance Rock', 'Bumper Cave Entrance'), + ('Bumper Cave Entrance Drop', 'West Dark World'), + ('Bumper Cave Ledge Drop', 'West Dark World'), + ('Skull Woods Forest', 'Skull Woods Forest'), + ('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'), + ('Paradox Cave Push Block', 'Paradox Cave Front'), + ('Paradox Cave Bomb Jump', 'Paradox Cave'), + ('Paradox Cave Drop', 'Paradox Cave Chest Area'), + ('Light World Death Mountain Shop', 'Light World Death Mountain Shop'), + ('Fairy Ascension Rocks', 'Fairy Ascension Plateau'), + ('Fairy Ascension Drop', 'East Death Mountain (Bottom)'), + ('Fairy Ascension Ledge Drop', 'Fairy Ascension Plateau'), + ('Fairy Ascension Ledge Access', 'Fairy Ascension Ledge'), + ('Fairy Ascension Cave Climb', 'Fairy Ascension Cave (Top)'), + ('Fairy Ascension Cave Pots', 'Fairy Ascension Cave (Bottom)'), + ('Fairy Ascension Cave Drop', 'Fairy Ascension Cave (Drop)'), + ('Dark Death Mountain Drop (East)', 'Dark Death Mountain (East Bottom)'), + ('Swamp Palace Moat', 'Swamp Palace (First Room)'), + ('Swamp Palace Small Key Door', 'Swamp Palace (Starting Area)'), + ('Swamp Palace (Center)', 'Swamp Palace (Center)'), + ('Swamp Palace (North)', 'Swamp Palace (North)'), + ('Thieves Town Big Key Door', 'Thieves Town (Deep)'), + ('Skull Woods Torch Room', 'Skull Woods Final Section (Mothula)'), + ('Skull Woods First Section Bomb Jump', 'Skull Woods First Section (Top)'), + ('Skull Woods First Section South Door', 'Skull Woods First Section (Right)'), + ('Skull Woods First Section West Door', 'Skull Woods First Section (Left)'), + ('Skull Woods First Section (Right) North Door', 'Skull Woods First Section'), + ('Skull Woods First Section (Left) Door to Right', 'Skull Woods First Section (Right)'), + ('Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section'), + ('Skull Woods First Section (Top) One-Way Path', 'Skull Woods First Section'), + ('Skull Woods Second Section (Drop)', 'Skull Woods Second Section'), + ('Blind Fight', 'Blind Fight'), + ('Desert Palace Pots (Outer)', 'Desert Palace Main (Inner)'), + ('Desert Palace Pots (Inner)', 'Desert Palace Main (Outer)'), + ('Ice Palace Entrance Room', 'Ice Palace (Main)'), + ('Ice Palace (East)', 'Ice Palace (East)'), + ('Ice Palace (East Top)', 'Ice Palace (East Top)'), + ('Ice Palace (Kholdstare)', 'Ice Palace (Kholdstare)'), + ('Misery Mire Entrance Gap', 'Misery Mire (Main)'), + ('Misery Mire (West)', 'Misery Mire (West)'), + ('Misery Mire Big Key Door', 'Misery Mire (Final Area)'), + ('Misery Mire (Vitreous)', 'Misery Mire (Vitreous)'), + ('Turtle Rock Entrance Gap', 'Turtle Rock (First Section)'), + ('Turtle Rock Entrance Gap Reverse', 'Turtle Rock (Entrance)'), + ('Turtle Rock Pokey Room', 'Turtle Rock (Chain Chomp Room)'), + ('Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Second Section)'), + ('Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock (First Section)'), + ('Turtle Rock Chain Chomp Staircase', 'Turtle Rock (Chain Chomp Room)'), + ('Turtle Rock (Big Chest) (North)', 'Turtle Rock (Second Section)'), + ('Turtle Rock Big Key Door', 'Turtle Rock (Crystaroller Room)'), + ('Turtle Rock Big Key Door Reverse', 'Turtle Rock (Second Section)'), + ('Turtle Rock Dark Room Staircase', 'Turtle Rock (Dark Room)'), + ('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Crystaroller Room)'), + ('Turtle Rock (Dark Room) (South)', 'Turtle Rock (Eye Bridge)'), + ('Turtle Rock Dark Room (South)', 'Turtle Rock (Dark Room)'), + ('Turtle Rock (Trinexx)', 'Turtle Rock (Trinexx)'), + ('Palace of Darkness Bridge Room', 'Palace of Darkness (Center)'), + ('Palace of Darkness Bonk Wall', 'Palace of Darkness (Bonk Section)'), + ('Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (Big Key Chest)'), + ('Palace of Darkness (North)', 'Palace of Darkness (North)'), + ('Palace of Darkness Big Key Door', 'Palace of Darkness (Final Section)'), + ('Palace of Darkness Hammer Peg Drop', 'Palace of Darkness (Center)'), + ('Palace of Darkness Spike Statue Room Door', 'Palace of Darkness (Harmless Hellway)'), + ('Palace of Darkness Maze Door', 'Palace of Darkness (Maze)'), + ('Ganons Tower (Tile Room)', 'Ganons Tower (Tile Room)'), + ('Ganons Tower (Tile Room) Key Door', 'Ganons Tower (Compass Room)'), + ('Ganons Tower (Bottom) (East)', 'Ganons Tower (Bottom)'), + ('Ganons Tower (Hookshot Room)', 'Ganons Tower (Hookshot Room)'), + ('Ganons Tower (Map Room)', 'Ganons Tower (Map Room)'), + ('Ganons Tower (Double Switch Room)', 'Ganons Tower (Firesnake Room)'), + ('Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)'), + ('Ganons Tower (Bottom) (West)', 'Ganons Tower (Bottom)'), + ('Ganons Tower Big Key Door', 'Ganons Tower (Top)'), + ('Ganons Tower Torch Rooms', 'Ganons Tower (Before Moldorm)'), + ('Ganons Tower Moldorm Door', 'Ganons Tower (Moldorm)'), + ('Ganons Tower Moldorm Gap', 'Agahnim 2'), + ('Ganon Drop', 'Bottom of Pyramid'), + ('Pyramid Drop', 'East Dark World'), + ('Post Aga Teleporter', 'Light World'), + ('LW Hyrule Castle Ledge SQ', 'Hyrule Castle Ledge'), + ('EDM Hyrule Castle Ledge SQ', 'Hyrule Castle Ledge'), + ('WDM Hyrule Castle Ledge SQ', 'Hyrule Castle Ledge'), + ('Secret Passage Inner Bushes', 'Light World'), + ('Secret Passage Outer Bushes', 'Hyrule Castle Secret Entrance Area'), + ('Potion Shop Inner Bushes', 'Light World'), + ('Potion Shop Outer Bushes', 'Potion Shop Area'), + ('Potion Shop Inner Rock', 'Northeast Light World'), + ('Potion Shop Outer Rock', 'Potion Shop Area'), + ('Potion Shop River Drop', 'River'), + ('Graveyard Cave Inner Bushes', 'Light World'), + ('Graveyard Cave Outer Bushes', 'Graveyard Cave Area'), + ('Graveyard Cave Mirror Spot', 'West Dark World'), + ('Light World River Drop', 'River'), + ('Light World Pier', 'Light World'), + ('Potion Shop Pier', 'Potion Shop Area'), + ('Hyrule Castle Ledge Courtyard Drop', 'Light World'), + ('Mimic Cave Ledge Access', 'Mimic Cave Ledge'), + ('Mimic Cave Ledge Drop', 'East Death Mountain (Bottom)'), + ('Turtle Rock Tail Drop', 'Turtle Rock (Top)'), + ('Turtle Rock Drop', 'Dark Death Mountain'), + ('Desert Ledge Drop', 'Light World'), + ('Floating Island Drop', 'Dark Death Mountain'), + ('Dark Lake Hylia Central Island Teleporter', 'Lake Hylia Central Island'), + ('Dark Desert Teleporter', 'Light World'), + ('East Dark World Teleporter', 'Light World'), + ('South Dark World Teleporter', 'Light World'), + ('West Dark World Teleporter', 'Light World'), + ('Dark Death Mountain Teleporter (West)', 'Death Mountain'), + ('Dark Death Mountain Teleporter (East)', 'East Death Mountain (Top)'), + ('Dark Death Mountain Teleporter (East Bottom)', 'East Death Mountain (Bottom)'), + ('Mire Mirror Spot', 'Dark Desert'), + ('Dark Desert Drop', 'Dark Desert'), + ('Desert Palace Stairs Mirror Spot', 'Dark Desert'), + ('Desert Palace North Mirror Spot', 'Dark Desert'), + ('Maze Race Mirror Spot', 'West Dark World'), + ('Lake Hylia Central Island Mirror Spot', 'Dark Lake Hylia'), + ('Hammer Peg Area Mirror Spot', 'Hammer Peg Area'), + ('Bumper Cave Ledge Mirror Spot', 'Bumper Cave Ledge'), + ('Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance'), + ('Death Mountain Mirror Spot', 'Dark Death Mountain'), + ('East Death Mountain Mirror Spot (Top)', 'Dark Death Mountain'), + ('East Death Mountain Mirror Spot (Bottom)', 'Dark Death Mountain (East Bottom)'), + ('Death Mountain (Top) Mirror Spot', 'Dark Death Mountain'), + ('Dark Death Mountain Ledge Mirror Spot (East)', 'Dark Death Mountain Ledge'), + ('Dark Death Mountain Ledge Mirror Spot (West)', 'Dark Death Mountain Ledge'), + ('Floating Island Mirror Spot', 'Death Mountain Floating Island (Dark World)'), + ('Laser Bridge Mirror Spot', 'Dark Death Mountain Isolated Ledge'), + ('East Dark World Mirror Spot', 'East Dark World'), + ('West Dark World Mirror Spot', 'West Dark World'), + ('South Dark World Mirror Spot', 'South Dark World'), + ('Potion Shop Mirror Spot', 'Northeast Dark World'), + ('Northeast Dark World Mirror Spot', 'Northeast Dark World'), + ('Shopping Mall Mirror Spot', 'Dark Lake Hylia Ledge'), + ('Skull Woods Mirror Spot', 'Skull Woods Forest (West)'), + ('DDM Flute', 'The Sky'), + ('DDM Landing', 'Dark Death Mountain'), + ('NEDW Flute', 'The Sky'), + ('NEDW Landing', 'Northeast Dark World'), + ('WDW Flute', 'The Sky'), + ('WDW Landing', 'West Dark World'), + ('SDW Flute', 'The Sky'), + ('SDW Landing', 'South Dark World'), + ('EDW Flute', 'The Sky'), + ('EDW Landing', 'East Dark World'), + ('DLHL Flute', 'The Sky'), + ('DLHL Landing', 'Dark Lake Hylia Ledge'), + ('DD Flute', 'The Sky'), + ('DD Landing', 'Dark Desert Ledge'), + ('EDDM Flute', 'The Sky'), + ('Dark Grassy Lawn Flute', 'The Sky'), + ('Hammer Peg Area Flute', 'The Sky'), + ('Chris Houlihan Room Exit', 'Pyramid Ledge'), + ('Bush Covered Lawn Inner Bushes', 'Light World'), + ('Bush Covered Lawn Outer Bushes', 'Bush Covered Lawn'), + ('Bush Covered Lawn Mirror Spot', 'Dark Grassy Lawn'), + ('Bomb Hut Inner Bushes', 'Light World'), + ('Bomb Hut Outer Bushes', 'Bomb Hut Area'), + ('Bomb Hut Mirror Spot', 'West Dark World')] # non-shuffled entrance links default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'), ("Blinds Hideout", "Blinds Hideout"), @@ -2061,6 +3367,154 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'), ('Pyramid Entrance', 'Bottom of Pyramid') ] +inverted_default_connections = [('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', 'Light World'), + ('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', 'Light World'), + ('Blacksmiths Hut', 'Blacksmiths Hut'), + ('Bat Cave Drop', 'Bat Cave (right)'), + ('Bat Cave Cave', 'Bat Cave (left)'), + ('Bat Cave Exit', 'Light World'), + ('Sick Kids House', 'Sick Kids House'), + ('Elder House (East)', 'Elder House'), + ('Elder House (West)', 'Elder House'), + ('Elder House Exit (East)', 'Light World'), + ('Elder House Exit (West)', 'Light World'), + ('North Fairy Cave Drop', 'North Fairy Cave'), + ('North Fairy Cave', 'North Fairy Cave'), + ('North Fairy Cave Exit', 'Light World'), + ('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'), + ('Lost Woods Hideout Drop', 'Lost Woods Hideout (top)'), + ('Lost Woods Hideout Stump', 'Lost Woods Hideout (bottom)'), + ('Lost Woods Hideout Exit', 'Light World'), + ('Lumberjack Tree Tree', 'Lumberjack Tree (top)'), + ('Lumberjack Tree Cave', 'Lumberjack Tree (bottom)'), + ('Lumberjack Tree Exit', 'Light World'), + ('Cave 45', 'Cave 45'), + ('Graveyard Cave', 'Graveyard Cave'), + ('Checkerboard Cave', 'Checkerboard Cave'), + ('Mini Moldorm Cave', 'Mini Moldorm Cave'), + ('Long Fairy Cave', 'Long Fairy Cave'), + ('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)', 'Light World'), + ('Two Brothers House Exit (West)', 'Maze Race Ledge'), + ('Sanctuary', 'Sanctuary'), + ('Sanctuary Grave', 'Sewer Drop'), + ('Sanctuary Exit', 'Light World'), + ('Old Man House (Bottom)', 'Old Man House'), + ('Old Man House Exit (Bottom)', 'Death Mountain'), + ('Old Man House (Top)', 'Old Man House Back'), + ('Old Man House Exit (Top)', 'Death Mountain'), + ('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', 'Death Mountain'), + ('Spectacle Rock Cave Exit (Top)', 'Death Mountain'), + ('Spectacle Rock Cave Exit (Peak)', 'Death Mountain'), + ('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)'), + ('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 (Bottom)', 'Spiral Cave (Bottom)'), + ('Spiral Cave Exit', 'East Death Mountain (Bottom)'), + ('Spiral Cave Exit (Top)', 'Spiral Cave Ledge'), + ('Pyramid Fairy', 'Pyramid Fairy'), + ('East Dark World Hint', 'East Dark World Hint'), + ('Palace of Darkness Hint', 'Palace of Darkness Hint'), + ('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'), + ('Red Shield Shop', 'Red Shield Shop'), + ('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'), + ('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'), + ('Superbunny Cave (Top)', 'Superbunny Cave'), + ('Cave Shop (Dark Death Mountain)', 'Cave Shop (Dark Death Mountain)'), + ('Superbunny Cave (Bottom)', 'Superbunny Cave'), + ('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'), + ('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'), + ('Hookshot Cave Back Entrance', 'Hookshot Cave'), + ('Mimic Cave', 'Mimic Cave'), + ('Inverted Pyramid Hole', 'Pyramid'), + ('Inverted Links House', 'Inverted Links House'), + ('Inverted Links House Exit', 'South Dark World'), + ('Inverted Big Bomb Shop', 'Inverted Big Bomb Shop'), + ('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'), + ('Old Man Cave (West)', 'Bumper Cave'), + ('Old Man Cave (East)', 'Death Mountain Return Cave'), + ('Old Man Cave Exit (West)', 'Dark Death Mountain'), + ('Old Man Cave Exit (East)', 'East Dark World'), + ('Dark Death Mountain Fairy', 'Old Man Cave'), + ('Bumper Cave (Bottom)', 'Old Man Cave'), + ('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'), + ('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'), + ('Bumper Cave Exit (Bottom)', 'Light World'), + ('Death Mountain Return Cave (East)', 'Bumper Cave'), + ('Death Mountain Return Cave (West)', 'Death Mountain Return Cave'), + ('Death Mountain Return Cave Exit (West)', 'Death Mountain'), + ('Death Mountain Return Cave Exit (East)', 'Death Mountain'), + ('Hookshot Cave Exit (South)', 'Dark Death Mountain'), + ('Superbunny Cave Exit (Top)', 'Dark Death Mountain'), + ('Pyramid Exit', 'Light World'), + ('Inverted Pyramid Entrance', 'Bottom of Pyramid')] + # non shuffled dungeons default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace Main (Inner)'), ('Desert Palace Entrance (West)', 'Desert Palace Main (Outer)'), @@ -2121,6 +3575,58 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace ('Ganons Tower Exit', 'Dark Death Mountain (Top)') ] +inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace Main (Inner)'), + ('Desert Palace Entrance (West)', 'Desert Palace Main (Outer)'), + ('Desert Palace Entrance (North)', 'Desert Palace North'), + ('Desert Palace Entrance (East)', 'Desert Palace Main (Outer)'), + ('Desert Palace Exit (South)', 'Desert Palace Stairs'), + ('Desert Palace Exit (West)', 'Desert Ledge'), + ('Desert Palace Exit (East)', 'Desert Palace Lone Stairs'), + ('Desert Palace Exit (North)', 'Desert Palace Entrance (North) Spot'), + ('Eastern Palace', 'Eastern Palace'), + ('Eastern Palace Exit', 'Light World'), + ('Tower of Hera', 'Tower of Hera (Bottom)'), + ('Tower of Hera Exit', 'Death Mountain (Top)'), + ('Hyrule Castle Entrance (South)', 'Hyrule Castle'), + ('Hyrule Castle Entrance (West)', 'Hyrule Castle'), + ('Hyrule Castle Entrance (East)', 'Hyrule Castle'), + ('Hyrule Castle Exit (South)', 'Light World'), + ('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'), + ('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'), + ('Thieves Town', 'Thieves Town (Entrance)'), + ('Thieves Town Exit', 'West Dark World'), + ('Skull Woods First Section Hole (East)', 'Skull Woods First Section (Right)'), + ('Skull Woods First Section Hole (West)', 'Skull Woods First Section (Left)'), + ('Skull Woods First Section Hole (North)', 'Skull Woods First Section (Top)'), + ('Skull Woods First Section Door', 'Skull Woods First Section'), + ('Skull Woods First Section Exit', 'Skull Woods Forest'), + ('Skull Woods Second Section Hole', 'Skull Woods Second Section (Drop)'), + ('Skull Woods Second Section Door (East)', 'Skull Woods Second Section'), + ('Skull Woods Second Section Door (West)', 'Skull Woods Second Section'), + ('Skull Woods Second Section Exit (East)', 'Skull Woods Forest'), + ('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'), + ('Skull Woods Final Section', 'Skull Woods Final Section (Entrance)'), + ('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'), + ('Ice Palace', 'Ice Palace (Entrance)'), + ('Misery Mire', 'Misery Mire (Entrance)'), + ('Misery Mire Exit', 'Dark Desert'), + ('Palace of Darkness', 'Palace of Darkness (Entrance)'), + ('Palace of Darkness Exit', 'East Dark World'), + ('Swamp Palace', 'Swamp Palace (Entrance)'), + ('Swamp Palace Exit', 'South Dark World'), + ('Turtle Rock', 'Turtle Rock (Entrance)'), + ('Turtle Rock Ledge Exit (West)', 'Dark Death Mountain Ledge'), + ('Turtle Rock Ledge Exit (East)', 'Dark Death Mountain Ledge'), + ('Dark Death Mountain Ledge (West)', 'Turtle Rock (Second Section)'), + ('Dark Death Mountain Ledge (East)', 'Turtle Rock (Big Chest)'), + ('Turtle Rock Isolated Ledge Exit', 'Dark Death Mountain Isolated Ledge'), + ('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock (Eye Bridge)'), + ('Inverted Ganons Tower', 'Inverted Ganons Tower (Entrance)'), + ('Inverted Ganons Tower Exit', 'Hyrule Castle Ledge'), + ('Inverted Agahnims Tower', 'Inverted Agahnims Tower'), + ('Inverted Agahnims Tower Exit', 'Dark Death Mountain'), + ('Turtle Rock Exit (Front)', 'Dark Death Mountain'), + ('Ice Palace Exit', 'Dark Lake Hylia')] # format: # Key=Name @@ -2130,6 +3636,7 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace # ToDo somehow merge this with creation of the locations door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), + 'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), 'Desert Palace Entrance (South)': (0x08, (0x0084, 0x30, 0x0314, 0x0c56, 0x00a6, 0x0ca8, 0x0128, 0x0cc3, 0x0133, 0x0a, 0xfa, 0x0000, 0x0000)), 'Desert Palace Entrance (West)': (0x0A, (0x0083, 0x30, 0x0280, 0x0c46, 0x0003, 0x0c98, 0x0088, 0x0cb3, 0x0090, 0x0a, 0xfd, 0x0000, 0x0000)), 'Desert Palace Entrance (North)': (0x0B, (0x0063, 0x30, 0x0016, 0x0c00, 0x00a2, 0x0c28, 0x0128, 0x0c6d, 0x012f, 0x00, 0x0e, 0x0000, 0x0000)), @@ -2139,7 +3646,9 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0 'Hyrule Castle Entrance (South)': (0x03, (0x0061, 0x1b, 0x0530, 0x0692, 0x0784, 0x06cc, 0x07f8, 0x06ff, 0x0803, 0x0e, 0xfa, 0x0000, 0x87be)), 'Hyrule Castle Entrance (West)': (0x02, (0x0060, 0x1b, 0x0016, 0x0600, 0x06ae, 0x0604, 0x0728, 0x066d, 0x0733, 0x00, 0x02, 0x0000, 0x8124)), 'Hyrule Castle Entrance (East)': (0x04, (0x0062, 0x1b, 0x004a, 0x0600, 0x0856, 0x0604, 0x08c8, 0x066d, 0x08d3, 0x00, 0xfa, 0x0000, 0x8158)), + 'Inverted Pyramid Entrance': (0x35, (0x0010, 0x1b, 0x0418, 0x0679, 0x06b4, 0x06c6, 0x0728, 0x06e6, 0x0733, 0x07, 0xf9, 0x0000, 0x0000)), 'Agahnims Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be)), + 'Inverted Ganons Tower': (0x23, (0x00e0, 0x1b, 0x0032, 0x0600, 0x0784, 0x0634, 0x07f8, 0x066d, 0x0803, 0x00, 0x0a, 0x0000, 0x82be)), 'Thieves Town': (0x33, (0x00db, 0x58, 0x0b2e, 0x075a, 0x0176, 0x07a8, 0x01f8, 0x07c7, 0x0203, 0x06, 0xfa, 0x0000, 0x0000)), 'Skull Woods First Section Door': (0x29, (0x0058, 0x40, 0x0f4c, 0x01f6, 0x0262, 0x0248, 0x02e8, 0x0263, 0x02ef, 0x0a, 0xfe, 0x0000, 0x0000)), 'Skull Woods Second Section Door (East)': (0x28, (0x0057, 0x40, 0x0eb8, 0x01e6, 0x01c2, 0x0238, 0x0248, 0x0253, 0x024f, 0x0a, 0xfe, 0x0000, 0x0000)), @@ -2187,12 +3696,14 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0 'Hookshot Cave': (0x39, (0x003c, 0x45, 0x04da, 0x00a3, 0x0cd6, 0x0107, 0x0d48, 0x0112, 0x0d53, 0x0b, 0xfa, 0x0000, 0x0000)), 'Hookshot Cave Back Entrance': (0x3A, (0x002c, 0x45, 0x004c, 0x0000, 0x0c56, 0x0038, 0x0cc8, 0x006f, 0x0cd3, 0x00, 0x0a, 0x0000, 0x0000)), 'Ganons Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000)), + 'Inverted Agahnims Tower': (0x36, (0x000c, 0x43, 0x0052, 0x0000, 0x0884, 0x0028, 0x08f8, 0x006f, 0x0903, 0x00, 0xfc, 0x0000, 0x0000)), 'Pyramid Entrance': (0x35, (0x0010, 0x5b, 0x0b0e, 0x075a, 0x0674, 0x07a8, 0x06e8, 0x07c7, 0x06f3, 0x06, 0xfa, 0x0000, 0x0000)), 'Skull Woods First Section Hole (West)': ([0xDB84D, 0xDB84E], None), 'Skull Woods First Section Hole (East)': ([0xDB84F, 0xDB850], None), 'Skull Woods First Section Hole (North)': ([0xDB84C], None), 'Skull Woods Second Section Hole': ([0xDB851, 0xDB852], None), 'Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856], None), + 'Inverted Pyramid Hole': ([0xDB854, 0xDB855, 0xDB856, 0x180340], None), 'Waterfall of Wishing': (0x5B, (0x0114, 0x0f, 0x0080, 0x0200, 0x0e00, 0x0207, 0x0e60, 0x026f, 0x0e7d, 0x00, 0x00, 0x0000, 0x0000)), 'Dam': (0x4D, (0x010b, 0x3b, 0x04a0, 0x0e8a, 0x06fa, 0x0ed8, 0x0778, 0x0ef7, 0x077f, 0x06, 0xfa, 0x0000, 0x0000)), 'Blinds Hideout': (0x60, (0x0119, 0x18, 0x02b2, 0x064a, 0x0186, 0x0697, 0x0208, 0x06b7, 0x0213, 0x06, 0xfa, 0x0000, 0x0000)), @@ -2252,6 +3763,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0 'Dark World Hammer Peg Cave': (0x7E, (0x0127, 0x62, 0x0894, 0x091e, 0x0492, 0x09a6, 0x0508, 0x098b, 0x050f, 0x00, 0x00, 0x0000, 0x0000)), 'Red Shield Shop': (0x74, (0x0110, 0x5a, 0x079a, 0x06e8, 0x04d6, 0x0738, 0x0548, 0x0755, 0x0553, 0x08, 0xf8, 0x0AA8, 0x0000)), 'Dark Sanctuary Hint': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000)), + 'Inverted Dark Sanctuary': (0x59, (0x0112, 0x53, 0x001e, 0x0400, 0x06e2, 0x0446, 0x0758, 0x046d, 0x075f, 0x00, 0x00, 0x0000, 0x0000)), 'Fortune Teller (Dark)': (0x65, (0x0122, 0x51, 0x0610, 0x04b4, 0x027e, 0x0507, 0x02f8, 0x0523, 0x0303, 0x0a, 0xf6, 0x091E, 0x0000)), 'Dark World Shop': (0x5F, (0x010f, 0x58, 0x1058, 0x0814, 0x02be, 0x0868, 0x0338, 0x0883, 0x0343, 0x0a, 0xf6, 0x0000, 0x0000)), 'Dark World Lumberjack Shop': (0x56, (0x010f, 0x42, 0x041c, 0x0074, 0x04e2, 0x00c7, 0x0558, 0x00e3, 0x055f, 0x0a, 0xf6, 0x0000, 0x0000)), @@ -2265,6 +3777,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000)), 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000)), 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000)), + 'Inverted Links House': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000)), 'Dark Lake Hylia Shop': (0x73, (0x010f, 0x75, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), 'Lumberjack House': (0x75, (0x011f, 0x02, 0x049c, 0x0088, 0x04e6, 0x00d8, 0x0558, 0x00f7, 0x0563, 0x08, 0xf8, 0x07AA, 0x0000)), 'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000)), @@ -2275,6 +3788,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0 # value = entrance # # | (entrance #, exit #) exit_ids = {'Links House Exit': (0x01, 0x00), + 'Inverted Links House Exit': (0x01, 0x00), 'Chris Houlihan Room Exit': (None, 0x3D), 'Desert Palace Exit (South)': (0x09, 0x0A), 'Desert Palace Exit (West)': (0x0B, 0x0C), @@ -2286,6 +3800,7 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Hyrule Castle Exit (West)': (0x03, 0x02), 'Hyrule Castle Exit (East)': (0x05, 0x04), 'Agahnims Tower Exit': (0x24, 0x25), + 'Inverted Agahnims Tower Exit': (0x24, 0x25), 'Thieves Town Exit': (0x34, 0x35), 'Skull Woods First Section Exit': (0x2A, 0x2B), 'Skull Woods Second Section Exit (East)': (0x29, 0x2A), @@ -2333,6 +3848,7 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Hookshot Cave Exit (South)': (0x3A, 0x3B), 'Hookshot Cave Exit (North)': (0x3B, 0x3C), 'Ganons Tower Exit': (0x37, 0x38), + 'Inverted Ganons Tower Exit': (0x37, 0x38), 'Pyramid Exit': (0x36, 0x37), 'Waterfall of Wishing': 0x5C, 'Dam': 0x4E, @@ -2384,6 +3900,7 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'East Dark World Hint': 0x69, 'Palace of Darkness Hint': 0x68, 'Big Bomb Shop': 0x53, + 'Inverted Big Bomb Shop': 0x53, 'Village of Outcasts Shop': 0x60, 'Dark Lake Hylia Shop': 0x60, 'Dark World Lumberjack Shop': 0x60, @@ -2397,6 +3914,7 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Dark World Hammer Peg Cave': 0x83, 'Red Shield Shop': 0x57, 'Dark Sanctuary Hint': 0x5A, + 'Inverted Dark Sanctuary': 0x5A, 'Fortune Teller (Dark)': 0x66, 'Archery Game': 0x59, 'Mire Shed': 0x5F, diff --git a/Gui.py b/Gui.py index be544aa8..078a3ee1 100755 --- a/Gui.py +++ b/Gui.py @@ -145,7 +145,7 @@ def guiMain(args=None): modeFrame = Frame(drowDownFrame) modeVar = StringVar() modeVar.set('open') - modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'swordless') + modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'swordless', 'inverted') modeOptionMenu.pack(side=RIGHT) modeLabel = Label(modeFrame, text='Game Mode') modeLabel.pack(side=LEFT) diff --git a/InvertedRegions.py b/InvertedRegions.py new file mode 100644 index 00000000..bc2ea357 --- /dev/null +++ b/InvertedRegions.py @@ -0,0 +1,642 @@ +import collections +from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType + + +def create_inverted_regions(world, player): + + world.regions += [ + create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'], + ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam', + 'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', + 'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump', + 'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', + 'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow', + 'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop', + 'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)', + 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', + 'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', + 'East Dark World Mirror Spot', 'West Dark World Mirror Spot', 'South Dark World Mirror Spot', 'Cave 45', 'Checkerboard Cave', 'Mire Mirror Spot', 'Hammer Peg Area Mirror Spot', + 'Shopping Mall Mirror Spot', 'Skull Woods Mirror Spot', 'Inverted Pyramid Entrance','Hyrule Castle Entrance (South)', 'Secret Passage Outer Bushes', 'LW Hyrule Castle Ledge SQ', 'Bush Covered Lawn Outer Bushes', + 'Potion Shop Outer Bushes', 'Graveyard Cave Outer Bushes', 'Bomb Hut Outer Bushes']), + create_lw_region(player, 'Bush Covered Lawn', None, ['Bush Covered House', 'Bush Covered Lawn Inner Bushes', 'Bush Covered Lawn Mirror Spot']), + create_lw_region(player, 'Bomb Hut Area', None, ['Light World Bomb Hut', 'Bomb Hut Inner Bushes', 'Bomb Hut Mirror Spot']), + create_lw_region(player, 'Hyrule Castle Secret Entrance Area', None, ['Hyrule Castle Secret Entrance Stairs', 'Secret Passage Inner Bushes']), + create_lw_region(player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop', 'Bumper Cave Entrance Mirror Spot']), + create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Mirror Spot']), + create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top", + "Blind\'s Hideout - Left", + "Blind\'s Hideout - Right", + "Blind\'s Hideout - Far Left", + "Blind\'s Hideout - Far Right"]), + create_lw_region(player, 'Northeast Light World', None, ['Zoras River', 'Waterfall of Wishing', 'Potion Shop Outer Rock', 'Northeast Dark World Mirror Spot']), + create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop', 'Potion Shop Inner Bushes', 'Potion Shop Inner Rock', 'Potion Shop Mirror Spot', 'Potion Shop River Drop']), + create_lw_region(player, 'Graveyard Cave Area', None, ['Graveyard Cave', 'Graveyard Cave Inner Bushes', 'Graveyard Cave Mirror Spot']), + create_lw_region(player, 'River', None, ['Light World Pier', 'Potion Shop Pier']), + create_cave_region(player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']), + create_lw_region(player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']), + create_cave_region(player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']), + create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']), + 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, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']), + create_cave_region(player, 'Inverted Links House', 'your house', ['Link\'s House'], ['Inverted Links House Exit']), + create_cave_region(player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']), + create_cave_region(player, 'Tavern', 'the tavern', ['Kakariko Tavern']), + create_cave_region(player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']), + create_cave_region(player, 'Snitch Lady (East)', 'a boring house'), + create_cave_region(player, 'Snitch Lady (West)', 'a boring house'), + create_cave_region(player, 'Bush Covered House', 'the grass man'), + create_cave_region(player, 'Tavern (Front)', 'the tavern'), + create_cave_region(player, 'Light World Bomb Hut', 'a restock room'), + create_cave_region(player, 'Kakariko Shop', 'a common shop'), + create_cave_region(player, 'Fortune Teller (Light)', 'a fortune teller'), + create_cave_region(player, 'Lake Hylia Fortune Teller', 'a fortune teller'), + create_cave_region(player, 'Lumberjack House', 'a boring house'), + create_cave_region(player, 'Bonk Fairy (Light)', 'a fairy fountain'), + create_cave_region(player, 'Bonk Fairy (Dark)', 'a fairy fountain'), + create_cave_region(player, 'Lake Hylia Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Swamp Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Desert Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Desert Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']), + create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']), + create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']), + create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle', + 'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']), + create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']), + create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']), + create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']), + create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']), + create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']), + create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']), + create_lw_region(player, 'Hobo Bridge', ['Hobo']), + create_cave_region(player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']), + create_cave_region(player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']), + create_cave_region(player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']), + create_cave_region(player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']), + create_cave_region(player, 'Cave 45', 'a cave with an item', ['Cave 45']), + create_cave_region(player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']), + create_cave_region(player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']), + create_cave_region(player, 'Long Fairy Cave', 'a fairy fountain'), + 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, 'Cave Shop (Lake Hylia)', 'a common shop'), + create_cave_region(player, 'Cave Shop (Dark Death Mountain)', 'a common shop'), + create_cave_region(player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']), + create_cave_region(player, 'Library', 'the library', ['Library']), + create_cave_region(player, 'Kakariko Gamble Game', 'a game of chance'), + create_cave_region(player, 'Potion Shop', 'the potion shop', ['Potion Shop']), + create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island']), + create_cave_region(player, 'Capacity Upgrade', 'the queen of fairies'), + create_cave_region(player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']), + create_lw_region(player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)', 'Maze Race Mirror Spot']), + create_cave_region(player, '50 Rupee Cave', 'a cave with some cash'), + create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)', 'Desert Ledge Drop']), + create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)', 'Desert Palace Stairs Mirror Spot']), + create_lw_region(player, 'Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']), + create_lw_region(player, 'Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks', 'Desert Palace North Mirror Spot']), + create_dungeon_region(player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'], + ['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']), + create_dungeon_region(player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']), + create_dungeon_region(player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']), + create_dungeon_region(player, 'Desert Palace North', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']), + create_dungeon_region(player, 'Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest', + 'Eastern Palace - Big Key Chest', 'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Palace Exit']), + create_lw_region(player, 'Master Sword Meadow', ['Master Sword Pedestal']), + create_cave_region(player, 'Lost Woods Gamble', 'a game of chance'), + create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']), + create_dungeon_region(player, 'Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest'], + ['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']), + 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, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross'], ['Sewers Door']), + create_dungeon_region(player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', + 'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']), + create_dungeon_region(player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']), + create_dungeon_region(player, 'Inverted Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'], ['Agahnim 1', 'Inverted Agahnims Tower Exit']), + create_dungeon_region(player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None), + create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']), + create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']), + create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']), + create_lw_region(player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', + 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Mirror Spot', 'WDM Hyrule Castle Ledge SQ']), + create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), + create_lw_region(player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot']), + create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), + create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), + create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), + create_lw_region(player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Mirror Spot (Bottom)', 'Hookshot Fairy', + 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)', 'EDM Hyrule Castle Ledge SQ']), + 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 Upper - Left', + 'Paradox Cave Upper - Right'], + ['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']), + 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'), + create_lw_region(player, 'East Death Mountain (Top)', ['Floating Island'], ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'East Death Mountain Mirror Spot (Top)', 'Fairy Ascension Ledge Access', 'Mimic Cave Ledge Access', + 'Floating Island Mirror Spot']), + create_lw_region(player, 'Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop', 'Dark Death Mountain Ledge Mirror Spot (West)']), + create_lw_region(player, 'Mimic Cave Ledge', None, ['Mimic Cave', 'Mimic Cave Ledge Drop', 'Dark Death Mountain Ledge Mirror Spot (East)']), + 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_lw_region(player, 'Fairy Ascension Plateau', None, ['Fairy Ascension Drop', 'Fairy Ascension Cave (Bottom)']), + 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_lw_region(player, 'Fairy Ascension Ledge', None, ['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)', 'Laser Bridge Mirror Spot']), + create_lw_region(player, 'Death Mountain (Top)', ['Ether Tablet', 'Spectacle Rock'], ['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop', 'Death Mountain (Top) Mirror Spot']), + create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Ledge Drop', 'Bumper Cave (Top)']), + create_dungeon_region(player, 'Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']), + create_dungeon_region(player, 'Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']), + create_dungeon_region(player, 'Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']), + + create_dw_region(player, 'East Dark World', ['Pyramid'], ['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)', + 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Northeast Dark World Broken Bridge Pass', 'East Dark World Teleporter', 'EDW Flute']), + create_dw_region(player, 'Northeast Dark World', ['Catfish'], ['West Dark World Gap', 'Dark World Potion Shop', 'East Dark World Broken Bridge Pass', 'NEDW Flute', 'Dark Lake Hylia Teleporter']), + create_cave_region(player, 'Palace of Darkness Hint', 'a storyteller'), + create_cave_region(player, 'East Dark World Hint', 'a storyteller'), + create_dw_region(player, 'South Dark World', ['Stumpy', 'Digging Game'], ['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock', 'East Dark World Bridge', 'Inverted Links House', 'Archery Game', 'Bonk Fairy (Dark)', + 'Dark Lake Hylia Shop', 'South Dark World Teleporter', 'Post Aga Teleporter', 'SDW Flute']), + create_cave_region(player, 'Inverted Big Bomb Shop', 'the bomb shop'), + create_cave_region(player, 'Archery Game', 'a game of skill'), + create_dw_region(player, 'Dark Lake Hylia', None, ['East Dark World Pier', 'Dark Lake Hylia Ledge Pier', 'Ice Palace', 'Dark Lake Hylia Central Island Teleporter']), + create_dw_region(player, 'Dark Lake Hylia Ledge', None, ['Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'DLHL Flute']), + 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, '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']), + create_dw_region(player, 'West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Bumper Cave Entrance Rock', + 'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Inverted Dark Sanctuary', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop', + 'West Dark World Teleporter', 'WDW Flute']), + create_dw_region(player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop', 'Dark Grassy Lawn Flute']), + create_dw_region(player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Dark World Hammer Peg Cave', 'Peg Area Rocks', 'Hammer Peg Area Flute']), + create_dw_region(player, 'Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Drop']), + create_cave_region(player, 'Fortune Teller (Dark)', 'a fortune teller'), + create_cave_region(player, 'Village of Outcasts Shop', 'a common shop'), + create_cave_region(player, 'Dark Lake Hylia Shop', 'a common shop'), + create_cave_region(player, 'Dark World Lumberjack Shop', 'a common shop'), + create_cave_region(player, 'Dark World Potion Shop', 'a common shop'), + create_cave_region(player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']), + create_cave_region(player, 'Pyramid Fairy', 'a cave with two chests', ['Pyramid Fairy - Left', 'Pyramid Fairy - Right']), + create_cave_region(player, 'Brewery', 'a house with a chest', ['Brewery']), + create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']), + create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']), + create_cave_region(player, 'Red Shield Shop', 'the rare shop'), + create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller'), + create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), + 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)']), + create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section']), + create_dw_region(player, 'Dark Desert', None, ['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy', 'DD Flute']), + create_dw_region(player, 'Dark Desert Ledge', None, ['Dark Desert Drop', 'Dark Desert Teleporter']), + create_cave_region(player, 'Mire Shed', 'a cave with two chests', ['Mire Shed - Left', 'Mire Shed - Right']), + create_cave_region(player, 'Dark Desert Hint', 'a storyteller'), + create_dw_region(player, 'Dark Death Mountain', None, ['Dark Death Mountain Drop (East)', 'Inverted Agahnims Tower', 'Superbunny Cave (Top)', 'Hookshot Cave', 'Turtle Rock', + 'Spike Cave', 'Dark Death Mountain Fairy', 'Dark Death Mountain Teleporter (West)', 'Turtle Rock Tail Drop', 'DDM Flute']), + create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)']), + create_dw_region(player, 'Turtle Rock (Top)', None, ['Dark Death Mountain Teleporter (East)', 'Turtle Rock Drop']), + create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Turtle Rock Isolated Ledge Entrance']), + create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Teleporter (East Bottom)', 'EDDM Flute']), + create_cave_region(player, 'Superbunny Cave', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], + ['Superbunny Cave Exit (Top)', 'Superbunny Cave Exit (Bottom)']), + create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), + create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], + ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), + create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance']), + create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']), + + create_dungeon_region(player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']), + create_dungeon_region(player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']), + create_dungeon_region(player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest'], ['Swamp Palace (Center)']), + create_dungeon_region(player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', + 'Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest'], ['Swamp Palace (North)']), + create_dungeon_region(player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', + 'Swamp Palace - Waterfall Room', 'Swamp Palace - Boss', 'Swamp Palace - Prize']), + create_dungeon_region(player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest', + 'Thieves\' Town - Map Chest', + 'Thieves\' Town - Compass Chest', + 'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']), + create_dungeon_region(player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic', + 'Thieves\' Town - Big Chest', + 'Thieves\' Town - Blind\'s Cell'], ['Blind Fight']), + create_dungeon_region(player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']), + create_dungeon_region(player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']), + create_dungeon_region(player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']), + create_dungeon_region(player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']), + create_dungeon_region(player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']), + create_dungeon_region(player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']), + create_dungeon_region(player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), + create_dungeon_region(player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), + create_dungeon_region(player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), + create_dungeon_region(player, 'Ice Palace (Entrance)', 'Ice Palace', None, ['Ice Palace Entrance Room', 'Ice Palace Exit']), + create_dungeon_region(player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest', + 'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']), + create_dungeon_region(player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']), + create_dungeon_region(player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']), + create_dungeon_region(player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']), + create_dungeon_region(player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']), + create_dungeon_region(player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby', + 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'], ['Misery Mire (West)', 'Misery Mire Big Key Door']), + create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']), + create_dungeon_region(player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']), + create_dungeon_region(player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']), + create_dungeon_region(player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']), + create_dungeon_region(player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left', + 'Turtle Rock - Roller Room - Right'], ['Turtle Rock Pokey Room', 'Turtle Rock Entrance Gap Reverse']), + create_dungeon_region(player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']), + create_dungeon_region(player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']), + create_dungeon_region(player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']), + create_dungeon_region(player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']), + create_dungeon_region(player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']), + create_dungeon_region(player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', + 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'], + ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']), + create_dungeon_region(player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']), + create_dungeon_region(player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']), + create_dungeon_region(player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'], + ['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']), + create_dungeon_region(player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']), + create_dungeon_region(player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']), + create_dungeon_region(player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'], + ['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']), + create_dungeon_region(player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']), + create_dungeon_region(player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']), + create_dungeon_region(player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']), + create_dungeon_region(player, 'Inverted Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right'], + ['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door', 'Inverted Ganons Tower Exit']), + create_dungeon_region(player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']), + create_dungeon_region(player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', + 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'], + ['Ganons Tower (Bottom) (East)']), + create_dungeon_region(player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', + 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'], + ['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']), + create_dungeon_region(player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']), + create_dungeon_region(player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']), + create_dungeon_region(player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', + 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'], + ['Ganons Tower (Bottom) (West)']), + create_dungeon_region(player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', + 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']), + create_dungeon_region(player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']), + create_dungeon_region(player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right', + 'Ganons Tower - Pre-Moldorm Chest'], ['Ganons Tower Moldorm Door']), + create_dungeon_region(player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']), + create_dungeon_region(player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None), + 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_dw_region(player, 'Pyramid Ledge', None, ['Pyramid Drop']), # houlihan room exits here in inverted + + # to simplify flute connections + create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing']) + ] + + for region_name, (room_id, shopkeeper, replaceable) in shop_table.items(): + region = world.get_region(region_name, player) + shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable) + region.shop = shop + world.shops.append(shop) + for index, (item, price) in enumerate(default_shop_contents[region_name]): + shop.add_inventory(index, item, price) + + region = world.get_region('Capacity Upgrade', player) + shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True) + region.shop = shop + world.shops.append(shop) + shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) + shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7) + world.intialize_regions() + +def create_lw_region(player, name, locations=None, exits=None): + return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits) + +def create_dw_region(player, name, locations=None, exits=None): + return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits) + +def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None): + return _create_region(player, name, RegionType.Cave, hint, locations, exits) + +def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None): + return _create_region(player, name, RegionType.Dungeon, hint, locations, exits) + +def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None): + ret = Region(name, type, hint, player) + if locations is None: + locations = [] + if exits is None: + exits = [] + + for exit in exits: + ret.exits.append(Entrance(player, exit, ret)) + for location in locations: + address, crystal, hint_text = location_table[location] + ret.locations.append(Location(player, location, address, crystal, hint_text, ret)) + return ret + +def mark_dark_world_regions(world): + # 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. + + queue = collections.deque(region for region in world.regions 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.type == RegionType.LightWorld: + # 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.regions 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.type == RegionType.DarkWorld: + # 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) + +# (room_id, shopkeeper, replaceable) +shop_table = { + 'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True), + 'Red Shield Shop': (0x0110, 0xC1, True), + 'Dark Lake Hylia Shop': (0x010F, 0xC1, True), + 'Dark World Lumberjack Shop': (0x010F, 0xC1, True), + 'Village of Outcasts Shop': (0x010F, 0xC1, True), + 'Dark World Potion Shop': (0x010F, 0xC1, True), + 'Light World Death Mountain Shop': (0x00FF, 0xA0, True), + 'Kakariko Shop': (0x011F, 0xA0, True), + 'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True), + 'Potion Shop': (0x0109, 0xFF, False), + # Bomb Shop not currently modeled as a shop, due to special nature of items +} +# region, [item] +# slot, item, price, max=0, replacement=None, replacement_price=0 +# item = (item, price) + +_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)] +default_shop_contents = { + 'Cave Shop (Dark Death Mountain)': _basic_shop_defaults, + 'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)], + 'Dark Lake Hylia Shop': _dark_world_shop_defaults, + 'Dark World Lumberjack Shop': _dark_world_shop_defaults, + 'Village of Outcasts Shop': _dark_world_shop_defaults, + 'Dark World Potion Shop': _dark_world_shop_defaults, + 'Light World Death Mountain Shop': _basic_shop_defaults, + 'Kakariko Shop': _basic_shop_defaults, + 'Cave Shop (Lake Hylia)': _basic_shop_defaults, + 'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], +} + +location_table = {'Mushroom': (0x180013, False, 'in the woods'), + 'Bottle Merchant': (0x2EB18, False, 'with a merchant'), + 'Flute Spot': (0x18014A, False, 'underground'), + 'Sunken Treasure': (0x180145, False, 'underwater'), + 'Purple Chest': (0x33D68, False, 'from a box'), + 'Blind\'s Hideout - Top': (0xEB0F, False, 'in a basement'), + 'Blind\'s Hideout - Left': (0xEB12, False, 'in a basement'), + 'Blind\'s Hideout - Right': (0xEB15, False, 'in a basement'), + 'Blind\'s Hideout - Far Left': (0xEB18, False, 'in a basement'), + 'Blind\'s Hideout - Far Right': (0xEB1B, False, 'in a basement'), + 'Link\'s Uncle': (0x2DF45, False, 'with your uncle'), + 'Secret Passage': (0xE971, False, 'near your uncle'), + 'King Zora': (0xEE1C3, False, 'at a high price'), + 'Zora\'s Ledge': (0x180149, False, 'near Zora'), + 'Waterfall Fairy - Left': (0xE9B0, False, 'near a fairy'), + 'Waterfall Fairy - Right': (0xE9D1, False, 'near a fairy'), + 'King\'s Tomb': (0xE97A, False, 'alone in a cave'), + 'Floodgate Chest': (0xE98C, False, 'in the dam'), + 'Link\'s House': (0xE9BC, False, 'in your home'), + 'Kakariko Tavern': (0xE9CE, False, 'in the bar'), + 'Chicken House': (0xE9E9, False, 'near poultry'), + 'Aginah\'s Cave': (0xE9F2, False, 'with Aginah'), + 'Sahasrahla\'s Hut - Left': (0xEA82, False, 'near the elder'), + 'Sahasrahla\'s Hut - Middle': (0xEA85, False, 'near the elder'), + 'Sahasrahla\'s Hut - Right': (0xEA88, False, 'near the elder'), + 'Sahasrahla': (0x2F1FC, False, 'with the elder'), + 'Kakariko Well - Top': (0xEA8E, False, 'in a well'), + 'Kakariko Well - Left': (0xEA91, False, 'in a well'), + 'Kakariko Well - Middle': (0xEA94, False, 'in a well'), + 'Kakariko Well - Right': (0xEA97, False, 'in a well'), + 'Kakariko Well - Bottom': (0xEA9A, False, 'in a well'), + 'Blacksmith': (0x18002A, False, 'with the smith'), + 'Magic Bat': (0x180015, False, 'with the bat'), + 'Sick Kid': (0x339CF, False, 'with the sick'), + 'Hobo': (0x33E7D, False, 'with the hobo'), + 'Lost Woods Hideout': (0x180000, False, 'near a thief'), + 'Lumberjack Tree': (0x180001, False, 'in a hole'), + 'Cave 45': (0x180003, False, 'alone in a cave'), + 'Graveyard Cave': (0x180004, False, 'alone in a cave'), + 'Checkerboard Cave': (0x180005, False, 'alone in a cave'), + 'Mini Moldorm Cave - Far Left': (0xEB42, False, 'near Moldorms'), + 'Mini Moldorm Cave - Left': (0xEB45, False, 'near Moldorms'), + 'Mini Moldorm Cave - Right': (0xEB48, False, 'near Moldorms'), + 'Mini Moldorm Cave - Far Right': (0xEB4B, False, 'near Moldorms'), + 'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'), + 'Ice Rod Cave': (0xEB4E, False, 'in a frozen cave'), + 'Bonk Rock Cave': (0xEB3F, False, 'alone in a cave'), + 'Library': (0x180012, False, 'near books'), + 'Potion Shop': (0x180014, False, 'near potions'), + 'Lake Hylia Island': (0x180144, False, 'on an island'), + 'Maze Race': (0x180142, False, 'at the race'), + 'Desert Ledge': (0x180143, False, 'in the desert'), + 'Desert Palace - Big Chest': (0xE98F, False, 'in Desert Palace'), + 'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'), + 'Desert Palace - Map Chest': (0xE9B6, False, 'in Desert Palace'), + 'Desert Palace - Compass Chest': (0xE9CB, False, 'in Desert Palace'), + 'Desert Palace - Big Key Chest': (0xE9C2, False, 'in Desert Palace'), + 'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'), + 'Eastern Palace - Compass Chest': (0xE977, False, 'in Eastern Palace'), + 'Eastern Palace - Big Chest': (0xE97D, False, 'in Eastern Palace'), + 'Eastern Palace - Cannonball Chest': (0xE9B3, False, 'in Eastern Palace'), + 'Eastern Palace - Big Key Chest': (0xE9B9, False, 'in Eastern Palace'), + 'Eastern Palace - Map Chest': (0xE9F5, False, 'in Eastern Palace'), + 'Eastern Palace - Boss': (0x180150, False, 'with the Armos'), + 'Master Sword Pedestal': (0x289B0, False, 'at the pedestal'), + 'Hyrule Castle - Boomerang Chest': (0xE974, False, 'in Hyrule Castle'), + 'Hyrule Castle - Map Chest': (0xEB0C, False, 'in Hyrule Castle'), + 'Hyrule Castle - Zelda\'s Chest': (0xEB09, False, 'in Hyrule Castle'), + 'Sewers - Dark Cross': (0xE96E, False, 'in the sewers'), + 'Sewers - Secret Room - Left': (0xEB5D, False, 'in the sewers'), + 'Sewers - Secret Room - Middle': (0xEB60, False, 'in the sewers'), + 'Sewers - Secret Room - Right': (0xEB63, False, 'in the sewers'), + 'Sanctuary': (0xEA79, False, 'in Sanctuary'), + 'Castle Tower - Room 03': (0xEAB5, False, 'in Castle Tower'), + 'Castle Tower - Dark Maze': (0xEAB2, False, 'in Castle Tower'), + 'Old Man': (0xF69FA, False, 'with the old man'), + 'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'), + 'Paradox Cave Lower - Far Left': (0xEB2A, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Left': (0xEB2D, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Right': (0xEB30, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Far Right': (0xEB33, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Middle': (0xEB36, False, 'in a cave with seven chests'), + 'Paradox Cave Upper - Left': (0xEB39, False, 'in a cave with seven chests'), + 'Paradox Cave Upper - Right': (0xEB3C, False, 'in a cave with seven chests'), + 'Spiral Cave': (0xE9BF, False, 'in spiral cave'), + 'Ether Tablet': (0x180016, False, 'at a monolith'), + 'Spectacle Rock': (0x180140, False, 'atop a rock'), + 'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'), + 'Tower of Hera - Map Chest': (0xE9AD, False, 'in Tower of Hera'), + 'Tower of Hera - Big Key Chest': (0xE9E6, False, 'in Tower of Hera'), + 'Tower of Hera - Compass Chest': (0xE9FB, False, 'in Tower of Hera'), + 'Tower of Hera - Big Chest': (0xE9F8, False, 'in Tower of Hera'), + 'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'), + 'Pyramid': (0x180147, False, 'on the pyramid'), + 'Catfish': (0xEE185, False, 'with a catfish'), + 'Stumpy': (0x330C7, False, 'with tree boy'), + 'Digging Game': (0x180148, False, 'underground'), + 'Bombos Tablet': (0x180017, False, 'at a monolith'), + 'Hype Cave - Top': (0xEB1E, False, 'near a bat-like man'), + 'Hype Cave - Middle Right': (0xEB21, False, 'near a bat-like man'), + 'Hype Cave - Middle Left': (0xEB24, False, 'near a bat-like man'), + 'Hype Cave - Bottom': (0xEB27, False, 'near a bat-like man'), + 'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'), + 'Peg Cave': (0x180006, False, 'alone in a cave'), + 'Pyramid Fairy - Left': (0xE980, False, 'near a fairy'), + 'Pyramid Fairy - Right': (0xE983, False, 'near a fairy'), + 'Brewery': (0xE9EC, False, 'alone in a home'), + 'C-Shaped House': (0xE9EF, False, 'alone in a home'), + 'Chest Game': (0xEDA8, False, 'as a prize'), + 'Bumper Cave Ledge': (0x180146, False, 'on a ledge'), + 'Mire Shed - Left': (0xEA73, False, 'near sparks'), + 'Mire Shed - Right': (0xEA76, False, 'near sparks'), + 'Superbunny Cave - Top': (0xEA7C, False, 'in a connection'), + 'Superbunny Cave - Bottom': (0xEA7F, False, 'in a connection'), + 'Spike Cave': (0xEA8B, False, 'beyond spikes'), + 'Hookshot Cave - Top Right': (0xEB51, False, 'across pits'), + 'Hookshot Cave - Top Left': (0xEB54, False, 'across pits'), + 'Hookshot Cave - Bottom Right': (0xEB5A, False, 'across pits'), + 'Hookshot Cave - Bottom Left': (0xEB57, False, 'across pits'), + 'Floating Island': (0x180141, False, 'on an island'), + 'Mimic Cave': (0xE9C5, False, 'in a cave of mimicry'), + 'Swamp Palace - Entrance': (0xEA9D, False, 'in Swamp Palace'), + 'Swamp Palace - Map Chest': (0xE986, False, 'in Swamp Palace'), + 'Swamp Palace - Big Chest': (0xE989, False, 'in Swamp Palace'), + 'Swamp Palace - Compass Chest': (0xEAA0, False, 'in Swamp Palace'), + 'Swamp Palace - Big Key Chest': (0xEAA6, False, 'in Swamp Palace'), + 'Swamp Palace - West Chest': (0xEAA3, False, 'in Swamp Palace'), + 'Swamp Palace - Flooded Room - Left': (0xEAA9, False, 'in Swamp Palace'), + 'Swamp Palace - Flooded Room - Right': (0xEAAC, False, 'in Swamp Palace'), + 'Swamp Palace - Waterfall Room': (0xEAAF, False, 'in Swamp Palace'), + 'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'), + 'Thieves\' Town - Big Key Chest': (0xEA04, False, 'in Thieves\' Town'), + 'Thieves\' Town - Map Chest': (0xEA01, False, 'in Thieves\' Town'), + 'Thieves\' Town - Compass Chest': (0xEA07, False, 'in Thieves\' Town'), + 'Thieves\' Town - Ambush Chest': (0xEA0A, False, 'in Thieves\' Town'), + 'Thieves\' Town - Attic': (0xEA0D, False, 'in Thieves\' Town'), + 'Thieves\' Town - Big Chest': (0xEA10, False, 'in Thieves\' Town'), + 'Thieves\' Town - Blind\'s Cell': (0xEA13, False, 'in Thieves\' Town'), + 'Thieves\' Town - Boss': (0x180156, False, 'with Blind'), + 'Skull Woods - Compass Chest': (0xE992, False, 'in Skull Woods'), + 'Skull Woods - Map Chest': (0xE99B, False, 'in Skull Woods'), + 'Skull Woods - Big Chest': (0xE998, False, 'in Skull Woods'), + 'Skull Woods - Pot Prison': (0xE9A1, False, 'in Skull Woods'), + 'Skull Woods - Pinball Room': (0xE9C8, False, 'in Skull Woods'), + 'Skull Woods - Big Key Chest': (0xE99E, False, 'in Skull Woods'), + 'Skull Woods - Bridge Room': (0xE9FE, False, 'near Mothula'), + 'Skull Woods - Boss': (0x180155, False, 'with Mothula'), + 'Ice Palace - Compass Chest': (0xE9D4, False, 'in Ice Palace'), + 'Ice Palace - Freezor Chest': (0xE995, False, 'in Ice Palace'), + 'Ice Palace - Big Chest': (0xE9AA, False, 'in Ice Palace'), + 'Ice Palace - Iced T Room': (0xE9E3, False, 'in Ice Palace'), + 'Ice Palace - Spike Room': (0xE9E0, False, 'in Ice Palace'), + 'Ice Palace - Big Key Chest': (0xE9A4, False, 'in Ice Palace'), + 'Ice Palace - Map Chest': (0xE9DD, False, 'in Ice Palace'), + 'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'), + 'Misery Mire - Big Chest': (0xEA67, False, 'in Misery Mire'), + 'Misery Mire - Map Chest': (0xEA6A, False, 'in Misery Mire'), + 'Misery Mire - Main Lobby': (0xEA5E, False, 'in Misery Mire'), + 'Misery Mire - Bridge Chest': (0xEA61, False, 'in Misery Mire'), + 'Misery Mire - Spike Chest': (0xE9DA, False, 'in Misery Mire'), + 'Misery Mire - Compass Chest': (0xEA64, False, 'in Misery Mire'), + 'Misery Mire - Big Key Chest': (0xEA6D, False, 'in Misery Mire'), + 'Misery Mire - Boss': (0x180158, False, 'with Vitreous'), + 'Turtle Rock - Compass Chest': (0xEA22, False, 'in Turtle Rock'), + 'Turtle Rock - Roller Room - Left': (0xEA1C, False, 'in Turtle Rock'), + 'Turtle Rock - Roller Room - Right': (0xEA1F, False, 'in Turtle Rock'), + 'Turtle Rock - Chain Chomps': (0xEA16, False, 'in Turtle Rock'), + 'Turtle Rock - Big Key Chest': (0xEA25, False, 'in Turtle Rock'), + 'Turtle Rock - Big Chest': (0xEA19, False, 'in Turtle Rock'), + 'Turtle Rock - Crystaroller Room': (0xEA34, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Bottom Left': (0xEA31, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Bottom Right': (0xEA2E, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Top Left': (0xEA2B, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'), + 'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'), + 'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'), + 'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'), + 'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'), + 'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'), + 'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Basement - Right': (0xEA4F, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Maze - Top': (0xEA55, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Maze - Bottom': (0xEA58, False, 'in Palace of Darkness'), + 'Palace of Darkness - Big Chest': (0xEA40, False, 'in Palace of Darkness'), + 'Palace of Darkness - Harmless Hellway': (0xEA46, False, 'in Palace of Darkness'), + 'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'), + 'Ganons Tower - Bob\'s Torch': (0x180161, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Hope Room - Left': (0xEAD9, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Hope Room - Right': (0xEADC, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Tile Room': (0xEAE2, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Compass Room - Top Left': (0xEAE5, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Compass Room - Top Right': (0xEAE8, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Compass Room - Bottom Left': (0xEAEB, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Compass Room - Bottom Right': (0xEAEE, False, 'in Ganon\'s Tower'), + 'Ganons Tower - DMs Room - Top Left': (0xEAB8, False, 'in Ganon\'s Tower'), + 'Ganons Tower - DMs Room - Top Right': (0xEABB, False, 'in Ganon\'s Tower'), + 'Ganons Tower - DMs Room - Bottom Left': (0xEABE, False, 'in Ganon\'s Tower'), + 'Ganons Tower - DMs Room - Bottom Right': (0xEAC1, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Map Chest': (0xEAD3, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Firesnake Room': (0xEAD0, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Randomizer Room - Top Left': (0xEAC4, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Randomizer Room - Top Right': (0xEAC7, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Randomizer Room - Bottom Left': (0xEACA, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Randomizer Room - Bottom Right': (0xEACD, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Bob\'s Chest': (0xEADF, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Big Chest': (0xEAD6, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Big Key Room - Left': (0xEAF4, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Big Key Room - Right': (0xEAF7, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Big Key Chest': (0xEAF1, False, 'in Ganon\'s Tower'), + 'Ganons Tower - Mini Helmasaur Room - Left': (0xEAFD, False, 'atop Ganon\'s Tower'), + 'Ganons Tower - Mini Helmasaur Room - Right': (0xEB00, False, 'atop Ganon\'s Tower'), + 'Ganons Tower - Pre-Moldorm Chest': (0xEB03, False, 'atop Ganon\'s Tower'), + 'Ganons Tower - Validation Chest': (0xEB06, False, 'atop Ganon\'s Tower'), + 'Ganon': (None, False, 'from me'), + 'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'), + 'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'), + 'Floodgate': (None, False, None), + 'Frog': (None, False, None), + 'Missing Smith': (None, False, None), + 'Dark Blacksmith Ruins': (None, False, None), + 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'), + 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'), + 'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'), + 'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'), + 'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'), + 'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'), + 'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'), + 'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'), + 'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'), + 'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')} diff --git a/ItemList.py b/ItemList.py index 97ab4198..629f2931 100644 --- a/ItemList.py +++ b/ItemList.py @@ -209,7 +209,7 @@ difficulties = { def generate_itempool(world, player): if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] - or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): + or world.mode not in ['open', 'standard', 'swordless', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): raise NotImplementedError('Not supported yet') if world.timer in ['ohko', 'timed-ohko']: @@ -662,7 +662,7 @@ def test(): for difficulty in ['easy', 'normal', 'hard', 'expert', 'insane']: for goal in ['ganon', 'triforcehunt', 'pedestal']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: - for mode in ['open', 'standard', 'swordless']: + for mode in ['open', 'standard', 'swordless', 'inverted']: for progressive in ['on', 'off']: for shuffle in ['full', 'insane']: for retro in [True, False]: diff --git a/Main.py b/Main.py index bef644a9..33afc1d1 100644 --- a/Main.py +++ b/Main.py @@ -9,7 +9,8 @@ import time from BaseClasses import World, CollectionState, Item, Region, Location, Shop from Regions import create_regions, mark_light_world_regions -from EntranceShuffle import link_entrances +from InvertedRegions import create_inverted_regions, mark_dark_world_regions +from EntranceShuffle import link_entrances, link_inverted_entrances from Rom import patch_rom, get_enemizer_patch, apply_rom_settings, Sprite, LocalRom, JsonRom from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive @@ -38,16 +39,27 @@ def main(args, seed=None): world.difficulty_requirements = difficulties[world.difficulty] - for player in range(1, world.players + 1): - create_regions(world, player) - create_dungeons(world, player) + if world.mode != 'inverted': + for player in range(1, world.players + 1): + create_regions(world, player) + create_dungeons(world, player) + else: + for player in range(1, world.players + 1): + create_inverted_regions(world, player) + create_dungeons(world, player) logger.info('Shuffling the World about.') - for player in range(1, world.players + 1): - link_entrances(world, player) + if world.mode != 'inverted': + for player in range(1, world.players + 1): + link_entrances(world, player) - mark_light_world_regions(world) + mark_light_world_regions(world) + else: + for player in range(1, world.players + 1): + link_inverted_entrances(world, player) + + mark_dark_world_regions(world) logger.info('Generating Item Pool.') @@ -189,9 +201,14 @@ def copy_world(world): ret.fix_fake_world = world.fix_fake_world ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms - for player in range(1, world.players + 1): - create_regions(ret, player) - create_dungeons(ret, player) + if world.mode != 'inverted': + for player in range(1, world.players + 1): + create_regions(ret, player) + create_dungeons(ret, player) + else: + for player in range(1, world.players + 1): + create_inverted_regions(ret, player) + create_dungeons(ret, player) copy_dynamic_regions_and_locations(world, ret) @@ -365,7 +382,10 @@ def create_playthrough(world): old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) for _, path in dict(old_world.spoiler.paths).items(): if any(exit == 'Pyramid Fairy' for (_, exit) in path): - old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) + if world.mode != 'inverted': + old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) + else: + old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player)) # we can finally output our playthrough old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) diff --git a/README.md b/README.md index 8da432d6..4c07f5ec 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,16 @@ Special notes: - The magic barrier to Hyrule Castle Tower can be broken with a Hammer. - The Hammer can be used to activate the Ether and Bombos tablets. +### Inverted + +This mode is similar to Open but requires the Moon Pearl in order to not transform into a bunny in the Light World and the Sanctuary spawn point is moved to the Dark Sanctuary + +Special Notes: + +- Link's House is shuffled freely. The Dark Sanctuary is shuffled within West Dark World. +- There are a number of overworld changes to account for the inability to mirror from the Light World to the Dark World. +- Legacy shuffles are not implemente for this mode. + ## Game Logic This determines the Item Requirements for each location. @@ -336,7 +346,7 @@ Output a Spoiler File (default: False) Select the game logic (default: noglitches) ``` ---mode [{standard,open,swordless}] +--mode [{standard,open,swordless,inverted}] ``` Select the game mode. (default: open) diff --git a/Rom.py b/Rom.py index b16b88b6..c425169c 100644 --- a/Rom.py +++ b/Rom.py @@ -3,17 +3,18 @@ import json import hashlib import logging import os +import random import struct import subprocess -import random from BaseClasses import ShopType, Region, Location, Item from Dungeons import dungeon_music_addresses -from Text import MultiByteTextMapper, text_addresses, Credits, TextTable +from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names -from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes +from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc from Items import ItemFactory, item_table +from EntranceShuffle import door_addresses JAP10HASH = '03a63945398191337e896e5771f77173' @@ -37,6 +38,10 @@ class JsonRom(object): def write_int16(self, address, value): self.write_bytes(address, int16_as_bytes(value)) + def write_int16s(self, startaddress, values): + byte_list = [int16_as_bytes(value) for value in values] + self.patches[str(startaddress)] = [byte for bytes in byte_list for byte in bytes] + def write_int32(self, address, value): self.write_bytes(address, int32_as_bytes(value)) @@ -70,9 +75,17 @@ class LocalRom(object): def write_int16(self, address, value): self.write_bytes(address, int16_as_bytes(value)) + def write_int16s(self, startaddress, values): + for i, value in enumerate(values): + self.write_int16(startaddress + (i * 2), value) + def write_int32(self, address, value): self.write_bytes(address, int32_as_bytes(value)) + def write_int32s(self, startaddress, values): + for i, value in enumerate(values): + self.write_int32(startaddress + (i * 2), value) + def write_to_file(self, file): with open(file, 'wb') as outfile: outfile.write(self.buffer) @@ -218,14 +231,21 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shuffleene 'IcePalace': world.get_dungeon("Ice Palace", player).boss.enemizer_name, 'MiseryMire': world.get_dungeon("Misery Mire", player).boss.enemizer_name, 'TurtleRock': world.get_dungeon("Turtle Rock", player).boss.enemizer_name, - 'GanonsTower1': world.get_dungeon('Ganons Tower', player).bosses['bottom'].enemizer_name, - 'GanonsTower2': world.get_dungeon('Ganons Tower', player).bosses['middle'].enemizer_name, - 'GanonsTower3': world.get_dungeon('Ganons Tower', player).bosses['top'].enemizer_name, 'GanonsTower4': 'Agahnim2', 'Ganon': 'Ganon', } } + if world.mode != 'inverted': + options['ManualBosses']['GanonsTower1'] = world.get_dungeon('Ganons Tower', player).bosses['bottom'].enemizer_name + options['ManualBosses']['GanonsTower2'] = world.get_dungeon('Ganons Tower', player).bosses['middle'].enemizer_name + options['ManualBosses']['GanonsTower3'] = world.get_dungeon('Ganons Tower', player).bosses['top'].enemizer_name + else: + options['ManualBosses']['GanonsTower1'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].enemizer_name + options['ManualBosses']['GanonsTower2'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].enemizer_name + options['ManualBosses']['GanonsTower3'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].enemizer_name + + rom.write_to_file(randopatch_path) with open(options_path, 'w') as f: @@ -501,7 +521,9 @@ def patch_rom(world, player, rom): else: # patch door table rom.write_byte(0xDBB73 + exit.addresses, exit.target) - + if world.mode == 'inverted': + patch_shuffled_dark_sanc(world, rom, player) + write_custom_shops(rom, world, player) # patch medallion requirements @@ -527,7 +549,7 @@ def patch_rom(world, player, rom): rom.write_byte(0x51DE, 0x00) # set open mode: - if world.mode in ['open', 'swordless']: + if world.mode in ['open', 'swordless', 'inverted']: rom.write_byte(0x180032, 0x01) # open mode # disable sword sprite from uncle @@ -541,7 +563,9 @@ def patch_rom(world, player, rom): rom.write_bytes(0x6D2EB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E]) rom.write_bytes(0x6D31B, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) - else: + if world.mode == 'inverted': + set_inverted_mode(world, rom) + elif world.mode == 'standard': rom.write_byte(0x180032, 0x00) # standard mode # set light cones @@ -855,6 +879,8 @@ def patch_rom(world, player, rom): # assorted fixes rom.write_byte(0x1800A2, 0x01) # remain in real dark world when dying in dark word dungion 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.mode == 'inverted': + rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death rom.write_byte(0x180173, 0x01) # Bob is enabled rom.write_byte(0x180168, 0x08) # Spike Cave Damage @@ -877,12 +903,12 @@ def patch_rom(world, player, rom): rom.write_byte(0x18302C, 0x18) # starting max health rom.write_byte(0x18302D, 0x18) # starting current health rom.write_byte(0x183039, 0x68) # starting abilities, bit array - rom.write_byte(0x18004A, 0x00) # Inverted mode (off) + rom.write_byte(0x18004A, 0x00 if world.mode != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier - rom.write_byte(0x2AF79, 0xD0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) - rom.write_byte(0x3A943, 0xD0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) - rom.write_byte(0x3A96D, 0xF0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) - rom.write_byte(0x3A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) + rom.write_byte(0x2AF79, 0xD0 if world.mode != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) + rom.write_byte(0x3A943, 0xD0 if world.mode != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) + rom.write_byte(0x3A96D, 0xF0 if world.mode != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) + rom.write_byte(0x3A9A7, 0xD0 if world.mode != 'inverted' else 0xF0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) rom.write_bytes(0x180080, [50, 50, 70, 70]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) @@ -1321,6 +1347,18 @@ def write_strings(rom, world, player): bombos_text = 'Some Hot Air' if bombositem is None else hint_text(bombositem, True) if bombositem.pedestal_hint_text is not None else 'Unknown Item' tt['tablet_bombos_book'] = bombos_text + # inverted spawn menu changes + if world.mode == 'inverted': + tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}" + tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}" + tt['intro_main'] = CompressedTextMapper.convert( + "{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n" + + "{PAUSE3}\nInverted\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n" + + "{PAUSE3}\nLink has been transported to the Dark World\n{PAUSE3}\nWhile he was slumbering\n" + + "{PAUSE3}\nWhatever will happen?\n{PAUSE3}\n{CHANGEPIC}\nGanon has moved around all the items in Hyrule.\n" + + "{PAUSE7}\nYou will have to find all the items necessary to beat Ganon.\n" + + "{PAUSE7}\nThis is your chance to be a hero.\n{PAUSE3}\n{CHANGEPIC}\n" + + "You must get the 7 crystals to beat Ganon.\n{PAUSE9}\n{CHANGEPIC}", False) rom.write_bytes(0xE0000, tt.getBytes()) credits = Credits() @@ -1359,6 +1397,218 @@ def write_strings(rom, world, player): rom.write_bytes(0x181500, data) rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]]) +def set_inverted_mode(world, rom): + rom.write_byte(snes_to_pc(0x0283E0), 0xF0) # residual portals + rom.write_byte(snes_to_pc(0x02B34D), 0xF0) + rom.write_byte(snes_to_pc(0x06DB78), 0x8B) + rom.write_byte(snes_to_pc(0x0DB3C5), 0xC6) + rom.write_byte(snes_to_pc(0x07A3F4), 0xF0) # duck + rom.write_int16s(snes_to_pc(0x02E849), [0x0043, 0x0056, 0x0058, 0x006C, 0x006F, 0x0070, 0x007B, 0x007F, 0x001B]) # dw flute + rom.write_int16(snes_to_pc(0x02E8D5), 0x07C8) + rom.write_int16(snes_to_pc(0x02E8F7), 0x01F8) + rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof + # the following bytes should only be written in vanilla + # or they'll overwrite the randomizer's shuffles + if world.shuffle == 'vanilla': + rom.write_byte(0x15B8C, 0x6C) + rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house + rom.write_byte(0xDBB73 + 0x52, 0x01) + rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT + rom.write_byte(0xDBB73 + 0x36, 0x24) + rom.write_int16(0x15AEE + 2*0x38, 0x00E0) + rom.write_int16(0x15AEE + 2*0x25, 0x000C) + rom.write_byte(0xDBB73 + 0x15, 0x06) # bumper and old man cave + rom.write_int16(0x15AEE + 2*0x17, 0x00F0) + rom.write_byte(0xDBB73 + 0x05, 0x16) + rom.write_int16(0x15AEE + 2*0x07, 0x00FB) + rom.write_byte(0xDBB73 + 0x2D, 0x17) + rom.write_int16(0x15AEE + 2*0x2F, 0x00EB) + rom.write_byte(0xDBB73 + 0x06, 0x2E) + rom.write_int16(0x15AEE + 2*0x08, 0x00E6) + rom.write_byte(0xDBB73 + 0x16, 0x5E) + rom.write_byte(0xDBB73 + 0x6F, 0x07) # DDM fairy to old man cave + rom.write_int16(0x15AEE + 2*0x18, 0x00F1) + rom.write_byte(0x15B8C + 0x18, 0x43) + rom.write_int16(0x15BDB + 2 * 0x18, 0x1400) + rom.write_int16(0x15C79 + 2 * 0x18, 0x0294) + rom.write_int16(0x15D17 + 2 * 0x18, 0x0600) + rom.write_int16(0x15DB5 + 2 * 0x18, 0x02E8) + rom.write_int16(0x15E53 + 2 * 0x18, 0x0678) + rom.write_int16(0x15EF1 + 2 * 0x18, 0x0303) + rom.write_int16(0x15F8F + 2 * 0x18, 0x0685) + rom.write_byte(0x1602D + 0x18, 0x0A) + rom.write_byte(0x1607C + 0x18, 0xF6) + rom.write_int16(0x160CB + 2 * 0x18, 0x0000) + rom.write_int16(0x16169 + 2 * 0x18, 0x0000) + rom.write_int16(0x15AEE + 2 * 0x3D, 0x0003) # pyramid exit and houlihan + rom.write_byte(0x15B8C + 0x3D, 0x5B) + rom.write_int16(0x15BDB + 2 * 0x3D, 0x0B0E) + rom.write_int16(0x15C79 + 2 * 0x3D, 0x075A) + rom.write_int16(0x15D17 + 2 * 0x3D, 0x0674) + rom.write_int16(0x15DB5 + 2 * 0x3D, 0x07A8) + rom.write_int16(0x15E53 + 2 * 0x3D, 0x06E8) + rom.write_int16(0x15EF1 + 2 * 0x3D, 0x07C7) + rom.write_int16(0x15F8F + 2 * 0x3D, 0x06F3) + rom.write_byte(0x1602D + 0x3D, 0x06) + rom.write_byte(0x1607C + 0x3D, 0xFA) + rom.write_int16(0x160CB + 2 * 0x3D, 0x0000) + rom.write_int16(0x16169 + 2 * 0x3D, 0x0000) + rom.write_int16(snes_to_pc(0x02D8D4), 0x112) # change sactuary spawn point to dark sanc + rom.write_bytes(snes_to_pc(0x02D8E8), [0x22, 0x22, 0x22, 0x23, 0x04, 0x04, 0x04, 0x05]) + rom.write_int16(snes_to_pc(0x02D91A), 0x0400) + rom.write_int16(snes_to_pc(0x02D928), 0x222E) + rom.write_int16(snes_to_pc(0x02D936), 0x229A) + rom.write_int16(snes_to_pc(0x02D944), 0x0480) + rom.write_int16(snes_to_pc(0x02D952), 0x00A5) + rom.write_int16(snes_to_pc(0x02D960), 0x007F) + rom.write_byte(snes_to_pc(0x02D96D), 0x14) + rom.write_byte(snes_to_pc(0x02D974), 0x00) + rom.write_byte(snes_to_pc(0x02D97B), 0xFF) + rom.write_byte(snes_to_pc(0x02D982), 0x00) + rom.write_byte(snes_to_pc(0x02D989), 0x02) + rom.write_byte(snes_to_pc(0x02D990), 0x00) + rom.write_int16(snes_to_pc(0x02D998), 0x0000) + rom.write_int16(snes_to_pc(0x02D9A6), 0x005A) + rom.write_byte(snes_to_pc(0x02D9B3), 0x12) + # keep the old man spawn point at old man house unless shuffle is vanilla + if world.shuffle == 'vanilla': + rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) + rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1) + rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03]) + rom.write_int16(snes_to_pc(0x02D924), 0x0300) + rom.write_int16(snes_to_pc(0x02D932), 0x1F10) + rom.write_int16(snes_to_pc(0x02D940), 0x1FC0) + rom.write_int16(snes_to_pc(0x02D94E), 0x0378) + rom.write_int16(snes_to_pc(0x02D95C), 0x0187) + rom.write_int16(snes_to_pc(0x02D96A), 0x017F) + rom.write_byte(snes_to_pc(0x02D972), 0x06) + rom.write_byte(snes_to_pc(0x02D979), 0x00) + rom.write_byte(snes_to_pc(0x02D980), 0xFF) + rom.write_byte(snes_to_pc(0x02D987), 0x00) + rom.write_byte(snes_to_pc(0x02D98E), 0x22) + rom.write_byte(snes_to_pc(0x02D995), 0x12) + rom.write_int16(snes_to_pc(0x02D9A2), 0x0000) + rom.write_int16(snes_to_pc(0x02D9B0), 0x0007) + rom.write_byte(snes_to_pc(0x02D9B8), 0x12) + rom.write_bytes(0x180247, [0x00, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00]) + rom.write_int16(0x15AEE + 2 * 0x06, 0x0020) # post aga hyrule castle spawn + rom.write_byte(0x15B8C + 0x06, 0x1B) + rom.write_int16(0x15BDB + 2 * 0x06, 0x00AE) + rom.write_int16(0x15C79 + 2 * 0x06, 0x0610) + rom.write_int16(0x15D17 + 2 * 0x06, 0x077E) + rom.write_int16(0x15DB5 + 2 * 0x06, 0x0672) + rom.write_int16(0x15E53 + 2 * 0x06, 0x07F8) + rom.write_int16(0x15EF1 + 2 * 0x06, 0x067D) + rom.write_int16(0x15F8F + 2 * 0x06, 0x0803) + rom.write_byte(0x1602D + 0x06, 0x00) + rom.write_byte(0x1607C + 0x06, 0xF2) + rom.write_int16(0x160CB + 2 * 0x06, 0x0000) + rom.write_int16(0x16169 + 2 * 0x06, 0x0000) + rom.write_int16(snes_to_pc(0x02E87B), 0x00AE) # move flute splot 9 + rom.write_int16(snes_to_pc(0x02E89D), 0x0610) + rom.write_int16(snes_to_pc(0x02E8BF), 0x077E) + rom.write_int16(snes_to_pc(0x02E8E1), 0x0672) + rom.write_int16(snes_to_pc(0x02E903), 0x07F8) + rom.write_int16(snes_to_pc(0x02E925), 0x067D) + rom.write_int16(snes_to_pc(0x02E947), 0x0803) + rom.write_int16(snes_to_pc(0x02E969), 0x0000) + rom.write_int16(snes_to_pc(0x02E98B), 0xFFF2) + rom.write_byte(snes_to_pc(0x1AF696), 0xF0) # bat sprite retreat + rom.write_byte(snes_to_pc(0x1AF6B2), 0x33) + rom.write_bytes(snes_to_pc(0x1AF730), [0x6A, 0x9E, 0x0C, 0x00, 0x7A, 0x9E, 0x0C, + 0x00, 0x8A, 0x9E, 0x0C, 0x00, 0x6A, 0xAE, + 0x0C, 0x00, 0x7A, 0xAE, 0x0C, 0x00, 0x8A, + 0xAE, 0x0C, 0x00, 0x67, 0x97, 0x0C, 0x00, + 0x8D, 0x97, 0x0C, 0x00]) + rom.write_int16s(snes_to_pc(0x0FF1C8), [0x190F, 0x190F, 0x190F, 0x194C, 0x190F, + 0x194B, 0x190F, 0x195C, 0x594B, 0x194C, + 0x19EE, 0x19EE, 0x194B, 0x19EE, 0x19EE, + 0x19EE, 0x594B, 0x190F, 0x595C, 0x190F, + 0x190F, 0x195B, 0x190F, 0x190F, 0x19EE, + 0x19EE, 0x195C, 0x19EE, 0x19EE, 0x19EE, + 0x19EE, 0x595C, 0x595B, 0x190F, 0x190F, + 0x190F]) + rom.write_int16s(snes_to_pc(0x0FA480), [0x190F, 0x196B, 0x9D04, 0x9D04, 0x196B, + 0x190F, 0x9D04, 0x9D04]) + rom.write_int16s(snes_to_pc(0x1bb810), [0x00BE, 0x00C0, 0x013E]) + rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B]) + rom.write_int16(snes_to_pc(0x308300), 0x0140) + rom.write_int16(snes_to_pc(0x308320), 0x001B) + if world.shuffle == 'vanilla': + rom.write_byte(snes_to_pc(0x308340), 0x7B) + rom.write_int16(snes_to_pc(0x1af504), 0x148B) + rom.write_int16(snes_to_pc(0x1af50c), 0x149B) + rom.write_int16(snes_to_pc(0x1af514), 0x14A4) + rom.write_int16(snes_to_pc(0x1af51c), 0x1489) + rom.write_int16(snes_to_pc(0x1af524), 0x14AC) + rom.write_int16(snes_to_pc(0x1af52c), 0x54AC) + rom.write_int16(snes_to_pc(0x1af534), 0x148C) + rom.write_int16(snes_to_pc(0x1af53c), 0x548C) + rom.write_int16(snes_to_pc(0x1af544), 0x1484) + rom.write_int16(snes_to_pc(0x1af54c), 0x5484) + rom.write_int16(snes_to_pc(0x1af554), 0x14A2) + rom.write_int16(snes_to_pc(0x1af55c), 0x54A2) + rom.write_int16(snes_to_pc(0x1af564), 0x14A0) + rom.write_int16(snes_to_pc(0x1af56c), 0x54A0) + rom.write_int16(snes_to_pc(0x1af574), 0x148E) + rom.write_int16(snes_to_pc(0x1af57c), 0x548E) + rom.write_int16(snes_to_pc(0x1af584), 0x14AE) + rom.write_int16(snes_to_pc(0x1af58c), 0x54AE) + rom.write_byte(snes_to_pc(0x00DB9D), 0x1A) # castle hole graphics + rom.write_byte(snes_to_pc(0x00DC09), 0x1A) + rom.write_byte(snes_to_pc(0x00D009), 0x31) + rom.write_byte(snes_to_pc(0x00D0e8), 0xE0) + rom.write_byte(snes_to_pc(0x00D1c7), 0x00) + rom.write_int16(snes_to_pc(0x1BE8DA), 0x39AD) + rom.write_byte(0xF6E58, 0x80) # no whirlpool under castle gate + rom.write_bytes(0x0086E, [0x5C, 0x00, 0xA0, 0xA1]) # TR tail + rom.write_bytes(snes_to_pc(0x1BC67A), [0x2E, 0x0B, 0x82]) # add warps under rocks + rom.write_bytes(snes_to_pc(0x1BC81E), [0x94, 0x1D, 0x82]) + rom.write_bytes(snes_to_pc(0x1BC655), [0x4A, 0x1D, 0x82]) + rom.write_bytes(snes_to_pc(0x1BC80D), [0xB2, 0x0B, 0x82]) + rom.write_bytes(snes_to_pc(0x1BC3DF), [0xD8, 0xD1]) + rom.write_bytes(snes_to_pc(0x1BD1D8), [0xA8, 0x02, 0x82, 0xFF, 0xFF]) + rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) + rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door + rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4) + if world.shuffle == 'vanilla': + rom.write_byte(0xDBB73 + 0x35, 0x36) + rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp + if world.shuffle == 'vanilla': + rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area + rom.write_byte(0x15B8C + 0x37, 0x1B) + rom.write_int16(0x15BDB + 2 * 0x37, 0x0418) + rom.write_int16(0x15C79 + 2 * 0x37, 0x0679) + rom.write_int16(0x15D17 + 2 * 0x37, 0x06B4) + rom.write_int16(0x15DB5 + 2 * 0x37, 0x06C6) + rom.write_int16(0x15E53 + 2 * 0x37, 0x0738) + rom.write_int16(0x15EF1 + 2 * 0x37, 0x06E6) + rom.write_int16(0x15F8F + 2 * 0x37, 0x0733) + rom.write_byte(0x1602D + 0x37, 0x07) + rom.write_byte(0x1607C + 0x37, 0xF9) + rom.write_int16(0x160CB + 2 * 0x37, 0x0000) + rom.write_int16(0x16169 + 2 * 0x37, 0x0000) + rom.write_bytes(snes_to_pc(0x1BC387), [0xDD, 0xD1]) + rom.write_bytes(snes_to_pc(0x1BD1DD), [0xA4, 0x06, 0x82, 0x9E, 0x06, 0x82, 0xFF, 0xFF]) + rom.write_byte(0x180089, 0x01) # open TR after exit + rom.write_byte(snes_to_pc(0x0ABFBB), 0x90) + rom.write_byte(snes_to_pc(0x0280A6), 0xD0) + rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05]) + +def patch_shuffled_dark_sanc(world, rom, player): + dark_sanc_entrance = str(world.get_region('Inverted Dark Sanctuary', player).entrances[0].name) + room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1] + if dark_sanc_entrance == 'Skull Woods Final Section': + link_y = 0x00F8 + door_index = door_addresses[str(dark_sanc_entrance)][0] + + rom.write_byte(0x180241, 0x01) + rom.write_byte(0x180248, door_index + 1) + rom.write_int16(0x180250, room_id) + rom.write_byte(0x180252, ow_area) + rom.write_int16s(0x180253, [vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x]) + rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00]) + InconvenientEntrances = {'Turtle Rock': 'Turtle Rock Main', 'Misery Mire': 'Misery Mire', 'Ice Palace': 'Ice Palace', diff --git a/Rules.py b/Rules.py index bf6f0a50..17b6f4e9 100644 --- a/Rules.py +++ b/Rules.py @@ -6,13 +6,21 @@ def set_rules(world, player): if world.logic == 'nologic': logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!') - world.get_region('Links House', player).can_reach = lambda state: True - world.get_region('Sanctuary', player).can_reach = lambda state: True - old_rule = world.get_region('Old Man House', player).can_reach - world.get_region('Old Man House', player).can_reach = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) - return - - global_rules(world, player) + if world.mode != 'inverted': + world.get_region('Links House', player).can_reach = lambda state: True + world.get_region('Sanctuary', player).can_reach = lambda state: True + old_rule = world.get_region('Old Man House', player).can_reach + world.get_region('Old Man House', player).can_reach = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) + return + else: + world.get_region('Inverted Links House', player).can_reach = lambda state: True + world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach = lambda state: True + if world.shuffle != 'vanilla': + old_rule = world.get_region('Old Man House', player).can_reach + world.get_region('Old Man House', player).can_reach = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) + return + if world.mode != 'inverted': + global_rules(world, player) if world.mode == 'open': open_rules(world, player) @@ -20,6 +28,9 @@ def set_rules(world, player): standard_rules(world, player) elif world.mode == 'swordless': swordless_rules(world, player) + elif world.mode == 'inverted': + open_rules(world, player) + inverted_rules(world, player) else: raise NotImplementedError('Not implemented yet') @@ -36,15 +47,20 @@ def set_rules(world, player): elif world.goal == 'ganon': # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) - - set_big_bomb_rules(world, player) + + if world.mode != 'inverted': + set_big_bomb_rules(world, player) + else: + set_inverted_big_bomb_rules(world, 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 Palace Moat', player), lambda state: state.has_Mirror(player)) - set_bunny_rules(world, player) - + if world.mode != 'inverted': + set_bunny_rules(world, player) + else: + set_inverted_bunny_rules(world, player) def set_rule(spot, rule): spot.access_rule = rule @@ -301,7 +317,7 @@ def global_rules(world, player): for location in ['Skull Woods - Boss']: forbid_item(world.get_location(location, player), 'Small Key (Skull Woods)', player) - set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or (state.has('Bombos', player) and state.has_sword(player))) + set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.can_melt_things(player)) set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state.has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state.has_key('Small Key (Ice Palace)', player, 1)))) # TODO: investigate change from VT. Changed to hookshot or 2 keys (no checking for big key in specific chests) @@ -422,14 +438,388 @@ def global_rules(world, player): set_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has('Crystal 1', player) and state.has('Crystal 2', player) and state.has('Crystal 3', player) and state.has('Crystal 4', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player) and state.has('Crystal 7', player)) +def inverted_rules(world, player): + world.get_location('Ganon', player).item_rule = lambda item: item.name == 'Triforce' and item.player == player + world.get_region('Inverted Links House', player).can_reach = lambda state: True + world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach = lambda state: True + + # we can s&q to the old man house after we rescue him. This may be somewhere completely different if caves are shuffled! + if world.shuffle != 'vanilla': + old_rule = world.get_region('Old Man House', player).can_reach + world.get_region('Old Man House', player).can_reach = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) + # overworld requirements + set_rule(world.get_location('Maze Race', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Potion Shop Pier', player), lambda state: state.has('Flippers', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Light World Pier', player), lambda state: state.has('Flippers', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player) and state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Kings Grave Inner Rocks', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Potion Shop Inner Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Potion Shop Outer Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Potion Shop Outer Rock', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Potion Shop Inner Rock', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Graveyard Cave Inner Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Graveyard Cave Outer Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Secret Passage Inner Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Secret Passage Outer Bushes', player), lambda state: state.has_Pearl(player)) + # Caution: If king's grave is releaxed 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) + set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player) and state.has_Pearl(player)) + add_rule(world.get_location('Sunken Treasure', player), lambda state: state.can_reach('Light World', 'Region', player) and state.has('Open Floodgate', player)) + set_rule(world.get_entrance('Bat Cave Drop Ledge', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has_Pearl(player) and state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Desert Palace Stairs', player), lambda state: state.has('Book of Mudora', player)) # bunny can use book + set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('20 Rupee Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Death Mountain Entrance Rock', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Dark Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.can_flute(player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('East Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('South Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('West Dark World Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) + set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player) and state.has_Pearl(player)) + set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) + set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest + + set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Flippers', player) and state.has_Pearl(player)) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo + set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player) or (state.can_reach('Light World', 'Region', player) and state.has_Mirror(player))) + set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith + 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) and state.has_Pearl(player)) + set_rule(world.get_location('Sick Kid', player), lambda state: state.has_bottle(player)) + set_rule(world.get_location('Mushroom', player), lambda state: state.has_Pearl(player)) # need pearl to pick up bushes + set_rule(world.get_entrance('Bush Covered Lawn Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Bush Covered Lawn Inner Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Bush Covered Lawn Outer Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Bomb Hut Inner Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Bomb Hut Outer Bushes', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('North Fairy Cave Drop', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Lost Woods Hideout Drop', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_location('Library', player), lambda state: state.has_Boots(player)) + set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and (state.can_reach('Potion Shop Area', 'Region', player))) # new inverted region, need pearl for bushes or access to potion shop door/waterfall fairy + set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Desert Ledge Return Rocks', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) # should we decide to place something that is not a dungeon end up there at some point + set_rule(world.get_entrance('Checkerboard Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(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)) + set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) + set_rule(world.get_entrance('Agahnim 1', player), lambda state: state.has_sword(player) and state.has_key('Small Key (Agahnims Tower)', player, 2)) + set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) + set_rule(world.get_location('Castle Tower - Dark Maze', player), lambda state: state.has_key('Small Key (Agahnims Tower)', player)) + set_rule(world.get_entrance('LW Hyrule Castle Ledge SQ', player), lambda state: state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('EDM Hyrule Castle Ledge SQ', player), lambda state: state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('WDM Hyrule Castle Ledge SQ', player), lambda state: state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('Hyrule Castle Secret Entrance Drop', player), lambda state: state.has_Pearl(player)) + set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up + set_rule(world.get_entrance('Broken Bridge (West)', player), lambda state: state.has('Hookshot', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Broken Bridge (East)', player), lambda state: state.has('Hookshot', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Dark Death Mountain Teleporter (East Bottom)', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Fairy Ascension Rocks', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has('Mirror', player)) # can erase block + set_rule(world.get_entrance('Death Mountain (Top)', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Dark Death Mountain Teleporter (East)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has('Hammer', player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) + set_rule(world.get_entrance('East Death Mountain (Top)', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) # bunny can not use hammer + + set_rule(world.get_location('Catfish', player), lambda state: state.can_lift_rocks(player) or (state.has('Flippers', player) and state.has_Mirror(player) and state.has_Pearl(player) and state.can_reach('Light World', 'Region', player))) + set_rule(world.get_entrance('Northeast Dark World Broken Bridge Pass', player), lambda state: ((state.can_lift_rocks(player) or state.has('Hammer', player)) or state.has('Flippers', player))) + set_rule(world.get_entrance('East Dark World Broken Bridge Pass', player), lambda state: (state.can_lift_rocks(player) or state.has('Hammer', player))) + set_rule(world.get_entrance('South Dark World Bridge', 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('West Dark World Gap', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (South)', player), lambda state: state.has('Flippers', player)) # ToDo any fake flipper set up? + set_rule(world.get_entrance('Dark Lake Hylia Ledge Pier', player), lambda state: state.has('Flippers', 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('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) # Fake Flippers + set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has('Flippers', player)) # ToDo any fake flipper set up? (Qirn Jump) + set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Hammer Peg Area Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Dark World Hammer Peg Cave', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Village of Outcasts Eastern Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Peg Area Rocks', player), lambda state: state.can_lift_heavy_rocks(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('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player)) + set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', 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.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) + + set_rule(world.get_entrance('Hookshot Cave', player), lambda state: state.can_lift_rocks(player)) + + set_rule(world.get_entrance('East Death Mountain Mirror Spot (Top)', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Death Mountain (Top) Mirror Spot', player), lambda state: state.has_Mirror(player)) + + set_rule(world.get_entrance('East Death Mountain Mirror Spot (Bottom)', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Dark Death Mountain Ledge Mirror Spot (East)', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Dark Death Mountain Ledge Mirror Spot (West)', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Laser Bridge Mirror Spot', player), lambda state: state.has_Mirror(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 + + 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 + (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)))))) + ) + + 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)) + set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) + set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) + + # new inverted spots + set_rule(world.get_entrance('Post Aga Teleporter', player), lambda state: state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('Mire Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Desert Palace Stairs Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Death Mountain Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('East Dark World Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('West Dark World Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('South Dark World Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Northeast Dark World Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Potion Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Shopping Mall Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Maze Race Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Desert Palace North Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Death Mountain (Top) Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Graveyard Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Bomb Hut Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Skull Woods Mirror Spot', player), lambda state: state.has_Mirror(player)) + + # inverted flute spots + + set_rule(world.get_entrance('DDM Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('NEDW Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('WDW Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('SDW Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('EDW Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('DLHL Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('DD Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('EDDM Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.can_flute(player)) + set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.can_flute(player)) + + set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player)) + set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player)) + + set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) + set_rule(world.get_location('Eastern Palace - Boss', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Eastern Palace - Prize', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + for location in ['Eastern Palace - Boss', 'Eastern Palace - Big Chest']: + forbid_item(world.get_location(location, player), 'Big Key (Eastern Palace)', player) + + set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) + set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state.has_key('Small Key (Desert Palace)', player)) + set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and world.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state.has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and world.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) + for location in ['Desert Palace - Boss', 'Desert Palace - Big Chest']: + forbid_item(world.get_location(location, player), 'Big Key (Desert Palace)', player) + + for location in ['Desert Palace - Boss', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']: + forbid_item(world.get_location(location, player), 'Small Key (Desert Palace)', player) + + set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state.has_key('Small Key (Tower of Hera)', player) or item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player)) + set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player)) + set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) + set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player)) + set_always_allow(world.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) + set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player)) + for location in ['Tower of Hera - Boss', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']: + forbid_item(world.get_location(location, player), 'Big Key (Tower of Hera)', player) +# for location in ['Tower of Hera - Big Key Chest']: +# forbid_item(world.get_location(location, player), 'Small Key (Tower of Hera)', player) + + set_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) + add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) + + set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state.has_key('Small Key (Swamp Palace)', player)) + set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player) or item_name(state, 'Swamp Palace - Big Chest', player) == ('Big Key (Swamp Palace)', player)) + set_always_allow(world.get_location('Swamp Palace - Big Chest', player), lambda state, item: item.name == 'Big Key (Swamp Palace)' and item.player == player) + set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player)) + set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', player)) + for location in ['Swamp Palace - Entrance']: + forbid_item(world.get_location(location, player), 'Big Key (Swamp Palace)', player) + + set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) + set_rule(world.get_entrance('Blind Fight', player), lambda state: state.has_key('Small Key (Thieves Town)', player)) + set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Prize', player)) + set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has_key('Small Key (Thieves Town)', player) or item_name(state, 'Thieves\' Town - Big Chest', player) == ('Small Key (Thieves Town)', player)) and state.has('Hammer', player)) + set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player and state.has('Hammer', player)) + set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state.has_key('Small Key (Thieves Town)', player)) + for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: + forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player) + for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Boss']: + forbid_item(world.get_location(location, player), 'Small Key (Thieves Town)', player) + + set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player)) + set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player)) + set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section + set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) + set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) or item_name(state, 'Skull Woods - Big Chest', player) == ('Big Key (Skull Woods)', player)) + set_always_allow(world.get_location('Skull Woods - Big Chest', player), lambda state, item: item.name == 'Big Key (Skull Woods)' and item.player == player) + set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player) and state.has_sword(player)) # sword required for curtain + set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) + for location in ['Skull Woods - Boss']: + forbid_item(world.get_location(location, player), 'Small Key (Skull Woods)', player) + + set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.can_melt_things(player)) + set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) + set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state.has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state.has_key('Small Key (Ice Palace)', player, 1)))) + # TODO: investigate change from VT. Changed to hookshot or 2 keys (no checking for big key in specific chests) + set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or (item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state.has_key('Small Key (Ice Palace)', player))) and (state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))) + set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', 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)) + for location in ['Ice Palace - Big Chest', 'Ice Palace - Boss']: + forbid_item(world.get_location(location, player), 'Big Key (Ice Palace)', player) + + set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has_Boots(player) or state.has('Hookshot', player)) and (state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player))) # need to defeat wizzrobes, bombs don't work ... + set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player)) + set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) + set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player)) + # you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ... + # big key gives backdoor access to that from the teleporter in the north west + set_rule(world.get_location('Misery Mire - Map Chest', player), lambda state: state.has_key('Small Key (Misery Mire)', player, 1) or state.has('Big Key (Misery Mire)', player)) + # in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key... + set_rule(world.get_location('Misery Mire - Main Lobby', player), lambda state: state.has_key('Small Key (Misery Mire)', player, 2) or state.has_key('Big Key (Misery Mire)', player)) + # we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet + set_rule(world.get_entrance('Misery Mire (West)', player), lambda state: state.has_key('Small Key (Misery Mire)', player, 2) if ((item_name(state, 'Misery Mire - Compass Chest', player) in [('Big Key (Misery Mire)', player)]) or + (item_name(state, 'Misery Mire - Big Key Chest', player) in [('Big Key (Misery Mire)', player)])) else state.has_key('Small Key (Misery Mire)', player, 3)) + set_rule(world.get_location('Misery Mire - Compass Chest', player), lambda state: state.has_fire_source(player)) + set_rule(world.get_location('Misery Mire - Big Key Chest', player), lambda state: state.has_fire_source(player)) + set_rule(world.get_entrance('Misery Mire (Vitreous)', 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)) + for location in ['Misery Mire - Big Chest', 'Misery Mire - Boss']: + forbid_item(world.get_location(location, player), 'Big Key (Misery Mire)', player) + + set_rule(world.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player)) # We could get here from the middle section without Cane as we don't cross the entrance gap! + set_rule(world.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + set_rule(world.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player))) + set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) + set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player)) + set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 4) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) + set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize', player)) + + set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: state.can_shoot_arrows(player)) + set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area + set_rule(world.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and state.can_shoot_arrows(player) and state.has('Hammer', player)) + set_rule(world.get_entrance('Palace of Darkness (North)', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 4)) + set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player)) + + set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 3))) + set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5)) + + set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 4))) + set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5)) + set_rule(world.get_entrance('Palace of Darkness Maze Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6)) + set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player)) + + # these key rules are conservative, you might be able to get away with more lenient rules + randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] + compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'] + + set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player)) + + set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4) or (item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player), ('Small Key (Ganons Tower)', player)] and state.has_key('Small Key (Ganons Tower)', player, 3))) + set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state.has_key('Small Key (Ganons Tower)', player, 3)) + + # It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements. + # However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos. + set_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 2)) + # It is possible to need more than 3 keys .... + set_rule(world.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3)) + + #The actual requirements for these rooms to avoid key-lock + set_rule(world.get_location('Ganons Tower - Firesnake Room', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state.has_key('Small Key (Ganons Tower)', player, 2))) + for location in randomizer_room_chests: + set_rule(world.get_location(location, player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state.has_key('Small Key (Ganons Tower)', player, 3))) + + # Once again it is possible to need more than 3 keys... + set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3) and state.has('Fire Rod', player)) + # Actual requirements + for location in compass_room_chests: + set_rule(world.get_location(location, player), lambda state: state.has('Fire Rod', player) and (state.has_key('Small Key (Ganons Tower)', player, 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state.has_key('Small Key (Ganons Tower)', player, 3)))) + + set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player)) + + set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player), lambda state: world.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Big Key Chest', player), lambda state: world.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player), lambda state: world.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + + set_rule(world.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player) and state.can_shoot_arrows(player)) + set_rule(world.get_entrance('Ganons Tower Torch Rooms', player), lambda state: state.has_fire_source(player) and world.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3)) + set_rule(world.get_entrance('Ganons Tower Moldorm Door', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4)) + set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state)) + set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) + set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player)) + for location in ['Ganons Tower - Big Chest', 'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right', + 'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']: + forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player) + + set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has('Crystal 1', player) and state.has('Crystal 2', player) + and state.has('Crystal 3', player) and state.has('Crystal 4', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player) and state.has('Crystal 7', player) + and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times + set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop + + set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. + + set_trock_key_rules(world, player) + + set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: state.has('Crystal 1', player) and state.has('Crystal 2', player) and state.has('Crystal 3', player) and state.has('Crystal 4', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player) and state.has('Crystal 7', player)) def no_glitches_rules(world, player): - set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) - set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to - set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) - set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) - set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + if world.mode != 'inverted': + set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) + set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + else: + set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player))) + set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has('Flippers', player)) + 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'] @@ -465,8 +855,12 @@ def no_glitches_rules(world, player): add_conditional_lamp('Palace of Darkness Maze Door', 'Palace of Darkness (Entrance)', 'Entrance') add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'Palace of Darkness (Entrance)', 'Location') add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', 'Location') - add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') - add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower', 'Location') + if world.mode != 'inverted': + add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') + add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower', 'Location') + else: + add_conditional_lamp('Agahnim 1', 'Inverted Agahnims Tower', 'Entrance') + add_conditional_lamp('Castle Tower - Dark Maze', 'Inverted Agahnims Tower', 'Location') add_conditional_lamp('Old Man', 'Old Man Cave', 'Location') add_conditional_lamp('Old Man Cave Exit (East)', 'Old Man Cave', 'Entrance') add_conditional_lamp('Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave', 'Entrance') @@ -528,12 +922,13 @@ def standard_rules(world, player): def set_trock_key_rules(world, player): - all_state = world.get_all_state(True) # First set all relevant locked doors to impassible. for entrance in ['Turtle Rock Dark Room Staircase', 'Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock Pokey Room']: set_rule(world.get_entrance(entrance, player), lambda state: False) + all_state = world.get_all_state(True) + # Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon. can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge is None else world.can_access_trock_eyebridge world.can_access_trock_eyebridge = can_reach_back @@ -840,6 +1235,176 @@ def set_big_bomb_rules(world, player): # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.has('Ocarina', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) +def set_inverted_big_bomb_rules(world, player): + bombshop_entrance = world.get_region('Inverted Big Bomb Shop', player).entrances[0] + Normal_LW_entrances = ['Blinds Hideout', + 'Bonk Fairy (Light)', + 'Lake Hylia Fairy', + 'Light Hype Fairy', + 'Desert Fairy', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Blacksmiths Hut', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Fortune Teller (Light)', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Bonk Rock Cave', + 'Library', + 'Potion Shop', + 'Waterfall of Wishing', + 'Dam', + 'Lumberjack House', + 'Lake Hylia Fortune Teller', + 'Eastern Palace', + 'Kakariko Gamble Game', + 'Kakariko Well Cave', + 'Bat Cave Cave', + 'Elder House (East)', + 'Elder House (West)', + 'North Fairy Cave', + 'Lost Woods Hideout Stump', + 'Lumberjack Tree Cave', + 'Two Brothers House (East)', + 'Sanctuary', + 'Hyrule Castle Entrance (South)', + 'Hyrule Castle Secret Entrance Stairs'] + LW_walkable_entrances = ['Dark Lake Hylia Ledge Fairy', + 'Dark Lake Hylia Ledge Spike Cave', + 'Dark Lake Hylia Ledge Hint', + 'Mire Shed', + 'Dark Desert Hint', + 'Dark Desert Fairy', + 'Misery Mire'] + Northern_DW_entrances = ['Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Hammer Peg Cave', + 'Red Shield Shop', + 'Dark Sanctuary Hint', + 'Fortune Teller (Dark)', + 'Dark World Shop', + 'Dark World Lumberjack Shop', + 'Thieves Town', + 'Skull Woods First Section Door', + 'Skull Woods Second Section Door (East)'] + Southern_DW_entrances = ['Hype Cave', + 'Bonk Fairy (Dark)', + 'Archery Game', + 'Inverted Big Bomb Shop', + 'Dark Lake Hylia Shop', + 'Swamp Palace'] + Isolated_DW_entrances = ['Spike Cave', + 'Cave Shop (Dark Death Mountain)', + 'Dark Death Mountain Fairy', + 'Mimic Cave', + 'Skull Woods Second Section Door (West)', + 'Skull Woods Final Section', + 'Ice Palace', + 'Turtle Rock', + 'Dark Death Mountain Ledge (West)', + 'Dark Death Mountain Ledge (East)', + 'Bumper Cave (Top)', + 'Superbunny Cave (Top)', + 'Superbunny Cave (Bottom)', + 'Hookshot Cave', + 'Turtle Rock Isolated Ledge Entrance', + 'Hookshot Cave Back Entrance', + 'Inverted Ganons Tower'] + Isolated_LW_entrances = ['Capacity Upgrade', + 'Tower of Hera', + 'Death Mountain Return Cave (West)', + 'Paradox Cave (Top)', + 'Fairy Ascension Cave (Top)', + 'Spiral Cave', + 'Desert Palace Entrance (East)'] + West_LW_DM_entrances = ['Old Man Cave (East)', + 'Old Man House (Bottom)', + 'Old Man House (Top)', + 'Death Mountain Return Cave (East)', + 'Spectacle Rock Cave Peak', + 'Spectacle Rock Cave', + 'Spectacle Rock Cave (Bottom)'] + East_LW_DM_entrances = ['Paradox Cave (Bottom)', + 'Paradox Cave (Middle)', + 'Hookshot Fairy', + 'Spiral Cave (Bottom)'] + Mirror_from_SDW_entrances = ['Two Brothers House (West)', + 'Cave 45'] + Castle_ledge_entrances = ['Hyrule Castle Entrance (West)', + 'Hyrule Castle Entrance (East)', + 'Inverted Ganons Tower'] + Desert_mirrorable_ledge_entrances = ['Desert Palace Entrance (West)', + 'Desert Palace Entrance (North)', + 'Desert Palace Entrance (South)', + 'Checkerboard Cave',] + Desert_ledge_entrances = ['Desert Palace Entrance (West)', + 'Desert Palace Entrance (North)', + 'Desert Palace Entrance (South)'] + + set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Inverted Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) + + #crossing peg bridge starting from the southern dark world + def cross_peg_bridge(state): + return state.has('Hammer', player) + + # Key for below abbreviations: + # P = pearl + # A = Aga1 + # H = hammer + # M = Mirror + # G = Glove + if bombshop_entrance.name in Normal_LW_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player)) + elif bombshop_entrance.name in LW_walkable_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player)) + elif bombshop_entrance.name in Northern_DW_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute or (state.can_lift_heavy_rocks(player) and (cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player))))) + elif bombshop_entrance.name == 'Bumper Cave (Bottom)': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute or (state.can_lift_heavy_rocks(player) and (cross_peg_bridge(state) or state.has_Mirror(player) and state.has('Beat Agahnim 1', player)))) + elif bombshop_entrance.name in Southern_DW_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or state.can_flute(player) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player))) + elif bombshop_entrance.name in Isolated_DW_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player)) + elif bombshop_entrance.name in Isolated_LW_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and state.has_Mirror(player)) + elif bombshop_entrance.name in West_LW_DM_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and state.has_Mirror(player)) + elif bombshop_entrance.name in East_LW_DM_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and state.has_Mirror(player)) + elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and state.has_Mirror(player)) + elif bombshop_entrance.name in Castle_ledge_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player)) + elif bombshop_entrance.name in Desert_ledge_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player)) + elif bombshop_entrance.name == 'Checkerboard Cave': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player)) + elif bombshop_entrance.name == 'Old Man Cave (West)': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and ((state.can_lift_rocks(player) and cross_peg_bridge(state)) or state.can_flute(player))) + elif bombshop_entrance.name == 'Graveyard Cave': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player)) + elif bombshop_entrance.name in Mirror_from_SDW_entrances: + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and (state.can_flute(player) or cross_peg_bridge(state))) + elif bombshop_entrance.name == 'Dark World Potion Shop': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) or state.has('Hammer', player) or state.can_lift_rocks(player)) + elif bombshop_entrance.name == 'Kings Grave': + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) + + def set_bunny_rules(world, player): # regions for the exits of multi-entrace caves/drops that bunny cannot pass @@ -910,3 +1475,76 @@ def set_bunny_rules(world, player): continue add_rule(location, get_rule_to_add(location.parent_region)) + +def set_inverted_bunny_rules(world, player): + + # regions for the exits of multi-entrace caves/drops that bunny cannot pass + # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. + bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)', + 'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)', 'Fairy Ascension Cave (Drop)', 'The Sky'] + + 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', 'Dark Blacksmith Ruins', 'Bombos Tablet', 'Ether Tablet', 'Purple Chest'] + + + def path_to_access_rule(path, entrance): + return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path) + + def options_to_access_rule(options): + return lambda state: any(rule(state) for rule in options) + + def get_rule_to_add(region): + if not region.is_dark_world: + return lambda state: state.has_Pearl(player) + # in this case we are mixed region. + # we collect possible options. + + # The base option is having the moon pearl + possible_options = [lambda state: state.has_Pearl(player)] + + # We will search entrances recursively until we find + # one that leads to an exclusively dark world region + # for each such entrance a new option is added that consist of: + # a) being able to reach it, and + # b) being able to access all entrances from there to `region` + seen = set([region]) + queue = collections.deque([(region, [])]) + while queue: + (current, path) = queue.popleft() + for entrance in current.entrances: + new_region = entrance.parent_region + if new_region in seen: + continue + new_path = path + [entrance.access_rule] + seen.add(new_region) + if not new_region.is_dark_world: + continue # we don't care about pure light world entrances + if new_region.is_light_world: + queue.append((new_region, new_path)) + else: + # we have reached pure dark world, so we have a new possible option + possible_options.append(path_to_access_rule(new_path, entrance)) + return options_to_access_rule(possible_options) + + # Add requirements for bunny-impassible caves if they occur in the light world + for region in [world.get_region(name, player) for name in bunny_impassable_caves]: + + if not region.is_light_world: + continue + rule = get_rule_to_add(region) + for exit in region.exits: + add_rule(exit, rule) + + paradox_shop = world.get_region('Light World Death Mountain Shop', player) + if paradox_shop.is_light_world: + add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop)) + + # Add requirements for all locations that are actually in the light world, except those available to the bunny + for location in world.get_locations(): + if location.player == player and location.parent_region.is_light_world: + + if location.name in bunny_accessible_locations: + continue + + add_rule(location, get_rule_to_add(location.parent_region)) + + diff --git a/Text.py b/Text.py index 22b2e044..b8baba83 100644 --- a/Text.py +++ b/Text.py @@ -25,7 +25,8 @@ Uncle_texts = [ 'Forward this message to 10 other people or this seed will be awful.', 'I hope you like your seeds bootless and fluteless.', '10\n9\n8\n7\n6\n5\n4\n3\n2\n1\nGo!', - 'I\'m off to visit cousin Fritzl.' + 'I\'m off to visit cousin Fritzl.', + 'Don\'t forget to check Antlion Cave.' ] * 2 + [ "We're out of\nWeetabix. To\nthe store!", "This seed is\nbootless\nuntil boots.",