From 1fd1f9ca5cf5b8532d1404654a6cf0a9142fda5b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 19 Sep 2022 01:19:22 -0500 Subject: [PATCH 01/59] Fixed issue with generating multiworlds --- Main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Main.py b/Main.py index dd8590d0..f6465a08 100644 --- a/Main.py +++ b/Main.py @@ -656,9 +656,10 @@ def copy_world_limited(world): ret.push_precollected(ItemFactory(item.name, item.player)) for edge in world.owedges: - copiededge = ret.check_for_owedge(edge.name, edge.player) - if copiededge is not None: - copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) + if edge.dest is not None: + copiededge = ret.check_for_owedge(edge.name, edge.player) + if copiededge is not None: + copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) for door in world.doors: entrance = ret.check_for_entrance(door.name, door.player) From 680a9f14fe1b15ec3dba96fc66a14ce751e7520b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 4 Oct 2022 21:32:23 -0500 Subject: [PATCH 02/59] Fixed bunny logic bug with rupee/bomb logic --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 58cfed79..2d61be2f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1104,7 +1104,7 @@ class CollectionState(object): def can_reach_non_bunny(regionname): region = self.world.get_region(regionname, player) - return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player)) + return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has_Pearl(player)) for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']: if can_reach_non_bunny(region): @@ -1185,7 +1185,7 @@ class CollectionState(object): def can_reach_non_bunny(regionname): region = self.world.get_region(regionname, player) - return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player)) + return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has_Pearl(player)) # bomb pickups for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []): From 1ef53e4317f0a696c7218db179727a1fb4d1d56e Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 5 Oct 2022 02:54:49 -0500 Subject: [PATCH 03/59] Moved Inverted Ice Portal to its correct logical region --- Regions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Regions.py b/Regions.py index a37b7da5..0a463e98 100644 --- a/Regions.py +++ b/Regions.py @@ -214,8 +214,8 @@ def create_regions(world, player): create_dw_region(player, 'Ice Lake Ledge (West)', None, ['Ice Lake Southwest Water Drop', 'South Shore Mirror Spot', 'Ice Lake WS']), create_dw_region(player, 'Ice Lake Ledge (East)', None, ['Ice Lake Southeast Water Drop', 'South Shore East Mirror Spot', 'Ice Lake ES']), create_dw_region(player, 'Ice Lake Water', None, ['Ice Lake Northeast Pier', 'Ice Lake Moat Bomb Jump', 'Lake Hylia Island Mirror Spot', 'Ice Lake NC', 'Ice Lake EC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Ice Lake Moat', None, ['Ice Lake Moat Water Entry', 'Ice Lake Northeast Pier Hop', 'Lake Hylia Water Mirror Spot', 'Lake Hylia Water D Mirror Spot']), - create_dw_region(player, 'Ice Palace Area', None, ['Ice Palace', 'Ice Palace Teleporter', 'Lake Hylia Central Island Mirror Spot']), + create_dw_region(player, 'Ice Lake Moat', None, ['Ice Palace Teleporter', 'Ice Lake Moat Water Entry', 'Ice Lake Northeast Pier Hop', 'Lake Hylia Water Mirror Spot', 'Lake Hylia Water D Mirror Spot']), + create_dw_region(player, 'Ice Palace Area', None, ['Ice Palace', 'Lake Hylia Central Island Mirror Spot']), create_dw_region(player, 'Shopping Mall Area', None, ['Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Ice Cave Mirror Spot', 'Shopping Mall SW', 'Shopping Mall SE']), create_dw_region(player, 'Swamp Nook Area', None, ['Desert Pass Ledge Mirror Spot', 'Desert Pass Mirror Spot', 'Swamp Nook EC', 'Swamp Nook ES']), create_dw_region(player, 'Swamp Area', None, ['Swamp Palace', 'Dam Mirror Spot', 'Swamp WC', 'Swamp WS', 'Swamp NC', 'Swamp EC']), From bb2e7d198da49828c4ec875654bac28613b81a2c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 5 Oct 2022 03:56:33 -0500 Subject: [PATCH 04/59] Moved Cave 45 Ledge Drop connection to ow_connections --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index f881aa81..442232a3 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1237,7 +1237,6 @@ mandatory_connections = [# Intra-tile OW Connections ('Bombos Tablet Drop', 'Desert Area'), ('Flute Boy Bush (North)', 'Flute Boy Approach Area'), #pearl ('Flute Boy Bush (South)', 'Flute Boy Bush Entry'), #pearl - ('Cave 45 Ledge Drop', 'Flute Boy Approach Area'), ('C Whirlpool Water Entry', 'C Whirlpool Water'), #flippers ('C Whirlpool Landing', 'C Whirlpool Area'), ('C Whirlpool Rock (Bottom)', 'C Whirlpool Outer Area'), #glove @@ -1681,6 +1680,7 @@ ow_connections = { ('Misery Mire Teleporter', 'Desert Palace Teleporter Ledge') ]), 0x32: ([ + ('Cave 45 Ledge Drop', 'Flute Boy Approach Area'), ('Flute Boy Entry Mirror Spot', 'Flute Boy Bush Entry'), ('Cave 45 Mirror Spot', 'Cave 45 Ledge') ], [ From af8349fbc3143e284624dcddf324145d855d620f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 5 Oct 2022 22:39:54 -0500 Subject: [PATCH 05/59] Fixed issue with OWG access to Dark Central Cliff portal --- Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index d18c9ae5..38a3af46 100644 --- a/Rules.py +++ b/Rules.py @@ -1261,7 +1261,7 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('Dark C Whirlpool Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark C Whirlpool Outer Mirror Spot', player), lambda state: state.has_Mirror(player)) 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)) - set_rule(world.get_entrance('South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Dark South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) if not world.is_tile_swapped(0x34, player): set_rule(world.get_entrance('Statues Mirror Spot', player), lambda state: state.has_Mirror(player)) From 0c53fc8519486feac26e890a8b0d2e5aad7a4f75 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Sun, 9 Oct 2022 01:23:47 +0200 Subject: [PATCH 06/59] Add Free Terrain mode and terrain rules --- BaseClasses.py | 4 + CLI.py | 5 +- Main.py | 3 + Mystery.py | 1 + OWEdges.py | 318 ++++++++++++++++++ OverworldShuffle.py | 12 +- README.md | 6 +- Rom.py | 2 +- Rules.py | 20 +- asm/owrando.asm | 310 ++++++++++++----- data/base2current.bps | Bin 104405 -> 104763 bytes mystery_example.yml | 3 + resources/app/cli/args.json | 4 + resources/app/cli/lang/en.json | 4 +- resources/app/gui/lang/en.json | 2 + .../app/gui/randomize/overworld/widgets.json | 8 +- source/classes/constants.py | 1 + source/gui/randomize/overworld.py | 6 +- 18 files changed, 603 insertions(+), 106 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 58cfed79..81230e99 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -25,6 +25,7 @@ class World(object): self.players = players self.teams = 1 self.owShuffle = owShuffle.copy() + self.owTerrain = {} self.owCrossed = owCrossed.copy() self.owKeepSimilar = {} self.owMixed = owMixed.copy() @@ -2912,6 +2913,7 @@ class Spoiler(object): 'weapons': self.world.swords, 'goal': self.world.goal, 'ow_shuffle': self.world.owShuffle, + 'ow_terrain': self.world.owTerrain, 'ow_crossed': self.world.owCrossed, 'ow_keepsimilar': self.world.owKeepSimilar, 'ow_mixed': self.world.owMixed, @@ -3124,6 +3126,8 @@ class Spoiler(object): outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player])) outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player])) outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) + if self.metadata['ow_shuffle'][player] != 'vanilla': + outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player])) outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none': outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player])) diff --git a/CLI.py b/CLI.py index 4e7ad021..bfd09183 100644 --- a/CLI.py +++ b/CLI.py @@ -97,8 +97,8 @@ def parse_cli(argv, no_defaults=False): for player in range(1, multiargs.multi + 1): playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', - 'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', + 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', @@ -154,6 +154,7 @@ def parse_settings(): "openpyramid": "auto", "shuffleganon": True, "ow_shuffle": "vanilla", + "ow_terrain": False, "ow_crossed": "none", "ow_keepsimilar": False, "ow_mixed": False, diff --git a/Main.py b/Main.py index f6465a08..f1762147 100644 --- a/Main.py +++ b/Main.py @@ -88,6 +88,7 @@ def main(args, seed=None, fish=None): world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} world.crystals_ganon_orig = args.crystals_ganon.copy() world.crystals_gt_orig = args.crystals_gt.copy() + world.owTerrain = args.ow_terrain.copy() world.owKeepSimilar = args.ow_keepsimilar.copy() world.owWhirlpoolShuffle = args.ow_whirlpool.copy() world.owFluteShuffle = args.ow_fluteshuffle.copy() @@ -433,6 +434,7 @@ def copy_world(world): ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() + ret.owTerrain = world.owTerrain.copy() ret.owKeepSimilar = world.owKeepSimilar.copy() ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy() ret.owFluteShuffle = world.owFluteShuffle.copy() @@ -593,6 +595,7 @@ def copy_world_limited(world): ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() + ret.owTerrain = world.owTerrain.copy() ret.owKeepSimilar = world.owKeepSimilar.copy() ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy() ret.owFluteShuffle = world.owFluteShuffle.copy() diff --git a/Mystery.py b/Mystery.py index 9bd1235a..cf759ccc 100644 --- a/Mystery.py +++ b/Mystery.py @@ -168,6 +168,7 @@ def roll_settings(weights): overworld_shuffle = get_choice('overworld_shuffle') ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' + ret.ow_terrain = get_choice('overworld_terrain') == 'on' valid_options = {'none', 'polar', 'grouped', 'limited', 'chaos'} ret.ow_crossed = get_choice('overworld_crossed') ret.ow_crossed = ret.ow_crossed if ret.ow_crossed in valid_options else 'none' diff --git a/OWEdges.py b/OWEdges.py index 8c980601..f6145831 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -697,6 +697,324 @@ OWEdgeGroups = { ) } +OWEdgeGroupsTerrain = { + #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup) + (St, LW, Vt, None, PL, 1): ( + [ + ['Hyrule Castle SW'], + ['Hyrule Castle SE'] + ], + [ + ['Central Bonk Rocks NW'], + ['Links House NE'] + ] + ), + (St, LW, Hz, None, PL, 3): ( + [ + ['Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES'] + ], + [ + ['Links House WN', 'Links House WC', 'Links House WS'] + ] + ), + (Op, LW, Hz, None, PL, 1): ( + [ + ['Lost Woods EN'], + ['East Death Mountain EN'], + ['Sanctuary EC'], + ['Graveyard EC'], + ['Kakariko ES'], + ['Hyrule Castle ES'], + ['Maze Race ES'], + ['Kakariko Suburb ES'], + ['Links House ES'], + ['Flute Boy Approach EC'], + ['Dam EC'], + ['South Pass ES'], + ['West Death Mountain EN'], + ['West Death Mountain ES'] + ], + [ + ['Lumberjack WN'], + ['Death Mountain TR Pegs WN'], + ['Graveyard WC'], + ['River Bend WC'], + ['Blacksmith WS'], + ['Sand Dunes WN'], + ['Kakariko Suburb WS'], + ['Flute Boy WS'], + ['Stone Bridge WS'], + ['C Whirlpool WC'], + ['South Pass WC'], + ['Lake Hylia WS'], + ['East Death Mountain WN'], + ['East Death Mountain WS'] + ] + ), + (Op, LW, Hz, None, NP, 1): ( + [ + ['Forgotten Forest ES'], + ['Hobo EC'] + ], + [ + ['Hyrule Castle WN'], + ['Stone Bridge WC'] + ] + ), + (Op, LW, Vt, None, PL, 1): ( + [ + ['Lumberjack SW'], + ['Mountain Entry SE'], + ['Lost Woods SE'], + ['Zora Waterfall SE'], + ['Kakariko Fortune SC'], + ['Wooden Bridge SW'], + ['Kakariko SE'], + ['Sand Dunes SC'], + ['Eastern Palace SW'], + ['Eastern Palace SE'], + ['Central Bonk Rocks SW'], + ['Links House SC'], + ['Stone Bridge SC'], + ['C Whirlpool SC'], + ['Statues SC'] + ], + [ + ['Mountain Entry NW'], + ['Kakariko Pond NE'], + ['Kakariko Fortune NE'], + ['Zora Approach NE'], + ['Kakariko NE'], + ['Sand Dunes NW'], + ['Kakariko Suburb NE'], + ['Stone Bridge NC'], + ['Tree Line NW'], + ['Eastern Nook NE'], + ['C Whirlpool NW'], + ['Statues NC'], + ['Lake Hylia NW'], + ['Dam NC'], + ['South Pass NC'] + ] + ), + (Op, LW, Vt, None, NP, 1): ( + [ + ['Master Sword Meadow SC'], + ['Zoras Domain SW'] + ], + [ + ['Lost Woods NW'], + ['Zora Waterfall NE'] + ] + ), + (Op, LW, Hz, None, PL, 2): ( + [ + ['Kakariko Fortune EN', 'Kakariko Fortune ES'], + ['Kakariko Pond EN', 'Kakariko Pond ES'], + ['Desert Pass EC', 'Desert Pass ES'], + ['Potion Shop EN', 'Potion Shop EC'], + ['Lake Hylia EC', 'Lake Hylia ES'], + ['Stone Bridge EN', 'Stone Bridge EC'] + ], + [ + ['Kakariko Pond WN', 'Kakariko Pond WS'], + ['Sanctuary WN', 'Sanctuary WS'], + ['Dam WC', 'Dam WS'], + ['Zora Approach WN', 'Zora Approach WC'], + ['Octoballoon WC', 'Octoballoon WS'], + ['Tree Line WN', 'Tree Line WC'] + ] + ), + (Op, LW, Hz, None, NP, 2): ( + [ + ['Desert EC', 'Desert ES'] + ], + [ + ['Desert Pass WC', 'Desert Pass WS'] + ] + ), + (Op, LW, Vt, None, PL, 2): ( + [ + ['Lost Woods SW', 'Lost Woods SC'], + ['Lost Woods Pass SW', 'Lost Woods Pass SE'], + ['Kakariko Pond SW', 'Kakariko Pond SE'], + ['Flute Boy SW', 'Flute Boy SC'], + ['Tree Line SC', 'Tree Line SE'], + ['Ice Cave SW', 'Ice Cave SE'] + ], + [ + ['Lost Woods Pass NW', 'Lost Woods Pass NE'], + ['Kakariko NW', 'Kakariko NC'], + ['Forgotten Forest NW', 'Forgotten Forest NE'], + ['Flute Boy Approach NW', 'Flute Boy Approach NC'], + ['Lake Hylia NC', 'Lake Hylia NE'], + ['Octoballoon NW', 'Octoballoon NE'] + ] + ), + (Op, LW, Hz, None, PL, 3): ( + [ + ['River Bend EN', 'River Bend EC', 'River Bend ES'], + ['C Whirlpool EN', 'C Whirlpool EC', 'C Whirlpool ES'] + ], + [ + ['Potion Shop WN', 'Potion Shop WC', 'Potion Shop WS'], + ['Statues WN', 'Statues WC', 'Statues WS'] + ] + ), + (Op, LW, Vt, None, PL, 3): ( + [ + ['River Bend SW', 'River Bend SC', 'River Bend SE'] + ], + [ + ['Wooden Bridge NW', 'Wooden Bridge NC', 'Wooden Bridge NE'] + ] + ), + (Op, DW, Hz, None, PL, 1): ( + [ + ['Skull Woods EN'], + ['East Dark Death Mountain EN'], + ['Dark Chapel EC'], + ['Dark Graveyard EC'], + ['Village of Outcasts ES'], + ['Pyramid ES'], + ['Frog ES'], + ['Big Bomb Shop ES'], + ['Stumpy Approach EC'], + ['Swamp EC'], + ['Dark South Pass ES'], + ['West Dark Death Mountain EN'], + ['West Dark Death Mountain ES'] + ], + [ + ['Dark Lumberjack WN'], + ['Turtle Rock WN'], + ['Dark Graveyard WC'], + ['Qirn Jump WC'], + ['Hammer Pegs WS'], + ['Dark Dunes WN'], + ['Stumpy WS'], + ['Hammer Bridge WS'], + ['Dark C Whirlpool WC'], + ['Dark South Pass WC'], + ['Ice Lake WS'], + ['East Dark Death Mountain WN'], + ['East Dark Death Mountain WS'] + ] + ), + (Op, DW, Vt, None, PL, 1): ( + [ + ['Dark Lumberjack SW'], + ['Bumper Cave SE'], + ['Skull Woods SE'], + ['Catfish SE'], + ['Dark Fortune SC'], + ['Broken Bridge SW'], + ['Village of Outcasts SE'], + ['Pyramid SW'], + ['Pyramid SE'], + ['Dark Dunes SC'], + ['Palace of Darkness SW'], + ['Palace of Darkness SE'], + ['Dark Bonk Rocks SW'], + ['Big Bomb Shop SC'], + ['Hammer Bridge SC'], + ['Dark C Whirlpool SC'], + ['Hype Cave SC'] + ], + [ + ['Bumper Cave NW'], + ['Outcast Pond NE'], + ['Dark Fortune NE'], + ['Catfish Approach NE'], + ['Village of Outcasts NE'], + ['Dark Dunes NW'], + ['Frog NE'], + ['Dark Bonk Rocks NW'], + ['Big Bomb Shop NE'], + ['Hammer Bridge NC'], + ['Dark Tree Line NW'], + ['Palace of Darkness Nook NE'], + ['Dark C Whirlpool NW'], + ['Hype Cave NC'], + ['Ice Lake NW'], + ['Swamp NC'], + ['Dark South Pass NC'] + ] + ), + (Op, DW, Hz, None, NP, 1): ( + [ ], + [ ] + ), + (Op, DW, Hz, None, PL, 2): ( + [ + ['Dark Fortune EN', 'Dark Fortune ES'], + ['Outcast Pond EN', 'Outcast Pond ES'], + ['Swamp Nook EC', 'Swamp Nook ES'], + ['Dark Witch EN', 'Dark Witch EC'], + ['Ice Lake EC', 'Ice Lake ES'], + ['Hammer Bridge EN', 'Hammer Bridge EC'] + ], + [ + ['Outcast Pond WN', 'Outcast Pond WS'], + ['Dark Chapel WN', 'Dark Chapel WS'], + ['Swamp WC', 'Swamp WS'], + ['Catfish Approach WN', 'Catfish Approach WC'], + ['Bomber Corner WC', 'Bomber Corner WS'], + ['Dark Tree Line WN', 'Dark Tree Line WC'] + ] + ), + (Op, DW, Vt, None, NP, 1): ( + [ ], + [ ] + ), + (Op, DW, Hz, None, NP, 2): ( + [ + ['Dig Game EC', 'Dig Game ES'] + ], + [ + ['Frog WC', 'Frog WS'] + ] + ), + (Op, DW, Vt, None, PL, 2): ( + [ + ['Skull Woods SW', 'Skull Woods SC'], + ['Skull Woods Pass SW', 'Skull Woods Pass SE'], + ['Outcast Pond SW', 'Outcast Pond SE'], + ['Stumpy SW', 'Stumpy SC'], + ['Dark Tree Line SC', 'Dark Tree Line SE'], + ['Shopping Mall SW', 'Shopping Mall SE'] + ], + [ + ['Skull Woods Pass NW', 'Skull Woods Pass NE'], + ['Village of Outcasts NW', 'Village of Outcasts NC'], + ['Shield Shop NW', 'Shield Shop NE'], + ['Stumpy Approach NW', 'Stumpy Approach NC'], + ['Ice Lake NC', 'Ice Lake NE'], + ['Bomber Corner NW', 'Bomber Corner NE'] + ] + ), + (Op, DW, Hz, None, PL, 3): ( + [ + ['Dark Bonk Rocks EN', 'Dark Bonk Rocks EC', 'Dark Bonk Rocks ES'], + ['Qirn Jump EN', 'Qirn Jump EC', 'Qirn Jump ES'], + ['Dark C Whirlpool EN', 'Dark C Whirlpool EC', 'Dark C Whirlpool ES'] + ], + [ + ['Big Bomb Shop WN', 'Big Bomb Shop WC', 'Big Bomb Shop WS'], + ['Dark Witch WN', 'Dark Witch WC', 'Dark Witch WS'], + ['Hype Cave WN', 'Hype Cave WC', 'Hype Cave WS'] + ] + ), + (Op, DW, Vt, None, PL, 3): ( + [ + ['Qirn Jump SW', 'Qirn Jump SC', 'Qirn Jump SE'] + ], + [ + ['Broken Bridge NW', 'Broken Bridge NC', 'Broken Bridge NE'] + ] + ) +} + OWTileRegions = bidict({ 'Lost Woods West Area': 0x00, 'Lost Woods East Area': 0x00, diff --git a/OverworldShuffle.py b/OverworldShuffle.py index f881aa81..4a562d3c 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -3,7 +3,7 @@ from collections import OrderedDict, defaultdict from DungeonGenerator import GenerationException from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_dark_world_regions, mark_light_world_regions -from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel +from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel from Utils import bidict version_number = '0.2.10.1' @@ -106,7 +106,7 @@ def link_overworld(world, player): return new_groups tile_groups = define_tile_groups(world, player, False) - trimmed_groups = copy.deepcopy(OWEdgeGroups) + trimmed_groups = copy.deepcopy(OWEdgeGroupsTerrain if world.owTerrain[player] else OWEdgeGroups) swapped_edges = list() # restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship @@ -639,9 +639,9 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): if not world.owKeepSimilar[player]: parity[1] += 2*parity[2] parity[2] = 0 - # if crossed terrain: - # parity[1] += parity[3] - # parity[3] = 0 + if world.owTerrain[player]: + parity[1] += parity[3] + parity[3] = 0 parity[4] %= 2 # actual parity if (world.owCrossed[player] == 'none' or do_grouped) and parity[:5] != [0, 0, 0, 0, 0]: attempts -= 1 @@ -775,6 +775,8 @@ def reorganize_groups(world, groups, player): new_group = list(group) if world.mode[player] != "standard": new_group[0] = None + if world.owTerrain[player]: + new_group[3] = None if world.owShuffle[player] != 'parallel': new_group[4] = None if not world.owKeepSimilar[player]: diff --git a/README.md b/README.md index be396995..59d3cd5f 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ OW Transitions are shuffled, but both worlds will have a matching layout. OW Transitions are shuffled within each world separately. +## Free Terrain (--ow_terrain) + +With OW Layout Shuffle, this allows land and water edges to be connected. + ## Crossed Options (--ow_crossed) This allows OW connections to be shuffled cross-world. @@ -102,7 +106,7 @@ Same as Limited, except that there is no limit to the number of cross-world conn ## Keep Similar Edges Together (--ow_keepsimilar) -This keeps similar edge transitions together. ie. The 2 west edges of Potion Shop will be paired to another set of two similar edges +This keeps similar edge transitions together. ie. The 2 west land edges of Potion Shop will be paired to another set of two similar edges, unless Free Terrain is also enabled, in which case these 2 edges together with the west water edge form a group of 3 similar edges. Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW. diff --git a/Rom.py b/Rom.py index a552ef90..03e14ef2 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '0aa072c020b0c1167c3e618b571efbbe' +RANDOMIZERBASEHASH = '4657087846e24d65cd69ff61241601fe' class JsonRom(object): diff --git a/Rules.py b/Rules.py index d18c9ae5..5d0a45b2 100644 --- a/Rules.py +++ b/Rules.py @@ -3,10 +3,11 @@ import logging from collections import deque import OverworldGlitchRules -from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType, LocationType +from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType, LocationType, Terrain from BaseClasses import PotFlags from Dungeons import dungeon_table from RoomData import DoorKind +from OWEdges import OWExitTypes from OverworldGlitchRules import overworld_glitches_rules @@ -25,6 +26,8 @@ def set_rules(world, player): ow_bunny_rules(world, player) + ow_terrain_rules(world, player) + if world.mode[player] == 'standard': if not world.is_copied_world: standard_rules(world, player) @@ -1474,6 +1477,21 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), player) +def ow_terrain_rules(world, player): + for edge in world.owedges: + if edge.player == player and edge.dest and edge.dest.terrain == Terrain.Water: + ent = world.get_entrance(edge.name, player) + if edge.terrain == Terrain.Land: + set_rule(ent, lambda state: state.has('Flippers', player)) + if ent.parent_region.is_light_world == (world.mode[player] != 'inverted') and ent.connected_region.is_dark_world == (world.mode[player] != 'inverted'): + add_rule(ent, lambda state: state.has_Pearl(player)) + + for whirlpool_name in OWExitTypes['Whirlpool']: + ent = world.get_entrance(whirlpool_name, player) + if ent.parent_region.is_light_world == (world.mode[player] != 'inverted') and ent.connected_region.is_dark_world == (world.mode[player] != 'inverted'): + add_rule(ent, lambda state: state.has_Pearl(player)) + + def no_glitches_rules(world, player): # todo: move some dungeon rules to no glictes logic - see these for examples # add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) diff --git a/asm/owrando.asm b/asm/owrando.asm index c709fc43..812cec20 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -1,3 +1,10 @@ +; Free RAM notes +; $06F8-$06F9: Used to store edge table addresses +; $06FA-$06FB: Used to store target edge IDs +; $06FC-$06FD: Used for custom walk destination after transitions +; $0703: Used to flag forced transitions +; $0704-$0705: Used to store terrain type at the start of a transition + org $aa8000 ;150000 db $4f, $52 ;OR OWMode: @@ -38,6 +45,9 @@ Overworld_LoadSpecialOverworld_RoomId: org $04E8B4 Overworld_LoadSpecialOverworld: +org $07982A +Link_ResetSwimmingState: + ; mirror hooks org $02FBAB @@ -54,6 +64,8 @@ JSL OWMirrorSpriteOnMap : BRA + : NOP #6 : + ; whirlpool shuffle cross world change org $02b3bd jsl OWWhirlpoolUpdate ;JSL $02EA6C +org $02B44E +jsl OWWhirlpoolEnd ; STZ.b $11 : STZ.b $B0 ; flute menu cancel org $0ab7af ;LDA $F2 : ORA $F0 : AND #$C0 @@ -172,6 +184,8 @@ OWCameraRangeIndex: db 2, 2, 0, 0 ; For OWCameraRange OWCameraRange: dw $011E, $0100 ; Length of the range the camera can move on small screens +OWAutoWalk: +db $04, $08, $01, $02 DivideByTwoPreserveSign: { @@ -201,13 +215,21 @@ OWMapWorldCheck16: OWWhirlpoolUpdate: { jsl $02ea6c ; what we wrote over - lda.l OWFlags : and #$01 : bne + - lda.l OWMode+1 : and #$02 : beq .return - + ldx $8a : jsr OWWorldUpdate - .return + ldx $8a : ldy #$03 : jsr OWWorldTerrainUpdate rtl } +OWWhirlpoolEnd: +{ + STZ.b $B0 ; what we wrote over + LDA.w $0703 : BEQ .normal + LDA.b #$3C : STA.w $012E ; play error sound before forced transition + RTL + .normal + STZ.b $11 ; end whirlpool transition + RTL +} + OWMirrorSpriteOnMap: { lda.w $1ac0,x : bit.b #$f0 : beq .continue @@ -525,39 +547,42 @@ OWDetectSpecialTransition: { STZ.w $06FC LDA.l OWMode : BEQ .normal - LDA.l OWSpecialDestIndex,X : BIT.w #$0080 : BNE .special - STA.w $06FA - LDA.l OWEdgeDataOffset,X : STA.w $06F8 - PLA : SEP #$30 : PLA ; delete 3 bytes from stack - JSL Link_CheckForEdgeScreenTransition : BCS .return ; Link_CheckForEdgeScreenTransition - LDA.l Overworld_CheckForSpecialOverworldTrigger_Direction,X : STA.b $00 : CMP.b #$08 : BNE .hobo - LSR : STA.b $20 : STZ.b $E8 ; move Link and camera to edge - LDA.b #$06 : STA.b $02 - STZ.w $0418 - BRA .continue - .hobo - STA.b $02 : STA.w $0418 - ASL : STA.b $22 : STZ.b $E2 ; move Link and camera to edge - LDA.b #$0A : STA.b $23 : STA.b $E3 - .continue - STZ.b $03 - ; copied from DeleteCertainAncillaeStopDashing at $028A0E - JSL Ancilla_TerminateSelectInteractives - LDA.w $0372 : BEQ .not_dashing - STZ.b $4D : STZ.b $46 - LDA.b #$FF : STA.b $29 : STA.b $C7 - STZ.b $3D : STZ.b $5E : STZ.w $032B : STZ.w $0372 : STZ.b $5D - .not_dashing - PLA : REP #$31 : PLA ; delete 3 bytes from stack - LDX.b $02 - LDA.b $84 - JML OverworldHandleTransitions_SpecialTrigger+6 - .special + TXA : AND.w #$0002 : LSR + STA.w $0704 + LDA.l OWSpecialDestIndex,X : BIT.w #$0080 : BEQ .switch_to_edge AND.w #$0003 : TAY : ASL : TAX .normal JSR OWLoadSpecialArea .return RTL + + .switch_to_edge + STA.w $06FA + LDA.l OWEdgeDataOffset,X : STA.w $06F8 + PLA : SEP #$30 : PLA ; delete 3 bytes from stack + JSL Link_CheckForEdgeScreenTransition : BCS .return ; Link_CheckForEdgeScreenTransition + LDA.l Overworld_CheckForSpecialOverworldTrigger_Direction,X : STA.b $00 : CMP.b #$08 : BNE .hobo + LSR : STA.b $20 : STZ.b $E8 ; move Link and camera to edge + LDA.b #$06 : STA.b $02 + STZ.w $0418 + BRA .continue + .hobo + STA.b $02 : STA.w $0418 + ASL : STA.b $22 : STZ.b $E2 ; move Link and camera to edge + LDA.b #$0A : STA.b $23 : STA.b $E3 + .continue + STZ.b $03 + ; copied from DeleteCertainAncillaeStopDashing at $028A0E + JSL Ancilla_TerminateSelectInteractives + LDA.w $0372 : BEQ .not_dashing + STZ.b $4D : STZ.b $46 + LDA.b #$FF : STA.b $29 : STA.b $C7 + STZ.b $3D : STZ.b $5E : STZ.w $032B : STZ.w $0372 : STZ.b $5D + .not_dashing + PLA : REP #$31 : PLA ; delete 3 bytes from stack + LDX.b $02 + LDA.b $84 + JML OverworldHandleTransitions_SpecialTrigger+6 } OWEdgeTransition: { @@ -577,9 +602,16 @@ OWEdgeTransition: } OWSpecialExit: { - LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : BEQ .return - JSR OWWorldUpdate - .return + PHY + LDY.b #$00 + LDA.w $0418 : LSR : BNE + + LDY.w $0704 : BRA ++ + + + LDA.w $0704 : BNE ++ + LDY.b #$02 + ++ + JSR OWWorldTerrainUpdate + PLY LDA.l $7EFD40,X ; what we wrote over RTL } @@ -611,7 +643,7 @@ OWShuffle: .nextTransition pha - jsr OWSearchTransition : bcs .newDestination + jsr OWSearchTransition_entry : bcs .newDestination txa : !add #$0010 : tax pla : dec : bne .nextTransition : bra .noTransition @@ -627,6 +659,10 @@ OWShuffle: } OWSearchTransition: { + .exitloop ; moved here because of branch distance + clc : rts + + .entry ;A-16 XY-16 lda $418 : bne + ;north lda.l OWNorthEdges,x : dec @@ -634,6 +670,7 @@ OWSearchTransition: lda.l OWNorthEdges+2,x : cmp $22 : !blt .exitloop ;MATCH lda.l OWNorthEdges+14,x : tay ;y = record id of dest + lda.l OWNorthEdges+12,x ;a = current terrain ldx.w #OWSouthEdges ;x = address of table bra .matchfound + dec : bne + ;south @@ -642,6 +679,7 @@ OWSearchTransition: lda.l OWSouthEdges+2,x : cmp $22 : !blt .exitloop ;MATCH lda.l OWSouthEdges+14,x : tay ;y = record id of dest + lda.l OWSouthEdges+12,x ;a = current terrain ldx.w #OWNorthEdges ;x = address of table bra .matchfound + dec : bne + ; west @@ -650,6 +688,7 @@ OWSearchTransition: lda.l OWWestEdges+2,x : cmp $20 : !blt .exitloop ;MATCH lda.l OWWestEdges+14,x : tay ;y = record id of dest + lda.l OWWestEdges+12,x ;a = current terrain ldx.w #OWEastEdges ;x = address of table bra .matchfound + lda.l OWEastEdges,x : dec ;east @@ -657,15 +696,13 @@ OWSearchTransition: lda.l OWEastEdges+2,x : cmp $20 : !blt .exitloop ;MATCH lda.l OWEastEdges+14,x : tay ;y = record id of dest + lda.l OWEastEdges+12,x ;a = current terrain ldx.w #OWWestEdges ;x = address of table .matchfound - stx $06f8 : sty $06fa : sec : rts + stx $06f8 : sty $06fa : sta $0704 : sec : rts plx : pla : pea $0001 : phx sec : rts - - .exitloop - clc : rts } OWNewDestination: { @@ -698,6 +735,16 @@ OWNewDestination: LDA.w $000F,X : AND.w #$00FF : STA.w $06FC ; position to walk to after transition (if non-zero) + LDY.w #$0000 + LDA.w $000C,X : AND.w #$0001 : BEQ + ; check if going to water transition + LDA.w $0704 : AND.w #$0001 : BNE ++ ; check if coming from water transition + INY : BRA ++ + + + LDA.w $0704 : BEQ ++ ; check if coming from water transition + LDY.w #$0002 + ++ + STY.b $08 + pla : pla : sep #$10 : ldy $418 ldx OWCoordIndex,y : lda $20,x : and #$fe00 : pha lda $20,x : and #$01ff : pha ;s1 = relative cur, s3 = ow cur @@ -743,27 +790,37 @@ OWNewDestination: sep #$30 : lda $04 : and #$3f : !add OWOppSlotOffset,y : asl : sta $700 - ; crossed OW shuffle - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return - ldx $05 : jsr OWWorldUpdate + ; crossed OW shuffle and terrain + ldx $05 : ldy $08 : jsr OWWorldTerrainUpdate - .return lda $05 : sta $8a rep #$30 : rts } OWLoadSpecialArea: { LDA.l Overworld_LoadSpecialOverworld_RoomId,X : STA.b $A0 - JSL Overworld_LoadSpecialOverworld - LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : BEQ .return - TYX : LDA.l OWSpecialDestSlot,X : TAX - JSR OWWorldUpdate + JSL Overworld_LoadSpecialOverworld ; sets M and X flags + TYX + LDY.b #$00 + CPX.b #$01 : BNE + ; check if going to water transition + LDA.w $0704 : BNE ++ ; check if coming from water transition + INY : BRA ++ + + + LDA.w $0704 : BEQ ++ ; check if coming from water transition + LDY.b #$02 + ++ + LDA.l OWSpecialDestSlot,X : TAX + JSR OWWorldTerrainUpdate .return RTS } -OWWorldUpdate: ; x = owid of destination screen +OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, 2 for water to land, 3 for whirlpools and 0 else { - lda.l OWTileWorldAssoc,x : cmp.l CurrentWorld : beq .return + LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : BEQ .not_crossed + LDA.l OWTileWorldAssoc,x : CMP.l CurrentWorld : BNE .crossed + .not_crossed + JMP .normal + .crossed sta.l CurrentWorld ; change world ; moving mirror portal off screen when in DW @@ -781,19 +838,87 @@ OWWorldUpdate: ; x = owid of destination screen lda CurrentWorld : and.b #$40 : bra + .inverted lda CurrentWorld : and.b #$40 : eor #$40 + cmp #$40 : bne .nobunny - ; turn into bunny - lda $5d : cmp #$04 : beq + ; if swimming, continue - lda #$17 : sta $5d - + lda #$01 : sta $02e0 : sta $56 - bra .return + + LDA.w $0703 : BEQ + ; check if forced transition + CPY.b #$03 : BEQ .end_forced_whirlpool + LDA.b #$17 : STA.b $5D + LDA.b #$01 : STA.w $02E0 : STA.b $56 + LDA.w $0703 : BRA .end_forced_edge + + + CPY.b #$01 : BEQ .auto ; check if going from land to water + CPY.b #$02 : BEQ .to_bunny_reset_swim ; bunny state if swimming to land + LDA.b $5D : CMP.b #$04 : BNE .to_bunny ; check if swimming + .auto + PHX + LDA.b #$01 + LDX.b $5D : CPX.b #$04 : BNE + + INC + + + STA.w $0703 + CPY.b #$03 : BEQ .whirlpool + LDA.b #$01 : STA.w $0345 + LDX.w $0418 + LDA.l OWAutoWalk,X : STA.b $49 + STZ.b $5D + PLX + BRA .to_pseudo_bunny + .whirlpool + PLX : RTS + .to_bunny_reset_swim + JSL Link_ResetSwimmingState + STZ.w $0345 + .to_bunny + LDA.b #$17 : STA.b $5D + .to_pseudo_bunny + LDA.b #$01 : STA.w $02E0 : STA.b $56 + RTS .nobunny lda $5d : cmp #$17 : bne + ; retain current state unless bunny stz $5d + stz $02e0 : stz $56 + .normal + LDA.w $0703 : BEQ .not_forced ; check if forced transition + CPY.b #$03 : BEQ .end_forced_whirlpool + .end_forced_edge + STZ.b $49 : STZ.w $0345 + .end_forced_whirlpool + STZ.w $0703 + CMP.b #$02 : BNE + + DEC : STA.w $0345 + LDA.b #$04 : BRA .set_state + + + CMP.b #$03 : BNE ++ + LDA.b #$17 + .set_state + STA.b $5D + ++ + RTS + .not_forced + CPY.b #$02 : BNE + ; check if going from water to land + JSL Link_ResetSwimmingState + STZ.w $0345 + LDA.b $5D : CMP.b #$04 : BNE + ; check if swimming + STZ.b $5D + + + CPY.b #$01 : BNE .return ; check if going from land to water + LDA.b #$01 : STA.w $0345 + LDA.b $5D : CMP.b #$04 : BEQ .return ; check if swimming + LDA.l FlippersEquipment : BEQ .no_flippers ; check if flippers obtained + LDA.b $5D : CMP.b #$17 : BEQ .no_flippers ; check if bunny + LDA.b #$04 : STA.b $5D : RTS + .no_flippers + PHX + INC : STA.w $0703 + LDX.w $0418 + LDA.l OWAutoWalk,X : STA.b $49 + PLX + LDA.b $5D : CMP.b #$17 : BNE .return ; check if bunny + LDA.b #$03 : STA.w $0703 + STZ.b $5D .return - rts + RTS } OWAdjustExitPosition: { @@ -807,6 +932,9 @@ OWAdjustExitPosition: LDA.b #$3B : STA.w $061E INC.b $23 : INC.w $061D : INC.w $061F .normal + LDA.w $0703 : BEQ + + LDA.b #$3C : STA.w $012E ; play error sound before forced transition + + INC.b $11 : STZ.b $B0 ; what we wrote over RTL } @@ -978,7 +1106,7 @@ db $80, $80, $81 org $aaa800 ;PC 152800 OWNorthEdges: -; Min Max Width Mid OW Slot/OWID VRAM *FREE* Dest Index +; Min Max Width Mid OW Slot/OWID VRAM Terrain Dest Index dw $00a0, $00a0, $0000, $00a0, $0000, $0000, $0000, $B040 ;Lost Woods (exit only) dw $0458, $0540, $00e8, $04cc, $0a0a, $0000, $0000, $0000 dw $0f38, $0f60, $0028, $0f4c, $0f0f, $0000, $0000, $2041 ;Waterfall (exit only) @@ -993,7 +1121,7 @@ dw $02e8, $0348, $0060, $0318, $1819, $0000, $0000, $0008 dw $0478, $04d0, $0058, $04a4, $1a1a, $0000, $0000, $0009 dw $0510, $0538, $0028, $0524, $1a1a, $0000, $0000, $000a dw $0a48, $0af0, $00a8, $0a9c, $1d1d, $0000, $0000, $000b -dw $0b28, $0b38, $0010, $0b30, $1d1d, $0000, $0000, $000c +dw $0b28, $0b38, $0010, $0b30, $1d1d, $0000, $0001, $000c dw $0b70, $0ba0, $0030, $0b88, $1d1d, $0000, $0000, $000d dw $0a40, $0b10, $00d0, $0aa8, $2525, $0000, $0000, $000e dw $0350, $0390, $0040, $0370, $2929, $0000, $0000, $000f @@ -1007,11 +1135,11 @@ dw $04d8, $04f8, $0020, $04e8, $3232, $0000, $0000, $0016 dw $0688, $06b0, $0028, $069c, $3333, $0000, $0000, $0017 dw $08d0, $08f0, $0020, $08e0, $3434, $0000, $0000, $0018 dw $0a80, $0b40, $00c0, $0ae0, $3535, $0000, $0000, $0019 -dw $0d38, $0d58, $0020, $0d48, $3536, $0000, $0000, $001a +dw $0d38, $0d58, $0020, $0d48, $3536, $0000, $0001, $001a dw $0d90, $0da0, $0010, $0d98, $3536, $0000, $0000, $001b dw $06a0, $07b0, $0110, $0728, $3b3b, $0000, $0000, $001c dw $0830, $09b0, $0180, $08f0, $3c3c, $0000, $0000, $001d -dw $0e78, $0e88, $0010, $0e80, $3f3f, $0000, $0000, $001e +dw $0e78, $0e88, $0010, $0e80, $3f3f, $0000, $0001, $001e dw $0ee0, $0fc0, $00e0, $0f50, $3f3f, $0000, $0000, $001f dw $0458, $0540, $00e8, $04cc, $4a4a, $0000, $0000, $0020 dw $0058, $0058, $0000, $0058, $5050, $0000, $0000, $0021 @@ -1025,7 +1153,7 @@ dw $02e8, $0348, $0060, $0318, $5859, $0000, $0000, $0028 dw $0478, $04d0, $0058, $04a4, $5a5a, $0000, $0000, $0029 dw $0510, $0538, $0028, $0524, $5a5a, $0000, $0000, $002a dw $0a48, $0af0, $00a8, $0a9c, $5d5d, $0000, $0000, $002b -dw $0b28, $0b38, $0010, $0b30, $5d5d, $0000, $0000, $002c +dw $0b28, $0b38, $0010, $0b30, $5d5d, $0000, $0001, $002c dw $0b70, $0ba0, $0030, $0b88, $5d5d, $0000, $0000, $002d dw $0a40, $0b10, $00d0, $0aa8, $6565, $0000, $0000, $002e dw $0350, $0390, $0040, $0370, $6969, $0000, $0000, $002f @@ -1039,11 +1167,11 @@ dw $04d8, $04f8, $0020, $04e8, $7272, $0000, $0000, $0036 dw $0688, $06b0, $0028, $069c, $7373, $0000, $0000, $0037 dw $08d0, $08f0, $0020, $08e0, $7474, $0000, $0000, $0038 dw $0a80, $0b40, $00c0, $0ae0, $7575, $0000, $0000, $0039 -dw $0d38, $0d58, $0020, $0d48, $7576, $0000, $0000, $003a +dw $0d38, $0d58, $0020, $0d48, $7576, $0000, $0001, $003a dw $0d90, $0da0, $0010, $0d98, $7576, $0000, $0000, $003b dw $06a0, $07b0, $0110, $0728, $7b7b, $0000, $0000, $003c dw $0830, $09b0, $0180, $08f0, $7c7c, $0000, $0000, $003d -dw $0e78, $0e88, $0010, $0e80, $7f7f, $0000, $0000, $003e +dw $0e78, $0e88, $0010, $0e80, $7f7f, $0000, $0001, $003e dw $0ee0, $0fc0, $00e0, $0f50, $7f7f, $0000, $0000, $003f OWSouthEdges: dw $0458, $0540, $00e8, $04cc, $0202, $0000, $0000, $0001 @@ -1058,7 +1186,7 @@ dw $02e8, $0348, $0060, $0318, $1111, $0000, $0000, $000a dw $0478, $04d0, $0058, $04a4, $1212, $0000, $0000, $000b dw $0510, $0538, $0028, $0524, $1212, $0000, $0000, $000c dw $0a48, $0af0, $00a8, $0a9c, $1515, $0000, $0000, $000d -dw $0b28, $0b38, $0010, $0b30, $1515, $0000, $0000, $000e +dw $0b28, $0b38, $0010, $0b30, $1515, $0000, $0001, $000e dw $0b70, $0ba0, $0030, $0b88, $1515, $0000, $0000, $000f dw $0a40, $0b10, $00d0, $0aa8, $1d1d, $0000, $0000, $0010 dw $0350, $0390, $0040, $0370, $1821, $0000, $0000, $0011 @@ -1072,11 +1200,11 @@ dw $04d8, $04f8, $0020, $04e8, $2a2a, $0000, $0000, $0018 dw $0688, $06b0, $0028, $069c, $2b2b, $0000, $0000, $0019 dw $08d0, $08f0, $0020, $08e0, $2c2c, $0000, $0000, $001a dw $0a80, $0b40, $00c0, $0ae0, $2d2d, $0000, $0000, $001b -dw $0d38, $0d58, $0020, $0d48, $2e2e, $0000, $0000, $001c +dw $0d38, $0d58, $0020, $0d48, $2e2e, $0000, $0001, $001c dw $0d90, $0da0, $0010, $0d98, $2e2e, $0000, $0000, $001d dw $06a0, $07b0, $0110, $0728, $3333, $0000, $0000, $001e dw $0830, $09b0, $0180, $08f0, $3434, $0000, $0000, $001f -dw $0e78, $0e88, $0010, $0e80, $3737, $0000, $0000, $0020 +dw $0e78, $0e88, $0010, $0e80, $3737, $0000, $0001, $0020 dw $0ee0, $0fc0, $00e0, $0f50, $3737, $0000, $0000, $0021 dw $0458, $0540, $00e8, $04cc, $4242, $0000, $0000, $0022 dw $0058, $0058, $0000, $0058, $4048, $0000, $0000, $0023 @@ -1090,7 +1218,7 @@ dw $02e8, $0348, $0060, $0318, $5151, $0000, $0000, $002a dw $0478, $04d0, $0058, $04a4, $5252, $0000, $0000, $002b dw $0510, $0538, $0028, $0524, $5252, $0000, $0000, $002c dw $0a48, $0af0, $00a8, $0a9c, $5555, $0000, $0000, $002d -dw $0b28, $0b38, $0010, $0b30, $5555, $0000, $0000, $002e +dw $0b28, $0b38, $0010, $0b30, $5555, $0000, $0001, $002e dw $0b70, $0ba0, $0030, $0b88, $5555, $0000, $0000, $002f dw $0a40, $0b10, $00d0, $0aa8, $5d5d, $0000, $0000, $0030 dw $0350, $0390, $0040, $0370, $5861, $0000, $0000, $0031 @@ -1104,11 +1232,11 @@ dw $04d8, $04f8, $0020, $04e8, $6a6a, $0000, $0000, $0038 dw $0688, $06b0, $0028, $069c, $6b6b, $0000, $0000, $0039 dw $08d0, $08f0, $0020, $08e0, $6c6c, $0000, $0000, $003a dw $0a80, $0b40, $00c0, $0ae0, $6d6d, $0000, $0000, $003b -dw $0d38, $0d58, $0020, $0d48, $6e6e, $0000, $0000, $003c +dw $0d38, $0d58, $0020, $0d48, $6e6e, $0000, $0001, $003c dw $0d90, $0da0, $0010, $0d98, $6e6e, $0000, $0000, $003d dw $06a0, $07b0, $0110, $0728, $7373, $0000, $0000, $003e dw $0830, $09b0, $0180, $08f0, $7474, $0000, $0000, $003f -dw $0e78, $0e88, $0010, $0e80, $7777, $0000, $0000, $0040 +dw $0e78, $0e88, $0010, $0e80, $7777, $0000, $0001, $0040 dw $0ee0, $0fc0, $00e0, $0f50, $7777, $0000, $0000, $0041 dw $0080, $0080, $0000, $0080, $8080, $0000, $0000, $0000 ;Pedestal (unused) dw $0288, $02c0, $0038, $02a4, $8189, $0000, $0000, $0002 ;Zora (unused) @@ -1123,10 +1251,10 @@ dw $0488, $0500, $0078, $04c4, $1313, $0000, $0000, $0006 dw $0538, $05a8, $0070, $0570, $1313, $0000, $0000, $0007 dw $0470, $05a8, $0138, $050c, $1414, $0000, $0000, $0008 dw $0470, $0598, $0128, $0504, $1515, $0000, $0000, $0009 -dw $0480, $0488, $0008, $0484, $1616, $0000, $0000, $000a +dw $0480, $0488, $0008, $0484, $1616, $0000, $0001, $000a dw $04b0, $0510, $0060, $04e0, $1616, $0000, $0000, $000b dw $0560, $0588, $0028, $0574, $1616, $0000, $0000, $000c -dw $0450, $0458, $0008, $0454, $1717, $0000, $0000, $000d +dw $0450, $0458, $0008, $0454, $1717, $0000, $0001, $000d dw $0480, $04a8, $0028, $0494, $1717, $0000, $0000, $000e dw $0718, $0738, $0020, $0728, $1b1b, $0000, $0000, $000f dw $0908, $0948, $0040, $0928, $2222, $0000, $0000, $0010 @@ -1136,13 +1264,13 @@ dw $0b60, $0ba0, $0040, $0b80, $2a2a, $0000, $0000, $0013 dw $0ab0, $0ad0, $0020, $0ac0, $2c2c, $0000, $0000, $0014 dw $0af0, $0b40, $0050, $0b18, $2c2c, $0000, $0000, $0015 dw $0b78, $0ba0, $0028, $0b8c, $2c2c, $0000, $0000, $0016 -dw $0b10, $0b28, $0018, $0b1c, $2d2d, $0000, $0000, $604a ;Stone Bridge (exit only) +dw $0b10, $0b28, $0018, $0b1c, $2d2d, $0000, $0001, $604a ;Stone Bridge (exit only) dw $0b68, $0b98, $0030, $0b80, $2d2d, $0000, $0000, $0017 dw $0a68, $0ab8, $0050, $0a90, $2e2e, $0000, $0000, $0018 -dw $0b00, $0b78, $0078, $0b3c, $2e2e, $0000, $0000, $0019 +dw $0b00, $0b78, $0078, $0b3c, $2e2e, $0000, $0001, $0019 dw $0c50, $0db8, $0168, $0d04, $3333, $0000, $0000, $001a dw $0c78, $0ce3, $006b, $0cad, $3434, $0000, $0000, $001b -dw $0ce4, $0d33, $004f, $0d0b, $3434, $0000, $0000, $001c +dw $0ce4, $0d33, $004f, $0d0b, $3434, $0000, $0001, $001c dw $0d34, $0db8, $0084, $0d76, $3434, $0000, $0000, $001d dw $0ea8, $0f20, $0078, $0ee4, $3a3a, $0000, $0000, $001e dw $0f70, $0fa8, $0038, $0f8c, $3a3a, $0000, $0000, $001f @@ -1150,7 +1278,7 @@ dw $0f18, $0f18, $0000, $0f18, $3b3b, $0000, $0000, $0020 dw $0fc8, $0fc8, $0000, $0fc8, $3b3b, $0000, $0000, $0021 dw $0e28, $0fb8, $0190, $0ef0, $3c3c, $0000, $0000, $0022 dw $0f78, $0fb8, $0040, $0f98, $353d, $0000, $0000, $0023 -dw $0f20, $0f40, $0020, $0f30, $3f3f, $0000, $0000, $0024 +dw $0f20, $0f40, $0020, $0f30, $3f3f, $0000, $0001, $0024 dw $0f70, $0fb8, $0048, $0f94, $3f3f, $0000, $0000, $0025 dw $0070, $00a0, $0030, $0088, $4242, $0000, $0000, $0026 dw $0068, $0078, $0010, $0070, $4545, $0000, $0000, $0027 @@ -1162,10 +1290,10 @@ dw $0488, $0500, $0078, $04c4, $5353, $0000, $0000, $002c dw $0538, $05a8, $0070, $0570, $5353, $0000, $0000, $002d dw $0470, $05a8, $0138, $050c, $5454, $0000, $0000, $002e dw $0470, $0598, $0128, $0504, $5555, $0000, $0000, $002f -dw $0480, $0488, $0008, $0484, $5656, $0000, $0000, $0030 +dw $0480, $0488, $0008, $0484, $5656, $0000, $0001, $0030 dw $04b0, $0510, $0060, $04e0, $5656, $0000, $0000, $0031 dw $0560, $0588, $0028, $0574, $5656, $0000, $0000, $0032 -dw $0450, $0458, $0008, $0454, $5757, $0000, $0000, $0033 +dw $0450, $0458, $0008, $0454, $5757, $0000, $0001, $0033 dw $0480, $04a8, $0028, $0494, $5757, $0000, $0000, $0034 dw $0908, $0948, $0040, $0928, $6262, $0000, $0000, $0035 dw $0878, $08a8, $0030, $0890, $6565, $0000, $0000, $0036 @@ -1177,16 +1305,16 @@ dw $0af0, $0b40, $0050, $0b18, $6c6c, $0000, $0000, $003b dw $0b78, $0ba0, $0028, $0b8c, $6c6c, $0000, $0000, $003c dw $0b68, $0b98, $0030, $0b80, $6d6d, $0000, $0000, $003d dw $0a68, $0ab8, $0050, $0a90, $6e6e, $0000, $0000, $003e -dw $0b00, $0b78, $0078, $0b3c, $6e6e, $0000, $0000, $003f +dw $0b00, $0b78, $0078, $0b3c, $6e6e, $0000, $0001, $003f dw $0c50, $0db8, $0168, $0d04, $7373, $0000, $0000, $0040 dw $0c78, $0ce3, $006b, $0cad, $7474, $0000, $0000, $0041 -dw $0ce4, $0d33, $004f, $0d0b, $7474, $0000, $0000, $0042 +dw $0ce4, $0d33, $004f, $0d0b, $7474, $0000, $0001, $0042 dw $0d34, $0db8, $0084, $0d76, $7474, $0000, $0000, $0043 dw $0f18, $0f18, $0000, $0f18, $7b7b, $0000, $0000, $0044 dw $0fc8, $0fc8, $0000, $0fc8, $7b7b, $0000, $0000, $0045 dw $0e28, $0fb8, $0190, $0ef0, $7c7c, $0000, $0000, $0046 dw $0f78, $0fb8, $0040, $0f98, $757d, $0000, $0000, $0047 -dw $0f20, $0f40, $0020, $0f30, $7f7f, $0000, $0000, $0048 +dw $0f20, $0f40, $0020, $0f30, $7f7f, $0000, $0001, $0048 dw $0f70, $0fb8, $0048, $0f94, $7f7f, $0000, $0000, $0049 OWEastEdges: dw $0070, $00a0, $0030, $0088, $0001, $0000, $0000, $0000 @@ -1199,10 +1327,10 @@ dw $0488, $0500, $0078, $04c4, $1212, $0000, $0000, $0006 dw $0538, $05a8, $0070, $0570, $1212, $0000, $0000, $0007 dw $0470, $05a8, $0138, $050c, $1313, $0000, $0000, $0008 dw $0470, $0598, $0128, $0504, $1414, $0000, $0000, $0009 -dw $0480, $0488, $0008, $0484, $1515, $0000, $0000, $000a +dw $0480, $0488, $0008, $0484, $1515, $0000, $0001, $000a dw $04b0, $0510, $0060, $04e0, $1515, $0000, $0000, $000b dw $0560, $0588, $0028, $0574, $1515, $0000, $0000, $000c -dw $0450, $0458, $0008, $0454, $1616, $0000, $0000, $000d +dw $0450, $0458, $0008, $0454, $1616, $0000, $0001, $000d dw $0480, $04a8, $0028, $0494, $1616, $0000, $0000, $000e dw $0718, $0738, $0020, $0728, $1a1a, $0000, $0000, $000f dw $0908, $0948, $0040, $0928, $1821, $0000, $0000, $0010 @@ -1214,10 +1342,10 @@ dw $0af0, $0b40, $0050, $0b18, $2b2b, $0000, $0000, $0015 dw $0b78, $0ba0, $0028, $0b8c, $2b2b, $0000, $0000, $0016 dw $0b68, $0b98, $0030, $0b80, $2c2c, $0000, $0000, $0018 dw $0a68, $0ab8, $0050, $0a90, $2d2d, $0000, $0000, $0019 -dw $0b00, $0b78, $0078, $0b3c, $2d2d, $0000, $0000, $001a +dw $0b00, $0b78, $0078, $0b3c, $2d2d, $0000, $0001, $001a dw $0c50, $0db8, $0168, $0d04, $3232, $0000, $0000, $001b dw $0c78, $0ce3, $006b, $0cad, $3333, $0000, $0000, $001c -dw $0ce4, $0d33, $004f, $0d0b, $3333, $0000, $0000, $001d +dw $0ce4, $0d33, $004f, $0d0b, $3333, $0000, $0001, $001d dw $0d34, $0db8, $0084, $0d76, $3333, $0000, $0000, $001e dw $0ea8, $0f20, $0078, $0ee4, $3039, $0000, $0000, $001f dw $0f70, $0fa8, $0038, $0f8c, $3039, $0000, $0000, $0020 @@ -1225,7 +1353,7 @@ dw $0f18, $0f18, $0000, $0f18, $3a3a, $0000, $0000, $0021 dw $0fc8, $0fc8, $0000, $0fc8, $3a3a, $0000, $0000, $0022 dw $0e28, $0fb8, $0190, $0ef0, $3b3b, $0000, $0000, $0023 dw $0f78, $0fb8, $0040, $0f98, $3c3c, $0000, $0000, $0024 -dw $0f20, $0f40, $0020, $0f30, $353e, $0000, $0000, $0025 +dw $0f20, $0f40, $0020, $0f30, $353e, $0000, $0001, $0025 dw $0f70, $0fb8, $0048, $0f94, $353e, $0000, $0000, $0026 dw $0070, $00a0, $0030, $0088, $4041, $0000, $0000, $0027 ;Skull Woods dw $0068, $0078, $0010, $0070, $4344, $0000, $0000, $0028 @@ -1237,10 +1365,10 @@ dw $0488, $0500, $0078, $04c4, $5252, $0000, $0000, $002d dw $0538, $05a8, $0070, $0570, $5252, $0000, $0000, $002e dw $0470, $05a8, $0138, $050c, $5353, $0000, $0000, $002f dw $0470, $0598, $0128, $0504, $5454, $0000, $0000, $0030 -dw $0480, $0488, $0008, $0484, $5555, $0000, $0000, $0031 +dw $0480, $0488, $0008, $0484, $5555, $0000, $0001, $0031 dw $04b0, $0510, $0060, $04e0, $5555, $0000, $0000, $0032 dw $0560, $0588, $0028, $0574, $5555, $0000, $0000, $0033 -dw $0450, $0458, $0008, $0454, $5656, $0000, $0000, $0034 +dw $0450, $0458, $0008, $0454, $5656, $0000, $0001, $0034 dw $0480, $04a8, $0028, $0494, $5656, $0000, $0000, $0035 dw $0908, $0948, $0040, $0928, $5861, $0000, $0000, $0036 dw $0878, $08a8, $0030, $0890, $5b64, $0000, $0000, $0037 @@ -1252,18 +1380,18 @@ dw $0af0, $0b40, $0050, $0b18, $6b6b, $0000, $0000, $003c dw $0b78, $0ba0, $0028, $0b8c, $6b6b, $0000, $0000, $003d dw $0b68, $0b98, $0030, $0b80, $6c6c, $0000, $0000, $003e dw $0a68, $0ab8, $0050, $0a90, $6d6d, $0000, $0000, $003f -dw $0b00, $0b78, $0078, $0b3c, $6d6d, $0000, $0000, $0040 +dw $0b00, $0b78, $0078, $0b3c, $6d6d, $0000, $0001, $0040 dw $0c50, $0db8, $0168, $0d04, $7272, $0000, $0000, $0041 dw $0c78, $0ce3, $006b, $0cad, $7373, $0000, $0000, $0042 -dw $0ce4, $0d33, $004f, $0d0b, $7373, $0000, $0000, $0043 +dw $0ce4, $0d33, $004f, $0d0b, $7373, $0000, $0001, $0043 dw $0d34, $0db8, $0084, $0d76, $7373, $0000, $0000, $0044 dw $0f18, $0f18, $0000, $0f18, $7a7a, $0000, $0000, $0045 dw $0fc8, $0fc8, $0000, $0fc8, $7a7a, $0000, $0000, $0046 dw $0e28, $0fb8, $0190, $0ef0, $7b7b, $0000, $0000, $0047 dw $0f78, $0fb8, $0040, $0f98, $7c7c, $0000, $0000, $0048 -dw $0f20, $0f40, $0020, $0f30, $757e, $0000, $0000, $0049 +dw $0f20, $0f40, $0020, $0f30, $757e, $0000, $0001, $0049 dw $0f70, $0fb8, $0048, $0f94, $757e, $0000, $0000, $004a -dw $0058, $00c0, $0068, $008c, $8080, $0000, $0000, $0017 ;Hobo (unused) +dw $0058, $00c0, $0068, $008c, $8080, $0000, $0001, $0017 ;Hobo (unused) org $aab9a0 ;PC 1539a0 OWSpecialDestIndex: diff --git a/data/base2current.bps b/data/base2current.bps index aa3ecc07e210ca0b15a00e67660df51bc4c770a8..50533c485865ff8e844c92191b67c16bacbc8da6 100644 GIT binary patch delta 2758 zcmXX`YgAKL7Cz_Rn+FMbFd{^rA=XL+YOJUMR04?dD#NPv(H7cJY0VhlTI;KFZVMXJ zkl>XZLW+sG2FMj8t#nnF!vtNmQ(WViS*u-bU8_^n)`j-Zt=f)@aoRbt{c-QU_w4Wc z_TFcoeZCx#UiwJt{ux@jez7x&IS>DR{!KU=!sPQER5^ry<^DnE!CLMyZA!K{e3}%L z`(DQehhTc&Y1rlvzTg%!HtJG07huw#}kJpZ+%oq}fWHThz=^?bMdTc{?U9%p0Iz!`v?jbEKdiarV+;<8j%^J_YU`xZx^ z!Q~4WB;TI#WUlJ{apu|+9rHSbM$~OcZaah2pX@(rKGJ1a$7$5*(9h+mH^bKRJ?d>A zOZ!(IIq}9Qr~Xv0?b6MgH}3|Q=k%{j`Nf~RinDyWhT7mg-}C8OUHw^#@PdTOz}>Oq6g?v4g-5;q_<(e|Dt__MuL$NWkf(DLR|4;uU9s5?cpI&&OQ5RA_nb? zXybJP?nYnu(LVv=yWb3Wv0VQLIqs*1lv$u(QuyT`8CCL+YUudedgM0reR zSU)VSd+pdnA(8z?S67KEieXTB@7T16YxUob)!oBlFgdL^?ttK|}UOl!%}A*LQRQlQ4k$fALZLI(n(0-rl}d>^tCSXAy1oR6ip+a{Y~#v@te$LPK(e5wTI zww9)5BR1v6dmpzn*{&?G^;4m?#?mmwn^LmTHz`BY((zVNb47dZvGiE&S-s-K+1n=j zsX~;A?5_mCu#TyTNro61k_|Jm7!{yM+xJH;|2t&ASr25FqQD`zHX$x2kwM|A+xpN^ zqeZs`$b4y={!@e$8=!#AOX1wson4Vcyl;gL6}8!lVV7(}IU zge5`{`p8hUJR)4xyU#@HBf_%5XkA1IilZnqDvmz&JEIRqZ! zk55a<4x6phwnq@$G+TT8^;o_P&V_Z@v);?z2_gPuERS+amxFZBtZ*+H@OW zdz;PXo1K(_Sn&71ICmfoF#ZOdPox0=#u9-M0#Ql!kl2{=b0jC9A|0SWo7`r*tsT-a ze^Wa?)*HdERpk8}#0Rr8&}s7>x+)}%|K_u7w`=%%J04#KpOLI)T{;%uTL$YXqXri( zhk4YVEd17TSkkIolZj@cF|Cwj)TfOfh!A5e)$M!Mp>P6OzvPUZbTA}oKYGPv^`sqe zBp}1+6muy6CSfd|R7~;t(_V|D*HPUWK_GCeuQ4wZbt|Hgi^2_8;5uDb+3g}zJORkq z!5d`!VhaCzEaGX@Iu2o?Jx|rOe)h$iaNHuLW z%4&@4w=(Dc(- zvM^1STAHl5tP~n6ND(65ST+M)y_?h$u@60wip=#VQu}v%E>lZxtlX+b{-TZVf30Amb^4MQgs;tB)c#4Z6xP#4pp#o_X4QI?9#p9oF9HC9 zG~iYRR9euZ!p`Az5RgV>T9_*`h zq)aJeaQolG7E30ge#N0GiS!7)r4m0WHlt>g-ZDHB3CmaVj28f2t$_5C zAU2CPpt#K@*1Kd$oT!iI)Jby6;a|n&D`8`jcg>o(#O6qdKK(he=7kX#=*!z8zAZY6>SW<9~a#H6Oiu`0qHWOBht?xPO@Py?OJEYpKrSvt27 z7gj-Q(XAAqRpu&nAVXPD5NFg_ZOR^UJ`nd25l;m+#RWwJa4T?iXGlk)?N#mtOO<#S zU#o&?MWp}VCoWX|87NhSh4`MgP<27o2KI1jw+p&MgKE8IFZf*D zCbH`m2hk==*y3QH=Sv-~GB0V_LVl$eH+$Jap6cyfxEQ%+VX+#%m|J#)9EtKHIOhm! z;SEo18L=+CgOJMkbO#0wwt_UvRs)}zo#SPzc!L)cTg{id{0s8$_)YnD@Y@+v1G_V( zJo9%5c%%mA&4R80;KF6D0gDSay9TT-d~0A5Umh68Ki5D@)-5)Tzm1y%Y&!o&fWX)c zqULILk(inMTX=OXe0~PIoXZZ9f(=2JzKhEV`hXjstc5S+G`raa{2Ok3nUr@0>$}*6 ze0CSUMM}F())tX9rVg$$5|%3`=qIe4AWLcr;?rz79~UE0D){s+47%7Ae5)JpsDoMh uY@U>4;E6j7Kn++A#(@nS$EA{o?I6{+r~DJ44$iKXcfKTDSAH?^>;D5Ia5Lus delta 2396 zcmXX_dr*_v6~8x6OvuB67MT`DZwCy z5X0sIDTE|MNPvYF9CfS{q}mRS4%wMH+qG+T#dcgf?PSqz9ZJ{UkMd7`xxe4_dmTkrWE*NE*EpbULM%lwI%kNla@To<_j)E^Oo7fJq z(cpdkx`Fb@vJGDSIrWptzp(WTm}035N{8A>ThLR8t9ZMO>U~3lfHbPVnr!9uG1!f5 zUUj=ASrBef3RYRl1pOt2ZQj=^L!ZoK8<4S|4qBs%d;h*>_tmx!u#@~k7TPjM3U8e4 zQd~02s&Gw$?8qj2!p51TSI)9-J9W_=2x>1Ik@Z-Tg~wpiWWVsl^^Bo&5kFqdzT;py zxn$%l@CWx+&D|0^*<`T$@~p}q=M<3)Yi?VgWaGw-bERZbKTCR*a=K!3Ed}>JVV=pj zI@x<~*Qw^mLGR#uae(m?g8@J^1dtbWH2$b=BvJaGA+&=8I=6T>6a-kZVuM#FJNB9} z&ce5X{-EXCF#_ysmx;tK-JR+1wm^XC0lKJ(-lOpbN@rteFZXb9v?u^kRo1m0?)ZZ@ z=L12O*tfHONT(I~nZ)L4gI23mKO1M;%8T6J2aU~zuTmN3!ZpOS!IP-gd$|360sgQ4 z!SzQf5-E{G0j28-L!{*0RpYp;0K=ow(eu%bvn|iQL*FW3-0B9(9SC+W;8);ypv5~L zgF-Xl0T_)=XTaU?WArEks^A(V%!EYDnf3?T*$F?}(^Rf(pUwAkWz}Px^6K{4G#vbm zq+E$U%7jk-eEVVml@U+xl+UB3OqjE`;yjZ?hc*Op=@lhIw93GcYc42yvG}XKRSI$54Sr-@q+z z8wvbJkRuC*!2RfY7EEY96(%LAu(=D@>-K49lC{?u?iRJu!$3dBeEStj=X(52*yti> zx;Y@45r6rPgV%)k&OGuWN!Oo7+F{=sl8MLTQ~me6?nwpU-u3~5*C=7}W(fIvQ1XIs zUzV#R#{t(C0oyZ3-a+UU00jp*)X(uOv3U< z_`t9?^S*52jE^zwy)WxL;S&yfhn>VI`AH^LiP2?sk54vAt2=!kj$#!IlUr$3HGx%P zBFmRQ;k{3Erur%-uo{>m*ZBfd%M#&Rn4%?#@24p&sU|w!ajqlNjens%n~<8 z{vM)ZBwCXTH?l)0Q#FYSb72Kztq^tP!URTL6uO%WvznW#qR6Lr#2m>%*eB*_UnIF% za;2mx*1$6Yu4}(MiuADY`?}fs%QHzDYxp^XQ8x98Wly67f?+|Mbmy|0a5?{g##y~Z0(@BOXW04V5fU9yLAl~@tLr9oqf49s>(LTN_w4jtM%AC{2&bKU?Bdr= zCqu}QUnKO~M9Cd?O=vGt7kAh~lRXd03#c29Rj`7Y-WShT@WmjBkJRERM=+@85Ac(K z!FF{_08~6N-;Y;z!v^cRvX~xOL8LB()(H1Hpb%6G2v8zGMn^d46buN& zpxd@Pc^-+j!HBr5y)Vb2K%>wg6a!MIRHB5nWZ!e)wCFjg6d~2Azv`vH{>xUha~nJ> zLYh`8(N)t*<0e-P`gR*Uv5Puwscoe)SLiH^`g7;3b$9KZ`p-YNf(p{!i9S6%PvyF* z56{z)N^G&$$Y0s_$Gk!rSql`?m6^3*+mp56s?J)lmNlS(B3Oirbx=ZAO$Uu4TRwq8{J6gUaho1;azz*E^B91a?=AtpX&=zzPB_tgtY& zCVio2f+c2az;j!8(Sn-~G;9Mq9yG8~mpd3#vZ}H3V-^xP90D9S%!&tHwiJg)2qatu zmjLnH2#XF;+o2}i9R|ewc)kQg^3jeCN2x~3@5bXMZ6`b2Yk`(`lUE5kdFXhDTLJ*T zk>3ci`N)XEOJR7rI|956P8ECv`1G_RL7L#Epaq<^rI_%HhlPZw5xf*;W+J)0uU<4P zQh+NWRB1=YOJQE9t;%E~Nb^lm3BFpXo!a118qmE`Se$9gX|<_Z?Rl+sRjYpYZfivu zg-5C~P|36fV+xIVJyfbIzlT1jxugbtZ7qsI&sshR9de)X%;|f^wJ;5n~!6 zOeoJZq%xrcrXh_9Ife$1$~=^3Mmx))D*Awl3U}3Fae#_&)ec}QUYvZ|j2EXCjhDj| zkv?~eZP4aGgqezUNzLeOIoz?{f%|gOR0>UDOr_C*yIN+q(5KoRu(*cSRP-svNjK!g zp8WrN3RWTuG3w8rMT7R`8bvhipo+1{f!lOa=Pma3>E-&4Qcw-1ffih`6|8zgt+h22 V-4Iv6^<_a1|NMRRZ=1h9`#+eI$Wj0R diff --git a/mystery_example.yml b/mystery_example.yml index feb4a3ed..1551f161 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -3,6 +3,9 @@ vanilla: 0 parallel: 2 full: 2 + overworld_terrain: + on: 1 + off: 1 overworld_crossed: none: 4 polar: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index d8166c48..09f9d69b 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -145,6 +145,10 @@ "full" ] }, + "ow_terrain": { + "action": "store_true", + "type": "bool" + }, "ow_crossed": { "choices": [ "none", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index aabea6c0..f1046bee 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -214,6 +214,8 @@ "Full: Overworld transitions are shuffled, but both worlds", " will have an independent map shape." ], + "ow_terrain": [ + "With OW Layout Shuffle, this allows land and water edges to be connected." ], "ow_crossed": [ "This allows cross-world connections to occur on the overworld.", "None: No transitions are cross-world connections.", @@ -226,7 +228,7 @@ ], "ow_keepsimilar": [ "This keeps similar edge transitions together. ie. the two west edges on", - "Potion Shop will be paired with another similar pair." ], + "Sanctuary will be paired with another similar pair." ], "ow_mixed": [ "Overworld tiles are randomly chosen to become part of the opposite world." ], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index e1b5f497..42607a14 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -131,6 +131,8 @@ "randomizer.overworld.overworldshuffle.parallel": "Parallel", "randomizer.overworld.overworldshuffle.full": "Full", + "randomizer.overworld.terrain": "Free Terrain", + "randomizer.overworld.crossed": "Crossed", "randomizer.overworld.crossed.none": "None", "randomizer.overworld.crossed.polar": "Polar", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index dc0363c6..91540b27 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -28,7 +28,7 @@ }, "mixed": { "type": "checkbox", - "default": true + "default": false }, "whirlpool": { "type": "checkbox", @@ -45,9 +45,13 @@ } }, "rightOverworldFrame": { + "terrain": { + "type": "checkbox", + "default": false + }, "keepsimilar": { "type": "checkbox", - "default": true + "default": false } } } \ No newline at end of file diff --git a/source/classes/constants.py b/source/classes/constants.py index 1b6a6aa5..92dd1eff 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -77,6 +77,7 @@ SETTINGSTOPROCESS = { }, "overworld": { "overworldshuffle": "ow_shuffle", + "terrain": "ow_terrain", "crossed": "ow_crossed", "keepsimilar": "ow_keepsimilar", "mixed": "ow_mixed", diff --git a/source/gui/randomize/overworld.py b/source/gui/randomize/overworld.py index d3796af6..190a750a 100644 --- a/source/gui/randomize/overworld.py +++ b/source/gui/randomize/overworld.py @@ -33,8 +33,10 @@ def overworld_page(parent): for key in dictWidgets: self.widgets[key] = dictWidgets[key] packAttrs = {"anchor":E} - if key == "keepsimilar": - packAttrs = {"side":LEFT, "pady":(18,0)} + if key == "terrain": + packAttrs = {"anchor":W, "pady":(3,0)} + elif key == "keepsimilar": + packAttrs = {"anchor":W, "pady":(6,0)} elif key == "overworldflute": packAttrs["pady"] = (20,0) elif key in ["mixed", "whirlpool"]: From dd6d8e508ff04c0f878f87117a0b86a7a0c0b7f1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 11 Oct 2022 07:12:52 -0500 Subject: [PATCH 07/59] Replaced bomb/rupee logic with pseudo locations/items --- BaseClasses.py | 157 ++++++------------------------------------------- ItemList.py | 105 +++++++++++++++++++++++++++++++++ Items.py | 2 + Main.py | 9 +-- Rules.py | 3 +- 5 files changed, 131 insertions(+), 145 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 81999759..b03e48a5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1087,150 +1087,14 @@ class CollectionState(object): return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player)) def can_farm_rupees(self, player): - tree_pulls = ['Lost Woods East Area', - 'Snitch Lady (East)', - 'Turtle Rock Area', - 'Pyramid Area', - 'Hype Cave Area', - 'Dark South Pass Area', - 'Bumper Cave Area'] - pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] - post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] + return self.has('Farmable Rupees', player) - rupee_farms = ['Archery Game', '50 Rupee Cave', '20 Rupee Cave'] - - bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] - pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] - rock_crabs = ['Desert Pass Area'] - - def can_reach_non_bunny(regionname): - region = self.world.get_region(regionname, player) - return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has_Pearl(player)) - - for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']: - if can_reach_non_bunny(region): - return True - - # tree pulls - if self.can_kill_most_things(player) and any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']): - for region in tree_pulls: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - else: - for region in post_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - - # bush crabs (final item isn't considered) - if self.world.enemy_shuffle[player] == 'none': - if self.world.prizes[player]['crab'][0] in [0xda, 0xdb]: - for region in bush_crabs: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_bush_crabs: - if can_reach_non_bunny(region): - return True - if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xda, 0xdb]: - for region in rock_crabs: - if can_reach_non_bunny(region): - return True - - return False - def can_farm_bombs(self, player): if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player): return True - - bush_bombs = ['Flute Boy Approach Area', - 'Kakariko Area', - 'Village of Outcasts Area', - 'Forgotten Forest Area', - 'Bat Cave Ledge', - 'East Dark Death Mountain (Bottom)'] - rock_bombs = ['Links House Area', - 'Dark Chapel Area', - 'Wooden Bridge Area', - 'Ice Cave Area', - 'Eastern Nook Area', - 'West Death Mountain (Bottom)', - 'Kakariko Fortune Area', - 'Skull Woods Forest', - 'Catfish Area', - 'Dark Fortune Area', - 'Qirn Jump Area', - 'Shield Shop Area', - 'Palace of Darkness Nook Area', - 'Swamp Nook Area', - 'Dark South Pass Area'] - bonk_bombs = ['Kakariko Fortune Area', 'Dark Graveyard Area'] #TODO: Flute Boy Approach Area and Bonk Rock Ledge are available post-Aga - bomb_caves = ['Graveyard Cave', 'Light World Bomb Hut'] - tree_pulls = ['Lost Woods East Area', - 'Snitch Lady (East)', - 'Turtle Rock Area', - 'Pyramid Area', - 'Hype Cave Area', - 'Dark South Pass Area', - 'Bumper Cave Area'] - pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] - post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] - - bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] - pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] - rock_crabs = ['Desert Pass Area'] - - def can_reach_non_bunny(regionname): - region = self.world.get_region(regionname, player) - return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has_Pearl(player)) - - # bomb pickups - for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []): - if can_reach_non_bunny(region): - return True - - if self.can_lift_rocks(player): - for region in rock_bombs: - if can_reach_non_bunny(region): - return True - - if not self.world.shuffle_bonk_drops[player] and self.can_collect_bonkdrops(player): - for region in bonk_bombs: - if can_reach_non_bunny(region): - return True - - # tree pulls - if self.can_kill_most_things(player) and any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']): - for region in tree_pulls: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - else: - for region in post_aga_tree_pulls: - if can_reach_non_bunny(region): - return True - - # bush crabs (final item isn't considered) - if self.world.enemy_shuffle[player] == 'none': - if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]: - for region in bush_crabs: - if can_reach_non_bunny(region): - return True - if not self.has_beaten_aga(player): - for region in pre_aga_bush_crabs: - if can_reach_non_bunny(region): - return True - if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]: - for region in rock_crabs: - if can_reach_non_bunny(region): - return True + if self.has('Farmable Bombs', player): + return True # stun prize if self.can_stun_enemies(player) and self.world.prizes[player]['stun'] in [0xdc, 0xdd, 0xde]: @@ -1239,6 +1103,7 @@ class CollectionState(object): # bomb purchases if self.can_farm_rupees(player) and (self.can_buy_unlimited('Bombs (10)', player) or self.can_reach('Big Bomb Shop', None, player)): return True + return False def item_count(self, item, player): @@ -1391,7 +1256,7 @@ class CollectionState(object): if self.has_Pearl(player): return True - return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world + return not region.can_cause_bunny(player) def can_reach_light_world(self, player): if True in [i.is_light_world for i in self.reachable_regions[player]]: @@ -1635,6 +1500,12 @@ class Region(object): return True + def can_cause_bunny(self, player): + if 'Moon Pearl' in list(map(str, [i for i in self.world.precollected_items if i.player == player])): + return False + + return self.is_dark_world if self.world.mode[player] != 'inverted' else self.is_light_world + def __str__(self): return str(self.__unicode__()) @@ -1830,6 +1701,9 @@ class Entrance(object): return found + def can_cause_bunny(self, player): + return self.parent_region.can_cause_bunny(player) + def connect(self, region, addresses=None, target=None, vanilla=None): self.connected_region = region self.target = target @@ -2681,6 +2555,9 @@ class Location(object): name += f' ({world.get_player_names(self.player)})' return name + def can_cause_bunny(self, player): + return self.parent_region.can_cause_bunny(player) + def __str__(self): return str(self.__unicode__()) diff --git a/ItemList.py b/ItemList.py index 7c252859..bd0aec53 100644 --- a/ItemList.py +++ b/ItemList.py @@ -505,6 +505,111 @@ def create_dynamic_shop_locations(world, player): loc.locked = True +def create_farm_locations(world, player): + bush_bombs = ['Flute Boy Approach Area', + 'Kakariko Area', + 'Village of Outcasts Area', + 'Forgotten Forest Area', + 'Bat Cave Ledge', + 'East Dark Death Mountain (Bottom)'] + rock_bombs = ['Links House Area', + 'Dark Chapel Area', + 'Wooden Bridge Area', + 'Ice Cave Area', + 'Eastern Nook Area', + 'West Death Mountain (Bottom)', + 'Kakariko Fortune Area', + 'Skull Woods Forest', + 'Catfish Area', + 'Dark Fortune Area', + 'Qirn Jump Area', + 'Shield Shop Area', + 'Palace of Darkness Nook Area', + 'Swamp Nook Area', + 'Dark South Pass Area'] + bonk_bombs = ['Kakariko Fortune Area', 'Dark Graveyard Area'] #TODO: Flute Boy Approach Area and Bonk Rock Ledge are available post-Aga + bomb_caves = ['Graveyard Cave', 'Light World Bomb Hut'] + + rupee_caves = ['50 Rupee Cave', '20 Rupee Cave'] + rupee_games = ['Archery Game'] + + tree_pulls = ['Lost Woods East Area', + 'Snitch Lady (East)', + 'Turtle Rock Area', + 'Pyramid Area', + 'Hype Cave Area', + 'Dark South Pass Area', + 'Bumper Cave Area'] + pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area'] + post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area'] + + bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area'] + pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area'] + rock_crabs = ['Desert Pass Area'] + + # NOTE: Altho pre-Aga locations cannot technically be guaranteed by the player, the + # goal here is just to ensure access to early rupees/bombs to get the player started, + # and hopefully access to more permanent farm locations + + def create_and_fill_location(region_name, loc_description, item_name): + region = world.get_region(region_name, player) + loc = Location(player, f'{region_name} {loc_description}', 0, region) + loc.type = LocationType.Logical + loc.parent_region = region + loc.event = True + loc.locked = True + loc.address = None + + world.push_item(loc, ItemFactory(item_name, player), False) + + region.locations.append(loc) + world.dynamic_locations.append(loc) + + return loc + + from Rules import set_rule, add_rule, add_bunny_rule + for region in bush_bombs: + loc = create_and_fill_location(region, 'Bush Drop', 'Farmable Bombs') + add_bunny_rule(loc, player) + for region in rock_bombs: + loc = create_and_fill_location(region, 'Rock Drop', 'Farmable Bombs') + set_rule(loc, lambda state: state.can_lift_rocks(player)) + add_bunny_rule(loc, player) + if not world.shuffle_bonk_drops[player]: + for region in bonk_bombs: + loc = create_and_fill_location(region, 'Bonk Drop', 'Farmable Bombs') + set_rule(loc, lambda state: state.can_collect_bonkdrops(player)) + add_bunny_rule(loc, player) + if world.pottery[player] in ['none', 'keys', 'dungeon']: + for region in bomb_caves + rupee_caves: + loc = create_and_fill_location(region, 'Pot Drop', 'Farmable Rupees' if region in rupee_caves else 'Farmable Bombs') + add_bunny_rule(loc, player) + for region in rupee_games: + loc = create_and_fill_location(region, 'Prize', 'Farmable Rupees') + add_bunny_rule(loc, player) + if any(i in [0xda, 0xdb, 0xdc, 0xdd, 0xde] for i in world.prizes[player]['pull']): + rupee_farm = any(i in [0xda, 0xdb] for i in world.prizes[player]['pull']) + for region in tree_pulls + pre_aga_tree_pulls + post_aga_tree_pulls: + loc = create_and_fill_location(region, 'Tree Pull', 'Farmable Rupees' if rupee_farm else 'Farmable Bombs') + set_rule(loc, lambda state: state.can_kill_most_things(player)) + if region in pre_aga_tree_pulls: + add_rule(loc, lambda state: not state.has_beaten_aga(player)) + elif region in post_aga_tree_pulls: + add_rule(loc, lambda state: state.has_beaten_aga(player)) + add_bunny_rule(loc, player) + if world.enemy_shuffle[player] == 'none' and any(i in [0xda, 0xdb, 0xdc, 0xdd, 0xde] for i in world.prizes[player]['crab']): + rupee_farm = any(i in [0xda, 0xdb] for i in world.prizes[player]['crab']) + for region in bush_crabs + pre_aga_bush_crabs + rock_crabs: + loc = create_and_fill_location(region, 'Crab Drop', 'Farmable Rupees' if rupee_farm else 'Farmable Bombs') + if region in pre_aga_bush_crabs: + set_rule(loc, lambda state: not state.has_beaten_aga(player)) + elif region in rock_crabs: + set_rule(loc, lambda state: state.can_lift_rocks(player)) + add_bunny_rule(loc, player) + + world.clear_location_cache() + + def create_dynamic_bonkdrop_locations(world, player): from Regions import bonk_prize_table for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items(): diff --git a/Items.py b/Items.py index 10d93159..59e6c228 100644 --- a/Items.py +++ b/Items.py @@ -198,4 +198,6 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), + 'Farmable Bombs': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), + 'Farmable Rupees': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), } diff --git a/Main.py b/Main.py index f1762147..f91f29c7 100644 --- a/Main.py +++ b/Main.py @@ -26,7 +26,7 @@ from Rules import set_rules from Dungeons import create_dungeons from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_pots from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops -from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops +from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, create_farm_locations from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config @@ -212,6 +212,10 @@ def main(args, seed=None, fish=None): mark_dark_world_regions(world, player) logger.info(world.fish.translate("cli", "cli", "generating.itempool")) + for player in range(1, world.players + 1): + set_prize_drops(world, player) + create_farm_locations(world, player) + for player in range(1, world.players + 1): generate_itempool(world, player) @@ -229,9 +233,6 @@ def main(args, seed=None, fish=None): else: lock_shop_locations(world, player) - for player in range(1, world.players + 1): - set_prize_drops(world, player) - massage_item_pool(world) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) diff --git a/Rules.py b/Rules.py index 5a7516f5..a0aafd7e 100644 --- a/Rules.py +++ b/Rules.py @@ -135,7 +135,8 @@ def add_rule(spot, rule, combine='and'): spot.access_rule = lambda state: rule(state) and old_rule(state) def add_bunny_rule(spot, player): - add_rule(spot, lambda state: state.is_not_bunny(spot.parent_region, player)) + if spot.can_cause_bunny(player): + add_rule(spot, lambda state: state.has_Pearl(player)) def or_rule(rule1, rule2): From 6e3b4a6b2d171234111a69dd4908d39b0a2b510d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 20 Oct 2022 10:29:59 -0500 Subject: [PATCH 08/59] Cato fixes for Free Terrain transitions --- Rom.py | 2 +- asm/owrando.asm | 39 ++++++++++++++++++++------------------- data/base2current.bps | Bin 104763 -> 104770 bytes 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Rom.py b/Rom.py index 03e14ef2..570cfbe3 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '4657087846e24d65cd69ff61241601fe' +RANDOMIZERBASEHASH = '10c74317eecc403488d1a6e7e3d8f49f' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 812cec20..2863b0d5 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -107,7 +107,7 @@ org $0ABA99 WorldMap_LoadDarkWorldMap: LDA.b $10 : CMP.b #$14 ; attract module BEQ .vanilla_light - LDA.l OWMode+1 : AND.b #$04 : BNE .mixed + LDA.l OWMode+1 : AND.b #!FLAG_OW_MIXED : BNE .mixed LDA.b $8A : AND.b #$40 BEQ .vanilla_light .mixed @@ -343,7 +343,7 @@ OWOldManSpeed: LoadMapDarkOrMixed: { - CMP.b #$04 : REP #$30 : BEQ .mixed + CMP.b #!FLAG_OW_MIXED : REP #$30 : BEQ .mixed LDX.w #$03FE ; draw vanilla Dark World (what we wrote over) .copy_next LDA.w $D739,X : STA.w $1000,X ; DB is $0A @@ -400,7 +400,7 @@ OWBonkDrops: { CMP.b #$D8 : BEQ + RTL - + LDA.l OWFlags+1 : AND.b #$02 : BNE + + + LDA.l OWFlags+1 : AND.b #!FLAG_OW_CROSSED : BNE + JSL.l Sprite_TransmuteToBomb : RTL + @@ -529,10 +529,10 @@ org $aa9000 OWDetectEdgeTransition: { STZ.w $06FC - LDA.l OWMode : ORA.l OWMode+1 : BEQ .normal + LDA.l OWMode : ORA.l OWMode+1 : BEQ .vanilla JSR OWShuffle LDA.w $06FA : BMI .special - .normal + .vanilla REP #$31 : LDX.b $02 : LDA.b $84 ; what we wrote over RTL .special @@ -586,9 +586,9 @@ OWDetectSpecialTransition: } OWEdgeTransition: { - LDA.l OWMode : ORA.l OWMode+1 : BEQ .normal + LDA.l OWMode : ORA.l OWMode+1 : BEQ .vanilla LDY.w $06FA : CPY.b #$7F - BEQ .normal + BEQ .vanilla REP #$10 LDX.w $06F8 PHB : PHK : PLB @@ -596,7 +596,7 @@ OWEdgeTransition: PLB SEP #$30 RTL - .normal + .vanilla LDA.l Overworld_ActualScreenID,X : ORA.l CurrentWorld ; what we wrote over RTL } @@ -835,9 +835,9 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, ; toggle bunny mode lda MoonPearlEquipment : bne .nobunny lda.l InvertedMode : bne .inverted - lda CurrentWorld : and.b #$40 : bra + - .inverted lda CurrentWorld : and.b #$40 : eor #$40 - + cmp #$40 : bne .nobunny + lda CurrentWorld : bra + + .inverted lda CurrentWorld : eor #$40 + + and #$40 : beq .nobunny LDA.w $0703 : BEQ + ; check if forced transition CPY.b #$03 : BEQ .end_forced_whirlpool @@ -865,8 +865,9 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, .whirlpool PLX : RTS .to_bunny_reset_swim - JSL Link_ResetSwimmingState - STZ.w $0345 + LDA.b $5D : CMP.b #$04 : BNE .to_bunny ; check if swimming + JSL Link_ResetSwimmingState + STZ.w $0345 .to_bunny LDA.b #$17 : STA.b $5D .to_pseudo_bunny @@ -886,7 +887,7 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, .end_forced_whirlpool STZ.w $0703 CMP.b #$02 : BNE + - DEC : STA.w $0345 + DEC : STA.w $0345 : STZ.w $0340 LDA.b #$04 : BRA .set_state + CMP.b #$03 : BNE ++ @@ -897,17 +898,17 @@ OWWorldTerrainUpdate: ; x = owid of destination screen, y = 1 for land to water, RTS .not_forced CPY.b #$02 : BNE + ; check if going from water to land - JSL Link_ResetSwimmingState - STZ.w $0345 - LDA.b $5D : CMP.b #$04 : BNE + ; check if swimming + LDA.b $5D : CMP.b #$04 : BNE .return ; check if swimming + JSL Link_ResetSwimmingState + STZ.w $0345 STZ.b $5D + CPY.b #$01 : BNE .return ; check if going from land to water - LDA.b #$01 : STA.w $0345 LDA.b $5D : CMP.b #$04 : BEQ .return ; check if swimming + LDA.b #$01 : STA.w $0345 LDA.l FlippersEquipment : BEQ .no_flippers ; check if flippers obtained LDA.b $5D : CMP.b #$17 : BEQ .no_flippers ; check if bunny - LDA.b #$04 : STA.b $5D : RTS + LDA.b #$04 : STA.b $5D : STZ.w $0340 : RTS .no_flippers PHX INC : STA.w $0703 diff --git a/data/base2current.bps b/data/base2current.bps index 50533c485865ff8e844c92191b67c16bacbc8da6..33d00fd368d02ccf043f17787a4167cd1469ec16 100644 GIT binary patch delta 366 zcmdnJiS5uPwhf7lEcvn%9&Ju#v}cw(JarXA^F$@#7WG$uCMxLzDTbzAC94*7gO*;U z-_3_uwjW|)TwulMK0Vok=CuB0`CeU2;h>YBo3{U18TFEGs(Em_73GW3HA&<@s=NKc^j9A@^DOc%JV zdVx#_=9MfBY$us7u=GD$nQ(yV0zcR!kJ)m?%hsmE9$>s+4HEcZI{D~iRbI}9vwT3V z_y>-aEUmFX%?Y-ch*W=rh5T4cTk{uzg@T`-d(2e%V1L@$+tZ zd=2w8)0?kX76~`3&eNVZo8Pc+euBuX=`rqovqcP7p2}3;e#L2T8FLc&^R_75Tlm_HP) z&MjQV*Z|}<#2#S$z;%G>gU-^}lPni>Z>?lp7W;tZ0;5zfGy8#Q8aeF`U76QOuy868o!x_g8|F)(rMJuFR`hik8hx0O}LJz%*O67-HZDMWFetK`EX<1#_6$PcmKL zub%7 From d8fc0ff752261870e653aac634e552ecb40bbc3e Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 20 Oct 2022 10:31:24 -0500 Subject: [PATCH 09/59] Minor simplification --- BaseClasses.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b03e48a5..903f72e6 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1253,10 +1253,7 @@ class CollectionState(object): 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 not region.can_cause_bunny(player) + return self.has_Pearl(player) or not region.can_cause_bunny(player) def can_reach_light_world(self, player): if True in [i.is_light_world for i in self.reachable_regions[player]]: From 111ce66962b405c07c3752d40a2c783335498bd0 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 22 Oct 2022 09:22:26 -0500 Subject: [PATCH 10/59] Fixed glitched fake world and OOB behavior --- Rom.py | 7 ++++--- asm/owrando.asm | 14 ++++++++++---- data/base2current.bps | Bin 104770 -> 104809 bytes 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Rom.py b/Rom.py index 570cfbe3..02219958 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '10c74317eecc403488d1a6e7e3d8f49f' +RANDOMIZERBASEHASH = 'cb04e9b67920dcc8103f1e7a7fe86fe2' class JsonRom(object): @@ -914,9 +914,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): return region.is_light_world and not region.is_dark_world # dark world spawns - sanc_region = world.get_region('Sanctuary', player) + sanc_name = 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint' + sanc_region = world.get_region(sanc_name, player) if should_be_bunny(sanc_region, world.mode[player]): - rom.write_bytes(0x13fff2, [0x12, 0x00]) + rom.write_bytes(0x13fff2, [0x12, 0x00 if sanc_name == 'Sanctuary' else 0x01]) lh_name = 'Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop' links_house = world.get_region(lh_name, player) diff --git a/asm/owrando.asm b/asm/owrando.asm index 2863b0d5..3472bfae 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -586,9 +586,9 @@ OWDetectSpecialTransition: } OWEdgeTransition: { - LDA.l OWMode : ORA.l OWMode+1 : BEQ .vanilla + LDA.l OWMode : ORA.l OWMode+1 : BEQ .unshuffled LDY.w $06FA : CPY.b #$7F - BEQ .vanilla + BEQ .unshuffled REP #$10 LDX.w $06F8 PHB : PHK : PLB @@ -596,9 +596,15 @@ OWEdgeTransition: PLB SEP #$30 RTL - .vanilla + + .unshuffled LDA.l Overworld_ActualScreenID,X : ORA.l CurrentWorld ; what we wrote over - RTL + TAX : LDA.l OWMode+1 : AND.b #!FLAG_OW_MIXED : BEQ .vanilla + LDA.l OWTileWorldAssoc,X : CMP.l CurrentWorld : BEQ .vanilla ; if dest screen mismatches the current world + TXA : EOR #$40 : RTL + + .vanilla + TXA : RTL } OWSpecialExit: { diff --git a/data/base2current.bps b/data/base2current.bps index 33d00fd368d02ccf043f17787a4167cd1469ec16..ac1a5a02a429ff34144ed8a4d6e185bff031e9f1 100644 GIT binary patch delta 546 zcmV+-0^R+>vIgn02C!iP1oH+A`LksKKLa1zm8t-Pks=d>C+hr>A}@p|>Hva`B07X8 zFocaF6PA<11WN(^gAWC_4+R0EC0Y%DdSD2JppA|MB9oaXukimT#h~yIuk!yV#h~yA zuk`;X#h}n3uN!ZVnXd(aCn*B(mPAQ{u?MLag>2&k*)d$Fb9?`PO;5v!Z0ZV0H_M8seb^C`UW6tmw!6}k76Q{tKu+go-}}gs+VVc zqnYpyzp%Ng&yy=?fB}JuNkD6gYg(WHtr!GK&;_gn2Y?5y1P9OppaLM2lzP8F{eElO zP5=kHzW|7;jTi(9!XPRC07&8>rHUy)7;OLtn{Y*$msvakEdh#`hdcqg0e+W4Jpp+E zQ3f%tz=!m0nj_8UC9LSHIv1YRSgM%%?_nq$rtblsRV^xoInF$+8T`m2ct@tTtERr k0uMr$pg;i?2{+D0ZVrH-d1kl1KmmMM2zwnU9mwMe3o(JzuK)l5 delta 522 zcmV+l0`>jrvIfGk2C!iP1aBRX;C+hr>A}@p|>Hva`B07X8 zFocaF`<0W!1WN(sgAWC_4+R0EB}SQgV1=NKjs%x-2Z@OS@G_bpHL$JFDX;MVC&i%f z5U=u+G?Ix3uk??Gsn8MPFsT=XUGCrlmwSGig~BjTl30C#mmw{`mkcWbJ_B<%v$stv z0qqO{e3v^o0i6pufC;ZkfH3d^sX&+DH~};QAc40RIRRY)9RrcA`UWrumMu=P&1=Fi zDFXng3aY7p0FC+vAXAr-I{}YN3X-ehFl(MPfE|EqTA%=}7z9et1*`-IfCsGv2hakb z0w8>pdcQ#ZerwuJ00+Cj0EnuOYJig=3OKKG0SlKVKLH{Ee3wZ- z0WK(flwg}&D;ztFm5<8ve#rpv0;vJdN`NrX3ZH_Poj(C~3w&{= zkMM~X&;pwkmmWX?H!dD?rmbXMzyZ)ZrCrGc@HLaglO+uafXxo2UC9^l2&n{xU7SDz zVA>ju0|%oTmxDk7LLgs36M)6$5oEAyto#P>1kL;gYu|Rk0vFFlZVrH-DQCCkKmmMM M2xt*S!SXz|rx8ZZr2qf` From 2c2ed5d5af3787c7dce9a9384b973d9f887e3098 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 22 Oct 2022 09:23:39 -0500 Subject: [PATCH 11/59] Adding missing shuffles for Simple Dungeon ER shuffle --- EntranceShuffle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index d6c207f2..96bc31d2 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1137,6 +1137,7 @@ def simple_shuffle_dungeons(world, player): multi_dungeons = ['Desert Palace', 'Turtle Rock'] if world.mode[player] == 'standard' or (world.mode[player] == 'inverted' and not world.shuffle_ganon): hc_target = 'Hyrule Castle' + random.shuffle(multi_dungeons) else: multi_dungeons.append('Hyrule Castle') @@ -1163,6 +1164,7 @@ def simple_shuffle_dungeons(world, player): random.shuffle(candidate_dungeons) hc_target = candidate_dungeons.pop() multi_dungeons.remove(hc_target) + random.shuffle(multi_dungeons) else: random.shuffle(multi_dungeons) hc_target = multi_dungeons.pop() From f28fc45a46aea803a0481ea6805975e19c6a9cb9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 16:55:44 -0500 Subject: [PATCH 12/59] Added Mixed OWG Logic --- Main.py | 4 +- OWEdges.py | 4 +- OverworldGlitchRules.py | 484 +++++++++++++++++++++++----------------- OverworldShuffle.py | 187 +++++++++------- Regions.py | 6 +- 5 files changed, 398 insertions(+), 287 deletions(-) diff --git a/Main.py b/Main.py index f91f29c7..e7d296ce 100644 --- a/Main.py +++ b/Main.py @@ -163,8 +163,6 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): create_regions(world, player) - if world.logic[player] in ('owglitches', 'nologic'): - create_owg_connections(world, player) create_dungeon_regions(world, player) create_owedges(world, player) create_shops(world, player) @@ -189,6 +187,8 @@ def main(args, seed=None, fish=None): link_overworld(world, player) create_shops(world, player) update_world_regions(world, player) + if world.logic[player] in ('owglitches', 'nologic'): + create_owg_connections(world, player) create_flute_exits(world, player) logger.info(world.fish.translate("cli","cli","shuffling.world")) diff --git a/OWEdges.py b/OWEdges.py index f6145831..9a6a52cb 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1087,6 +1087,7 @@ OWTileRegions = bidict({ 'Hyrule Castle Courtyard Northeast': 0x1b, 'Hyrule Castle Ledge': 0x1b, 'Hyrule Castle East Entry': 0x1b, + 'Hyrule Castle Water': 0x1b, 'Wooden Bridge Area': 0x1d, 'Wooden Bridge Northeast': 0x1d, @@ -1227,6 +1228,7 @@ OWTileRegions = bidict({ 'Pyramid Crack': 0x5b, 'Pyramid Exit Ledge': 0x5b, 'Pyramid Pass': 0x5b, + 'Pyramid Water': 0x5b, 'Broken Bridge Area': 0x5d, 'Broken Bridge Northeast': 0x5d, @@ -1491,7 +1493,6 @@ OWExitTypes = { 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', - 'Desert Pass Southeast Cliff Ledge Drop', 'Dam Cliff Ledge Drop', 'Bombos Tablet Drop', 'Cave 45 Ledge Drop', @@ -1540,6 +1541,7 @@ OWExitTypes = { 'Dark South Teleporter Cliff Ledge Drop', 'Misery Mire Teleporter Ledge Drop', 'Mire Cliff Ledge Drop', + 'Dark Checkerboard Cliff Ledge Drop', 'Archery Game Cliff Ledge Drop', 'Stumpy Approach Cliff Ledge Drop', 'Mire C Whirlpool Cliff Ledge Drop', diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index cfe15841..291295c6 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -3,6 +3,7 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. """ from BaseClasses import Entrance +from OWEdges import OWTileRegions def get_sword_required_superbunny_mirror_regions(): @@ -25,6 +26,8 @@ def get_boots_required_superbunny_mirror_locations(): yield 'Sahasrahla\'s Hut - Middle' yield 'Sahasrahla\'s Hut - Right' + # TODO: Add pottery locations + def get_invalid_mirror_bunny_entrances(): """ @@ -98,7 +101,7 @@ def get_superbunny_accessible_locations(): yield location -def get_non_mandatory_exits(inverted): +def get_non_mandatory_exits(world, player): """ Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit. The following are still be mandatory exits: @@ -116,257 +119,169 @@ def get_non_mandatory_exits(inverted): yield 'Death Mountain Return Cave (West)' yield 'Hookshot Cave Back Entrance' - if inverted: + if world.is_tile_swapped(0x30, player): yield 'Desert Palace Entrance (North)' yield 'Desert Palace Entrance (West)' + else: + yield 'Desert Palace Entrance (East)' + + if world.is_tile_swapped(0x1b, player): yield 'Agahnims Tower' yield 'Hyrule Castle Entrance (West)' yield 'Hyrule Castle Entrance (East)' - else: + + if not world.is_tile_swapped(0x05, player): yield 'Dark Death Mountain Ledge (West)' yield 'Dark Death Mountain Ledge (East)' - yield 'Mimic Cave' - yield 'Desert Palace Entrance (East)' + #yield 'Mimic Cave' #TODO: This was here, I don't think this is true -def get_boots_clip_exits_lw(inverted = False): +def get_boots_clip_exits_lw(world, player): """ Special Light World region exits that require boots clips. """ - yield ('Lumberjack DMA Clip', 'Lumberjack Area', 'West Death Mountain (Bottom)') - yield ('Lumberjack DMD Clip', 'West Death Mountain (Top)', 'Lumberjack Area') - yield ('DM Glitched Bridge Clip', 'West Death Mountain (Bottom)', 'East Death Mountain (Top East)') - yield ('WDM to EDM Top Clip', 'West Death Mountain (Top)', 'East Death Mountain (Top West)') - yield ('Hera Ascent Clip', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)') #cannot guarantee camera correction, but a bomb clip exists - yield ('Sanctuary DMD Clip', 'West Death Mountain (Bottom)', 'Sanctuary Area') - yield ('Graveyard Ledge DMD Clip', 'West Death Mountain (Bottom)', 'Graveyard Ledge') - yield ('Kings Grave DMD Clip', 'West Death Mountain (Bottom)', 'Kings Grave Area') - yield ('EDM to WDM Top Clip', 'East Death Mountain (Top West)', 'West Death Mountain (Top)') - yield ('EDM East Dropdown Clip', 'East Death Mountain (Top East)', 'East Death Mountain (Bottom Left)') - yield ('EDM To TR Pegs Clip', 'East Death Mountain (Top East)', 'Death Mountain TR Pegs') - yield ('EDM DMD FAWT Clip', 'East Death Mountain (Bottom)', 'Potion Shop Area') - yield ('WDM To EDM Bottom Clip', 'East Death Mountain (Bottom Left)', 'East Death Mountain (Bottom)') - yield ('WDM DMD To River Bend Clip', 'East Death Mountain (Bottom Left)', 'River Bend Area') - yield ('EDM DMD To River Bend Clip', 'East Death Mountain (Bottom)', 'River Bend Area') - yield ('TR Pegs Ledge Clip', 'Death Mountain TR Pegs', 'Death Mountain TR Pegs Ledge') - yield ('TR Pegs Ledge Descent Clip', 'Death Mountain TR Pegs Ledge', 'Death Mountain TR Pegs') - yield ('TR Pegs To EDM Clip', 'Death Mountain TR Pegs', 'East Death Mountain (Top East)') - yield ('Zora DMD Clip', 'Death Mountain TR Pegs', 'Zora Waterfall Area') - yield ('Mountain Entry To Ledge Clip', 'Mountain Entry Area', 'Mountain Entry Ledge') - yield ('Mountain Ledge Drop Clip', 'Mountain Entry Ledge', 'Mountain Entry Entrance') - yield ('Mountain Entry To Pond Clip', 'Mountain Entry Area', 'Kakariko Pond Area') - yield ('Zora Waterfall Ledge Clip', 'Zora Waterfall Area', 'Zora Approach Area') - - #yield ('Pond DMA Clip', 'Kakariko Pond Area', 'West Death Mountain (Bottom)') #cannot guarantee camera correction - yield ('Pond To Mountain Entry Clip', 'Kakariko Pond Area', 'Mountain Entry Area') - yield ('Pond To Bonk Rocks Clip', 'Kakariko Pond Area', 'Bonk Rock Ledge') - yield ('River Bend To Potion Shop Clip', 'River Bend East Bank', 'Potion Shop Area') - yield ('River Bend To Wooden Bridge Clip', 'River Bend East Bank', 'Wooden Bridge Area') - yield ('Potion Shop To EP Clip', 'Potion Shop Area', 'Eastern Palace Area') - yield ('Potion Shop To River Bend Clip', 'Potion Shop Area', 'River Bend East Bank') - yield ('Potion Shop To Zora Approach Clip', 'Potion Shop Northeast', 'Zora Approach Area') - yield ('Zora Approach To Potion Shop Clip', 'Zora Approach Area', 'Potion Shop Area') - - yield ('Kakariko Bomb Hut Clip', 'Kakariko Area', 'Maze Race Area') - yield ('Forgotten Forest To Blacksmith Clip', 'Forgotten Forest Area', 'Blacksmith Area') #fake flipper - yield ('Hyrule Castle To Blacksmith Clip', 'Hyrule Castle Area', 'Blacksmith Area') #fake flipper - yield ('Wooden Bridge To Dunes Clip', 'Wooden Bridge Area', 'Sand Dunes Area') - yield ('Eastern Palace To Zora Approach Clip', 'Eastern Palace Area', 'Zora Approach Area') - yield ('Eastern Palace To Nook Clip', 'Eastern Palace Area', 'Eastern Nook Area') - yield ('Eastern Palace To Cliff Clip', 'Eastern Palace Area', 'Eastern Cliff') - #yield ('Bat Cave River Clip Spot', 'Blacksmith Area', 'Bat Cave Ledge') #cannot guarantee camera correction - yield ('Sand Dunes To Cliff Clip', 'Sand Dunes Area', 'Eastern Cliff') - - yield ('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize') - yield ('Maze Race To Desert Ledge Clip', 'Maze Race Area', 'Desert Ledge') - yield ('Maze Race To Desert Boss Clip', 'Maze Race Area', 'Desert Palace Entrance (North) Spot') - yield ('Suburb To Cliff Clip', 'Kakariko Suburb Area', 'Desert Northeast Cliffs') - yield ('Central Bonk Rocks To Cliff Clip', 'Central Bonk Rocks Area', 'Central Cliffs') - yield ('Links House To Cliff Clip', 'Links House Area', 'Central Cliffs') - yield ('Stone Bridge To Cliff Clip', 'Stone Bridge Area', 'Central Cliffs') - yield ('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water') #requires flippers - yield ('Eastern Nook To Eastern Clip', 'Eastern Nook Area', 'Eastern Palace Area') - yield ('Eastern Nook To Ice Cave FAWT Clip', 'Eastern Nook Area', 'Ice Cave Area') - - yield ('Desert To Maze Race Clip', 'Desert Ledge', 'Maze Race Area') - yield ('Desert Ledge To Cliff Clip', 'Desert Ledge', 'Desert Northeast Cliffs') #requires gloves - yield ('Checkerboard To Cliff Clip', 'Desert Checkerboard Ledge', 'Desert Northeast Cliffs') - yield ('Desert To Cliff Clip', 'Desert Area', 'Desert Northeast Cliffs') - yield ('Desert To Teleporter Clip', 'Desert Area', 'Desert Palace Teleporter Ledge') - yield ('Desert To Bombos Tablet Clip', 'Desert Area', 'Bombos Tablet Ledge') - - yield ('Flute Boy To Cliff Clip', 'Flute Boy Approach Area', 'Desert Northeast Cliffs') - yield ('Cave 45 To Cliff Clip', 'Cave 45 Ledge', 'Desert Northeast Cliffs') - yield ('C Whirlpool To Cliff Clip', 'C Whirlpool Area', 'Central Cliffs') - yield ('C Whirlpool Outer To Cliff Clip', 'C Whirlpool Outer Area', 'Central Cliffs') - yield ('Statues To Cliff Clip', 'Statues Area', 'Central Cliffs') - yield ('Lake Hylia To Statues Clip', 'Lake Hylia Area', 'Statues Area') - yield ('Lake Hylia To South Pass Clip', 'Lake Hylia Area', 'South Pass Area') - yield ('Lake Hylia To Shore Clip', 'Lake Hylia Area', 'Lake Hylia South Shore') - yield ('Desert Pass To Cliff Clip', 'Desert Pass Area', 'Desert Northeast Cliffs') - yield ('Desert Pass Southeast To Cliff Clip', 'Desert Pass Southeast', 'Desert Northeast Cliffs') - #yield ('Desert Pass To Zora Clip', 'Desert Pass Area', 'Zoras Domain') #revisit when Zora is shuffled - yield ('Dam To Cliff Clip', 'Dam Area', 'Desert Northeast Cliffs') - yield ('South Pass To Lake Hylia Clip', 'South Pass Area', 'Lake Hylia Area') - yield ('South Pass To Shore Clip', 'South Pass Area', 'Lake Hylia South Shore') - #yield ('Octoballoon To Shore Clip', 'Octoballoon Area', 'Lake Hylia South Shore') #map wrap hardlock risk - - if not inverted: - yield ('Spectacle Rock Ledge Clip', 'West Death Mountain (Top)', 'Spectacle Rock Ledge') - yield ('Floating Island Clip', 'East Death Mountain (Top East)', 'Death Mountain Floating Island') - yield ('Floating Island Return Clip', 'Death Mountain Floating Island', 'East Death Mountain (Top East)') + for name, parent_region, target_region in boots_clips_local: + if not world.is_tile_swapped(OWTileRegions[parent_region], player): + yield(name, parent_region, target_region) -def get_boots_clip_exits_dw(inverted): + for name, parent_region, target_region in boots_clips: + parent_swapped, target_swapped = get_swapped_status(world, player, parent_region, target_region) + if parent_region[0] and not parent_swapped: + if target_region[0] and not target_swapped: + yield(name[0], parent_region[0], target_region[0]) + elif target_region[1]: + yield(name[0], parent_region[0], target_region[1]) + elif parent_region[1]: + if target_region[0] and not target_swapped: + yield(name[1], parent_region[1], target_region[0]) + elif target_region[1]: + yield(name[1], parent_region[1], target_region[1]) + +def get_boots_clip_exits_dw(world, player): """ Special Dark World region exits that require boots clips. """ - yield ('Dark Lumberjack DMA Clip', 'Dark Lumberjack Area', 'West Dark Death Mountain (Bottom)') - yield ('DDM Glitched Bridge Clip', 'West Dark Death Mountain (Bottom)', 'East Dark Death Mountain (Top)') - yield ('Chapel DMD Clip', 'West Dark Death Mountain (Bottom)', 'Dark Chapel Area') - yield ('Dark Graveyard DMD Clip', 'West Dark Death Mountain (Bottom)', 'Dark Graveyard Area') - yield ('EDDM West Dropdown Clip', 'East Dark Death Mountain (Top)', 'East Dark Death Mountain (Bottom Left)') - yield ('EDDM To WDDM Clip', 'East Dark Death Mountain (Top)', 'West Dark Death Mountain (Top)') - yield ('TR Bridge Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge') - yield ('Dark Witch DMD FAWT Clip', 'East Dark Death Mountain (Bottom)', 'Dark Witch Area') - yield ('Qirn Jump DMD Clip', 'East Dark Death Mountain (Bottom Left)', 'Qirn Jump Area') - yield ('WDDM To EDDM Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)') - #yield ('DW Floating Island Clip', 'East Dark Death Mountain (Bottom)', 'Dark Death Mountain Floating Island') #cannot guarantee camera correction - yield ('TR To EDDM Clip', 'Turtle Rock Area', 'East Dark Death Mountain (Top)') - yield ('Catfish DMD Clip', 'Turtle Rock Area', 'Catfish Area') - yield ('Bumper Cave Ledge Clip', 'Bumper Cave Area', 'Bumper Cave Ledge') - yield ('Bumper Cave Ledge Drop Clip', 'Bumper Cave Ledge', 'Bumper Cave Entrance') - yield ('Bumper Cave To Pond Clip', 'Bumper Cave Area', 'Outcast Pond Area') - yield ('Catfish Ledge Clip', 'Catfish Area', 'Catfish Approach Area') - - #yield ('Dark Pond DMA Clip', 'Outcast Pond Area', 'West Dark Death Mountain (Bottom)') #cannot guarantee camera correction - yield ('Pond To Bumper Cave Clip', 'Outcast Pond Area', 'Bumper Cave Area') - yield ('Pond To Chapel Clip', 'Outcast Pond Area', 'Dark Chapel Area') - yield ('Qirn Jump To Dark Witch Clip', 'Qirn Jump East Bank', 'Dark Witch Area') - yield ('Qirn Jump To Broken Bridge North Clip', 'Qirn Jump East Bank', 'Broken Bridge Northeast') - yield ('Qirn Jump To Broken Bridge Clip', 'Qirn Jump East Bank', 'Broken Bridge Area') - yield ('Dark Witch To PoD Clip', 'Dark Witch Area', 'Palace of Darkness Area') - yield ('Dark Witch To Qirn Jump Clip', 'Dark Witch Area', 'Qirn Jump East Bank') - yield ('Dark Witch To Catfish Approach Clip', 'Dark Witch Northeast', 'Catfish Approach Area') - yield ('Catfish Approach To Dark Witch Clip', 'Catfish Approach Area', 'Dark Witch Area') - yield ('Catfish Approach To PoD Clip', 'Catfish Approach Area', 'Palace of Darkness Area') - - yield ('VoO To Dig Game Clip', 'Village of Outcasts Area', 'Dig Game Area') - yield ('VoO To Dig Game Hook Clip', 'Village of Outcasts Area', 'Dig Game Ledge') #requires hookshot - yield ('Broken Bridge To Dunes Clip', 'Broken Bridge West', 'Dark Dunes Area') - yield ('Broken Bridge To Hammer Pegs Clip', 'Broken Bridge West', 'Hammer Pegs Area') #fake flipper - yield ('Broken Bridge To Bomb Shop Clip', 'Broken Bridge West', 'Big Bomb Shop Area') #fake flipper - yield ('PoD To Cliff Clip', 'Palace of Darkness Area', 'Darkness Cliff') - yield ('Dark Dunes To Cliff Clip', 'Dark Dunes Area', 'Darkness Cliff') - yield ('Dark Dunes To Hammer Pegs Clip', 'Dark Dunes Area', 'Hammer Pegs Area') - yield ('Dark Dunes To Bomb Shop Clip', 'Dark Dunes Area', 'Big Bomb Shop Area') - - yield ('Dig Game To Mire Clip', 'Dig Game Area', 'Misery Mire Area') - yield ('Archery Game To Cliff Clip', 'Archery Game Area', 'Mire Northeast Cliffs') - yield ('Dark Bonk Rocks To Cliff Clip', 'Dark Bonk Rocks Area', 'Dark Central Cliffs') - yield ('Bomb Shop To Cliff Clip', 'Big Bomb Shop Area', 'Dark Central Cliffs') - yield ('Bomb Shop To Hammer Bridge FAWT Clip', 'Big Bomb Shop Area', 'Hammer Bridge North Area') - yield ('Hammer Bridge To Bomb Shop Clip', 'Hammer Bridge North Area', 'Big Bomb Shop Area') - yield ('Hammer Bridge To Hammer Pegs Clip', 'Hammer Bridge North Area', 'Hammer Pegs Area') - yield ('Hammer Bridge To Cliff Clip', 'Hammer Bridge South Area', 'Dark Central Cliffs') - yield ('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water') #requires flippers - yield ('PoD Nook To Shopping Mall FAWT Clip', 'Palace of Darkness Nook Area', 'Shopping Mall Area') - - yield ('Mire To Cliff Clip', 'Misery Mire Area', 'Mire Northeast Cliffs') - yield ('Mire To Teleporter Clip', 'Misery Mire Area', 'Misery Mire Teleporter Ledge') - yield ('Stumpy To Cliff Clip', 'Stumpy Approach Area', 'Mire Northeast Cliffs') - yield ('Dark C Whirlpool To Cliff Clip', 'Dark C Whirlpool Area', 'Dark Central Cliffs') - yield ('Dark C Whirlpool Outer To Cliff Clip', 'Dark C Whirlpool Outer Area', 'Dark Central Cliffs') - yield ('Hype To Cliff Clip', 'Hype Cave Area', 'Dark Central Cliffs') - yield ('Ice Lake To Hype Clip', 'Ice Lake Area', 'Hype Cave Area') - yield ('Ice Lake To South Pass Clip', 'Ice Lake Area', 'Dark South Pass Area') - yield ('Ice Lake To Shore Clip', 'Ice Lake Area', 'Ice Lake Ledge (West)') - - yield ('Swamp Nook To Cliff Clip', 'Swamp Nook Area', 'Mire Northeast Cliffs') - yield ('Swamp To Cliff Clip', 'Swamp Area', 'Mire Northeast Cliffs') - yield ('South Pass To Ice Lake Clip', 'Dark South Pass Area', 'Ice Lake Area') - yield ('South Pass To Dark Shore Clip', 'Dark South Pass Area', 'Ice Lake Ledge (West)') - #yield ('Bomber Corner To Shore Clip', 'Bomber Corner Area', 'Ice Lake Ledge (East)') #map wrap hardlock risk - - if not inverted: - yield ('Ganons Tower Screen Wrap Clip', 'West Dark Death Mountain (Bottom)', 'GT Approach') # This only gets you to the GT entrance - yield ('WDDM Bomb Clip', 'West Dark Death Mountain (Bottom)', 'West Dark Death Mountain (Top)') #cannot guarantee camera correction, but a bomb clip exists - yield ('Turtle Rock Ledge Clip', 'Turtle Rock Area', 'Turtle Rock Ledge') - else: - yield ('Misery Mire Teleporter Clip', 'Misery Mire Area', 'Misery Mire Teleporter Ledge') + for name, parent_region, target_region in boots_clips_local: + if world.is_tile_swapped(OWTileRegions[parent_region], player): + yield(name, parent_region, target_region) + + for name, parent_region, target_region in boots_clips: + parent_swapped, target_swapped = get_swapped_status(world, player, parent_region, target_region) + if parent_region[0] and parent_swapped: + if target_region[0] and target_swapped: + yield(name[0], parent_region[0], target_region[0]) + elif target_region[1]: + yield(name[0], parent_region[0], target_region[1]) + elif parent_region[1]: + if target_region[0] and target_swapped: + yield(name[1], parent_region[1], target_region[0]) + elif target_region[1]: + yield(name[1], parent_region[1], target_region[1]) -def get_glitched_speed_drops_lw(inverted = False): +def get_glitched_speed_drops_lw(world, player): """ Light World drop-down ledges that require glitched speed. """ - -def get_glitched_speed_drops_dw(inverted = False): +def get_glitched_speed_drops_dw(world, player): """ Dark World drop-down ledges that require glitched speed. """ -def get_mirror_clip_spots_dw(): +def get_mirror_clip_spots(world, player): """ Out of bounds transitions using the mirror """ - yield ('Qirn Jump Bunny DMD Clip', 'East Dark Death Mountain (Bottom Left)', 'Qirn Jump Area') - yield ('EDDM Mirror Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)') - yield ('Desert East Mirror Clip', 'Misery Mire Area', 'Desert Palace Mouth') + + for name, parent_region, target_region in mirror_clips_local: + if not world.is_tile_swapped(OWTileRegions[parent_region], player): + yield(name, parent_region, target_region) + + for name, parent_region, target_region in mirror_clips: + parent_swapped, target_swapped = get_swapped_status(world, player, parent_region, target_region) + if parent_region[0] and not parent_swapped: + if target_region[0] and not target_region: + yield(name[0], parent_region[0], target_region[0]) + elif target_region[1]: + yield(name[0], parent_region[0], target_region[1]) + elif parent_region[1]: + if target_region[0] and not target_region: + yield(name[1], parent_region[1], target_region[0]) + elif target_region[1]: + yield(name[1], parent_region[1], target_region[1]) -def get_mirror_offset_spots_dw(): +def get_mirror_offset_spots(world, player): """ Mirror shenanigans placing a mirror portal with a broken camera """ - yield ('Dark Death Mountain Offset Mirror', 'West Dark Death Mountain (Bottom)', 'Pyramid Area') + + # TODO: These really should check to see if there is a mirrorless path to the mirror portal + # but being that OWG is very very open, it's very unlikely there isn't a path, but possible + + for name, parent_region, target_region, path_to in mirror_offsets: + parent_swapped, target_swapped = get_swapped_status(world, player, parent_region, target_region) + if parent_region[0] and not parent_swapped: + if target_region[0] and not target_region: + yield(name[0], parent_region[0], target_region[0]) + elif target_region[1]: + yield(name[0], parent_region[0], target_region[1]) + elif parent_region[1]: + if target_region[0] and not target_region: + yield(name[1], parent_region[1], target_region[0]) + elif target_region[1]: + yield(name[1], parent_region[1], target_region[1]) -def get_mirror_offset_spots_lw(player): - """ - Mirror shenanigans placing a mirror portal with a broken camera - """ - #yield ('Death Mountain Offset Mirror', 'West Death Mountain (Bottom)', 'Hyrule Castle Courtyard') #revisit when we can guarantee walk access to Pyramid Area - #yield ('Death Mountain Offset Mirror (Houlihan Exit)', 'West Death Mountain (Bottom)', 'Hyrule Castle Ledge', lambda state: state.has_Mirror(player) and state.can_boots_clip_dw(player) and state.has_Pearl(player)) +def get_swapped_status(world, player, parents, targets): + if parents[0]: + parent_swapped = world.is_tile_swapped(OWTileRegions[parents[0]], player) + else: + parent_swapped = world.is_tile_swapped(OWTileRegions[parents[1]], player) + + if targets[0] and targets[0] in (glitch_regions[0] + glitch_regions[1]): + target_swapped = targets[0] in glitch_regions[1] + else: + if targets[0]: + target_swapped = world.is_tile_swapped(OWTileRegions[targets[0]], player) + else: + target_swapped = world.is_tile_swapped(OWTileRegions[targets[1]], player) + return parent_swapped, target_swapped + def create_owg_connections(world, player): """ Add OWG transitions to player's world without logic """ - create_no_logic_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) - create_no_logic_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted')) + create_no_logic_connections(player, world, get_boots_clip_exits_lw(world, player)) + create_no_logic_connections(player, world, get_boots_clip_exits_dw(world, player)) # Glitched speed drops. - #create_no_logic_connections(player, world, get_glitched_speed_drops_lw(world.mode[player] == 'inverted')) - #create_no_logic_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted')) + #create_no_logic_connections(player, world, get_glitched_speed_drops_lw(world, player)) + #create_no_logic_connections(player, world, get_glitched_speed_drops_dw(world, player)) # Mirror clip spots. - if world.mode[player] != 'inverted': - create_no_logic_connections(player, world, get_mirror_clip_spots_dw()) - create_no_logic_connections(player, world, get_mirror_offset_spots_dw()) - #else: - #create_no_logic_connections(player, world, get_mirror_offset_spots_lw(player)) + create_no_logic_connections(player, world, get_mirror_clip_spots(world, player)) + create_no_logic_connections(player, world, get_mirror_offset_spots(world, player)) def overworld_glitches_rules(world, player): # Boots-accessible locations. - set_owg_rules(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: state.can_boots_clip_lw(player)) - set_owg_rules(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted'), lambda state: state.can_boots_clip_dw(player)) + set_owg_rules(player, world, get_boots_clip_exits_lw(world, player), lambda state: state.can_boots_clip_lw(player)) + set_owg_rules(player, world, get_boots_clip_exits_dw(world, player), lambda state: state.can_boots_clip_dw(player)) # Glitched speed drops. - #set_owg_rules(player, world, get_glitched_speed_drops_lw(world.mode[player] == 'inverted'), lambda state: state.can_get_glitched_speed_lw(player)) - #set_owg_rules(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: state.can_get_glitched_speed_dw(player)) + #set_owg_rules(player, world, get_glitched_speed_drops_lw(world, player), lambda state: state.can_get_glitched_speed_lw(player)) + #set_owg_rules(player, world, get_glitched_speed_drops_dw(world, player), lambda state: state.can_get_glitched_speed_dw(player)) # Mirror clip spots. - if world.mode[player] != 'inverted': - set_owg_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has_Mirror(player)) - set_owg_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player)) - #else: - #set_owg_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has_Mirror(player) and state.can_boots_clip_dw(player)) + set_owg_rules(player, world, get_mirror_clip_spots(world, player), lambda state: state.has_Mirror(player)) + set_owg_rules(player, world, get_mirror_offset_spots(world, player), lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player)) # Regions that require the boots and some other stuff. # TODO: Revisit below when we can guarantee water walk @@ -378,18 +293,9 @@ def overworld_glitches_rules(world, player): # Zora's Ledge via waterwalk setup. #add_alternate_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has_Boots(player)) #revisit when we can guarantee water walk - # Regions that can bypass item requirements - add_alternate_rule(world.get_entrance('DM Hammer Bridge (West)', player), lambda state: state.can_boots_clip_lw(player)) - add_alternate_rule(world.get_entrance('DM Broken Bridge (West)', player), lambda state: state.can_boots_clip_lw(player)) - add_alternate_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_boots_clip_lw(player)) - add_alternate_rule(world.get_entrance('Potion Shop Rock (South)', player), lambda state: state.can_boots_clip_lw(player)) - add_alternate_rule(world.get_entrance('Dark Witch Rock (North)', player), lambda state: state.can_boots_clip_dw(player)) - add_alternate_rule(world.get_entrance('Dark Witch Rock (South)', player), lambda state: state.can_boots_clip_dw(player)) - # Adding additional item requirements to OWG Clips - add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) - add_additional_rule(world.get_entrance('Desert Ledge To Cliff Clip', player), lambda state: state.can_lift_rocks(player)) add_additional_rule(world.get_entrance('VoO To Dig Game Hook Clip', player), lambda state: state.has('Hookshot', player)) + add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) add_additional_rule(world.get_entrance('Dark Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) @@ -417,3 +323,171 @@ def set_owg_rules(player, world, connections, default_rule): connection = world.get_entrance(entrance, player) rule = rule_override[0] if len(rule_override) > 0 else default_rule connection.access_rule = rule + + +glitch_regions = (['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs'], + ['Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs']) + +# same screen clips, no OWR tile swap implications +boots_clips_local = [ # (name, from_region, to_region) + ('Hera Ascent Clip', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists + ('WDDM Bomb Clip', 'West Dark Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists + ('Ganons Tower Screen Wrap Clip', 'West Dark Death Mountain (Bottom)', 'GT Approach'), # This only gets you to the GT entrance + ('Spectacle Rock Ledge Clip', 'West Death Mountain (Top)', 'Spectacle Rock Ledge'), + + ('Floating Island Clip', 'East Death Mountain (Top East)', 'Death Mountain Floating Island'), + ('Floating Island Return Clip', 'Death Mountain Floating Island', 'East Death Mountain (Top East)'), + #('DW Floating Island Clip', 'East Dark Death Mountain (Bottom)', 'Dark Death Mountain Floating Island'), #cannot guarantee camera correction + ('EDM East Dropdown Clip', 'East Death Mountain (Top East)', 'East Death Mountain (Bottom Left)'), + ('EDM Hammer Bypass Teleport', 'East Death Mountain (Top West)', 'East Death Mountain (Top East)'), + ('EDDM West Dropdown Clip', 'East Dark Death Mountain (Top)', 'East Dark Death Mountain (Bottom Left)'), + ('WDM To EDM Bottom Clip', 'East Death Mountain (Bottom Left)', 'East Death Mountain (Bottom)'), + ('WDDM To EDDM Bottom Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'), + ('TR Bridge Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge'), + + ('TR Pegs Ledge Clip', 'Death Mountain TR Pegs', 'Death Mountain TR Pegs Ledge'), + ('TR Pegs Ledge Descent Clip', 'Death Mountain TR Pegs Ledge', 'Death Mountain TR Pegs'), # inverted only, but doesn't hurt to exist always + ('Turtle Rock Ledge Clip', 'Turtle Rock Area', 'Turtle Rock Ledge'), + + ('Mountain Entry To Ledge Clip', 'Mountain Entry Area', 'Mountain Entry Ledge'), + ('Bumper Cave Ledge Clip', 'Bumper Cave Area', 'Bumper Cave Ledge'), + ('Mountain Ledge Drop Clip', 'Mountain Entry Ledge', 'Mountain Entry Entrance'), + ('Bumper Cave Ledge Drop Clip', 'Bumper Cave Ledge', 'Bumper Cave Entrance'), + + ('Potion Shop Northbound Rock Bypass Clip', 'Potion Shop Area', 'Potion Shop Northeast'), + ('Potion Shop Southbound Rock Bypass Clip', 'Potion Shop Northeast', 'Potion Shop Area'), + ('Dark Witch Northbound Rock Bypass Clip', 'Dark Witch Area', 'Dark Witch Northeast'), + ('Dark Witch Southbound Rock Bypass Clip', 'Dark Witch Northeast', 'Dark Witch Area'), + + ('Hyrule Castle To Water Clip', 'Hyrule Castle Area', 'Hyrule Castle Water'), #fake flipper + + #('Bat Cave River Clip Spot', 'Blacksmith Area', 'Bat Cave Ledge'), #cannot guarantee camera correction + + ('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize'), + + ('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water'), #requires flippers + ('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water'), #requires flippers + + ('Desert To Teleporter Clip', 'Desert Area', 'Desert Palace Teleporter Ledge'), + ('Mire To Teleporter Clip', 'Misery Mire Area', 'Misery Mire Teleporter Ledge'), + ('Desert To Bombos Tablet Clip', 'Desert Area', 'Bombos Tablet Ledge'), + + ('Lake Hylia To Shore Clip', 'Lake Hylia Area', 'Lake Hylia South Shore'), + ('Ice Lake To Shore Clip', 'Ice Lake Area', 'Ice Lake Ledge (West)') + + #('Desert Pass To Zora Clip', 'Desert Pass Area', 'Zoras Domain', ) #revisit when Zora is shuffled +] + +# Common structure for cross-screen connections: +# (name, from_region, to_region) <- each three consists of [LW, DW] +# This is so OWR Tile Swap can properly connect both connections, and simultaneously be aware of which one requires pearl +# Note: Some clips have no way to reach the OOB area, and others have no way to get from the OOB area +# to a proper destination, these are marked with 'None'; these connections will not be made +boots_clips = [ + (['Lumberjack DMA Clip', 'Dark Lumberjack DMA Clip'], ['Lumberjack Area', 'Dark Lumberjack Area'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)']), + + (['Lumberjack DMD Clip', None], ['West Death Mountain (Top)', None], ['Lumberjack Area', 'Dark Lumberjack Area']), + (['DM Glitched Bridge Clip', 'DDM Glitched Bridge Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)']), + (['WDM to EDM Top Clip', 'WDDM to EDDM Top Clip'], ['West Death Mountain (Top)', 'West Dark Death Mountain (Top)'], ['East Death Mountain (Top West)', None]), + (['Sanctuary DMD Clip', 'Chapel DMD Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Sanctuary Area', 'Dark Chapel Area']), + (['Graveyard Ledge DMD Clip', 'Dark Graveyard DMD Clip', ], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Graveyard Ledge', 'Dark Graveyard North']), + (['Kings Grave DMD Clip', 'Dark Kings Grave DMD Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Kings Grave Area', None]), + + (['EDM to WDM Top Clip', 'EDDM To WDDM Clip'], ['East Death Mountain (Top West)', 'East Dark Death Mountain (Top)'], ['West Death Mountain (Top)', 'West Dark Death Mountain (Top)']), + (['EDM To TR Pegs Clip', 'EDDM To TR Clip'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)'], ['Death Mountain TR Pegs', None]), + (['EDM DMD FAWT Clip', 'Dark Witch DMD FAWT Clip'], ['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], ['Potion Shop Area', 'Dark Witch Area']), + (['WDM DMD To River Bend Clip', 'WDDM DMD To Qirn Jump Clip'], ['East Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom Left)'], ['River Bend Area', 'Qirn Jump Area']), + (['EDM DMD To River Bend Clip', 'EDDM DMD To Qirn Jump Clip'], ['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], ['River Bend Area', 'Qirn Jump Area']), + + (['TR Pegs To EDM Clip', 'TR To EDDM Clip'], ['Death Mountain TR Pegs', 'Turtle Rock Area'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)']), + (['Zora DMD Clip', 'Catfish DMD Clip'], ['Death Mountain TR Pegs', 'Turtle Rock Area'], ['Zora Waterfall Area', 'Catfish Area']), + + (['Mountain Entry To Pond Clip', 'Bumper Cave To Pond Clip'], ['Mountain Entry Area', 'Bumper Cave Area'], ['Kakariko Pond Area', 'Outcast Pond Area']), + + (['Zora Waterfall Ledge Clip', 'Catfish Ledge Clip'], ['Zora Waterfall Area', 'Catfish Area'], ['Zora Approach Area', 'Catfish Approach Area']), + + #(['Pond DMA Clip', 'Dark Pond DMA Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)']), #cannot guarantee camera correction + (['Pond To Mountain Entry Clip', 'Pond To Bumper Cave Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Mountain Entry Area', 'Bumper Cave Area']), + (['Pond To Bonk Rocks Clip', 'Pond To Chapel Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Bonk Rock Ledge', 'Dark Chapel Area']), + + (['River Bend To Potion Shop Clip', 'Qirn Jump To Dark Witch Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], ['Potion Shop Area', 'Dark Witch Area']), + (['River Bend To Wooden Bridge Clip', 'Qirn Jump To Broken Bridge North Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], ['Wooden Bridge Area', 'Broken Bridge Northeast']), + (['River Bend To Broken Bridge Clip', 'Qirn Jump To Broken Bridge Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], [None, 'Broken Bridge Area']), + + (['Potion Shop To EP Clip', 'Dark Witch To PoD Clip'], ['Potion Shop Area', 'Dark Witch Area'], ['Eastern Palace Area', 'Palace of Darkness Area']), + (['Potion Shop To River Bend Clip', 'Dark Witch To Qirn Jump Clip'], ['Potion Shop Area', 'Dark Witch Area'], ['River Bend East Bank', 'Qirn Jump East Bank']), + (['Potion Shop To Zora Approach Clip', 'Dark Witch To Catfish Approach Clip'], ['Potion Shop Northeast', 'Dark Witch Northeast'], ['Zora Approach Area', 'Catfish Approach Area']), + + (['Zora Approach To Potion Shop Clip', 'Catfish Approach To Dark Witch Clip'], ['Zora Approach Area', 'Catfish Approach Area'], ['Potion Shop Area', 'Dark Witch Area']), + (['Zora Approach To PoD Clip', 'Catfish Approach To PoD Clip'], ['Zora Approach Area', 'Catfish Approach Area'], [None, 'Palace of Darkness Area']), + + (['Kakariko Bomb Hut Clip', 'VoO To Dig Game Clip'], ['Kakariko Southwest', 'Village of Outcasts Area'], ['Maze Race Area', 'Dig Game Area']), + (['Kakariko To Dig Game Hook Clip', 'VoO To Dig Game Hook Clip'], ['Kakariko Southwest', 'Village of Outcasts Area'], [None, 'Dig Game Ledge']), #requires hookshot + + (['Forgotten Forest To Blacksmith Clip', None], ['Forgotten Forest Area', None], ['Hyrule Castle Water', 'Pyramid Water']), #fake flipper + + (['Wooden Bridge To Dunes Clip', 'Broken Bridge To Dunes Clip'], ['Wooden Bridge Area', 'Broken Bridge West'], ['Sand Dunes Area', 'Dark Dunes Area']), + (['Wooden Bridge To Water Clip', 'Broken Bridge To Water Clip'], ['Wooden Bridge Area', 'Broken Bridge West'], [None, 'Pyramid Water']), #fake flipper + + (['Eastern Palace To Zora Approach Clip', None], ['Eastern Palace Area', None], ['Zora Approach Area', 'Catfish Approach Area']), + (['Eastern Palace To Nook Clip', None], ['Eastern Palace Area', None], ['Eastern Nook Area', 'Palace of Darkness Nook Area']), + (['Eastern Palace To Cliff Clip', 'PoD To Cliff Clip'], ['Eastern Palace Area', 'Palace of Darkness Area'], ['Eastern Cliff', 'Darkness Cliff']), + + (['Sand Dunes To Cliff Clip', 'Dark Dunes To Cliff Clip'], ['Sand Dunes Area', 'Dark Dunes Area'], ['Eastern Cliff', 'Darkness Cliff']), + (['Sand Dunes To Water Clip', 'Dark Dunes To Water Clip'], ['Sand Dunes Area', 'Dark Dunes Area'], [None, 'Pyramid Water']), #fake flipper + + (['Maze Race To Desert Ledge Clip', 'Dig Game To Mire Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Ledge', 'Misery Mire Area']), + (['Maze Race To Desert Boss Clip', 'Dig Game To Desert Boss Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Palace Entrance (North) Spot', None]), + (['Suburb To Cliff Clip', 'Archery Game To Cliff Clip'], ['Kakariko Suburb Area', 'Archery Game Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Central Bonk Rocks To Cliff Clip', 'Dark Bonk Rocks To Cliff Clip'], ['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], ['Central Cliffs', 'Dark Central Cliffs']), + (['Links House To Cliff Clip', 'Bomb Shop To Cliff Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Central Cliffs', 'Dark Central Cliffs']), + (['Stone Bridge To Cliff Clip', 'Hammer Bridge To Cliff Clip'], ['Stone Bridge Area', 'Hammer Bridge South Area'], ['Central Cliffs', 'Dark Central Cliffs']), + (['Eastern Nook To Eastern Clip', None], ['Eastern Nook Area', None], ['Eastern Palace Area', 'Palace of Darkness Area']), + (['Eastern Nook To Ice Cave FAWT Clip', 'PoD Nook To Shopping Mall FAWT Clip'], ['Eastern Nook Area', 'Palace of Darkness Nook Area'], ['Ice Cave Area', 'Shopping Mall Area']), + + (['Links To Bridge FAWT Clip', 'Bomb Shop To Hammer Bridge FAWT Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Stone Bridge Area', 'Hammer Bridge North Area']), #fake flipper + + (['Stone Bridge To Water Clip', 'Hammer Bridge To Water Clip'], ['Stone Bridge Area', 'Hammer Bridge North Area'], [None, 'Pyramid Water']), #fake flipper + + (['Desert To Maze Race Clip', None], ['Desert Ledge', None], ['Maze Race Area', 'Dig Game Area']), + (['Desert To Cliff Clip', 'Mire To Cliff Clip'], ['Desert Area', 'Misery Mire Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + + (['Flute Boy To Cliff Clip', 'Stumpy To Cliff Clip'], ['Flute Boy Approach Area', 'Stumpy Approach Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Cave 45 To Cliff Clip', None], ['Cave 45 Ledge', None], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + + (['C Whirlpool To Cliff Clip', 'Dark C Whirlpool To Cliff Clip'], ['C Whirlpool Area', 'Dark C Whirlpool Area'], ['Central Cliffs', 'Dark Central Cliffs']), + (['C Whirlpool Outer To Cliff Clip', 'Dark C Whirlpool Outer To Cliff Clip'], ['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], ['Central Cliffs', 'Dark Central Cliffs']), + + (['Statues To Cliff Clip', 'Hype To Cliff Clip'], ['Statues Area', 'Hype Cave Area'], ['Central Cliffs', 'Dark Central Cliffs']), + + (['Lake Hylia To Statues Clip', 'Ice Lake To Hype Clip'], ['Lake Hylia Area', 'Ice Lake Area'], ['Statues Area', 'Hype Cave Area']), + (['Lake Hylia To South Pass Clip', 'Ice Lake To South Pass Clip'], ['Lake Hylia Area', 'Ice Lake Area'], ['South Pass Area', 'Dark South Pass Area']), + + (['Desert Pass To Cliff Clip', 'Swamp Nook To Cliff Clip'], ['Desert Pass Area', 'Swamp Nook Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Desert Pass Southeast To Cliff Clip', None], ['Desert Pass Southeast', None], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + + (['Dam To Cliff Clip', 'Swamp To Cliff Clip'], ['Dam Area', 'Swamp Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), + (['Dam To Desert Pass Southeast Clip', 'Swamp To Desert Pass Southeast Clip'], ['Dam Area', 'Swamp Area'], ['Desert Pass Southeast', None]), + + (['South Pass To Lake Hylia Clip', 'South Pass To Ice Lake Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia Area', 'Ice Lake Area']), + (['South Pass To Shore Clip', 'South Pass To Dark Shore Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia South Shore', 'Ice Lake Ledge (West)']), + #(['Octoballoon To Shore Clip', 'Bomber Corner To Shore Clip'], ['Octoballoon Area', 'Bomber Corner Area'], ['Lake Hylia South Shore', 'Ice Lake Ledge (East)']), #map wrap hardlock risk + + (['HC Water To Blacksmith Clip', 'Pyramid Water To Hammerpegs Clip'], ['Hyrule Castle Water', 'Pyramid Water'], ['Blacksmith Area', 'Hammer Pegs Area']), #TODO: THIS IS NOT A BOOTS CLIP, this is a normal connection that needs to occur somewhere + ([None, 'Pyramid Water To Bomb Shop Clip'], [None, 'Pyramid Water'], ['Links House Area', 'Big Bomb Shop Area']) #TODO: THIS IS NOT A BOOTS CLIP, this is a normal connection that needs to occur somewhere +] + +mirror_clips_local = [ + ('Desert East Mirror Clip', 'Misery Mire Area', 'Desert Palace Mouth'), + ('EDDM Mirror Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'), + ('EDDM Mirror Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge') +] + +mirror_clips = [ + ([None, 'Qirn Jump Bunny DMD Clip'], [None, 'East Dark Death Mountain (Bottom Left)'], ['River Bend Area', 'Qirn Jump Area']) +] + +mirror_offsets = [ + (['DM Offset Mirror', 'DDM Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Courtyard', 'Pyramid Area'], ['Pyramid Area', 'Hyrule Castle Courtyard']), + (['DM To HC Ledge Offset Mirror', 'DDM To HC Ledge Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', None], ['Pyramid Area', None]) +] \ No newline at end of file diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d0ea7fec..571b37ee 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1340,58 +1340,15 @@ mandatory_connections = [# Intra-tile OW Connections ('Bomber Corner Waterfall Water Drop', 'Bomber Corner Water'), #flippers ('Bomber Corner Pier', 'Bomber Corner Area'), - # OWG Connections - ('Sand Dunes Ledge Drop', 'Sand Dunes Area'), - ('Stone Bridge East Ledge Drop', 'Stone Bridge Area'), - ('Tree Line Ledge Drop', 'Tree Line Area'), - ('Eastern Palace Ledge Drop', 'Eastern Palace Area'), - - ('Links House Cliff Ledge Drop', 'Links House Area'), - ('Central Bonk Rocks Cliff Ledge Drop', 'Central Bonk Rocks Area'), - ('Stone Bridge Cliff Ledge Drop', 'Stone Bridge Area'), - ('Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Area'), - ('C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), - ('C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Outer Area'), - ('South Teleporter Cliff Ledge Drop', 'Dark Central Cliffs'), - ('Statues Cliff Ledge Drop', 'Statues Area'), - ('Lake Hylia Island FAWT Ledge Drop', 'Lake Hylia Island'), + # OWG In-Bounds Connections ('Stone Bridge EC Cliff Water Drop', 'Stone Bridge Water'), #fake flipper - ('Tree Line WC Cliff Water Drop', 'Tree Line Water'), #fake flipper - - ('Desert Boss Cliff Ledge Drop', 'Desert Palace Entrance (North) Spot'), - ('Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge'), - ('Suburb Cliff Ledge Drop', 'Kakariko Suburb Area'), - ('Cave 45 Cliff Ledge Drop', 'Cave 45 Ledge'), - ('Desert Pass Cliff Ledge Drop', 'Desert Pass Area'), - ('Desert Pass Southeast Cliff Ledge Drop', 'Desert Pass Southeast'), - ('Desert C Whirlpool Cliff Ledge Drop', 'C Whirlpool Outer Area'), - ('Dam Cliff Ledge Drop', 'Dam Area'), + ('Tree Line WC Cliff Water Drop', 'Tree Line Water'), #fake flipper, - ('Dark Dunes Ledge Drop', 'Dark Dunes Area'), - ('Hammer Bridge North Ledge Drop', 'Hammer Bridge North Area'), - ('Dark Tree Line Ledge Drop', 'Dark Tree Line Area'), - ('Palace of Darkness Ledge Drop', 'Palace of Darkness Area'), - - ('Mire Cliff Ledge Drop', 'Misery Mire Area'), - ('Archery Game Cliff Ledge Drop', 'Archery Game Area'), - ('Stumpy Approach Cliff Ledge Drop', 'Stumpy Approach Area'), - ('Swamp Nook Cliff Ledge Drop', 'Swamp Nook Area'), - ('Mire C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), - ('Swamp Cliff Ledge Drop', 'Swamp Area'), - - ('Bomb Shop Cliff Ledge Drop', 'Big Bomb Shop Area'), - ('Dark Bonk Rocks Cliff Ledge Drop', 'Dark Bonk Rocks Area'), - ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area'), - ('Ice Lake Area Cliff Ledge Drop', 'Ice Lake Area'), - ('Ice Lake Northeast Pier Hop', 'Ice Lake Northeast Bank'), - ('Ice Lake Moat Bomb Jump', 'Ice Lake Moat'), - ('Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), - ('Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), - ('Hype Cliff Ledge Drop', 'Hype Cave Area'), - ('Ice Palace Island FAWT Ledge Drop', 'Ice Lake Moat'), ('Hammer Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), #fake flipper - ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') #fake flipper - ] + ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water'), #fake flipper + ('Ice Lake Northeast Pier Hop', 'Ice Lake Northeast Bank'), + ('Ice Lake Moat Bomb Jump', 'Ice Lake Moat') + ] default_whirlpool_connections = [ ((0x33, 'C Whirlpool', 'C Whirlpool Water'), (0x15, 'River Bend Whirlpool', 'River Bend Water')), @@ -1594,9 +1551,13 @@ ow_connections = { ('Broken Bridge Northeast Mirror Spot', 'Broken Bridge Northeast') ]), 0x1e: ([ - ('Eastern Palace Mirror Spot', 'Eastern Palace Area') + ('Eastern Palace Mirror Spot', 'Eastern Palace Area'), + ('Eastern Palace Ledge Drop', 'Eastern Palace Area'), # OWG + ('Palace of Darkness Ledge Drop', 'Palace of Darkness Area') # OWG ], [ - ('Palace of Darkness Mirror Spot', 'Palace of Darkness Area') + ('Palace of Darkness Mirror Spot', 'Palace of Darkness Area'), + ('Eastern Palace Ledge Drop', 'Palace of Darkness Area'), # OWG + ('Palace of Darkness Ledge Drop', 'Eastern Palace Area') # OWG ]), 0x22: ([ ('Blacksmith Mirror Spot', 'Blacksmith Area'), @@ -1607,9 +1568,13 @@ ow_connections = { ('Hammer Pegs Entry Mirror Spot', 'Hammer Pegs Entry') ]), 0x25: ([ - ('Sand Dunes Mirror Spot', 'Sand Dunes Area') + ('Sand Dunes Mirror Spot', 'Sand Dunes Area'), + ('Sand Dunes Ledge Drop', 'Sand Dunes Area'), # OWG + ('Dark Dunes Ledge Drop', 'Dark Dunes Area') # OWG ], [ - ('Dark Dunes Mirror Spot', 'Dark Dunes Area') + ('Dark Dunes Mirror Spot', 'Dark Dunes Area'), + ('Sand Dunes Ledge Drop', 'Dark Dunes Area'), # OWG + ('Dark Dunes Ledge Drop', 'Sand Dunes Area') # OWG ]), 0x28: ([ ('Maze Race Mirror Spot', 'Maze Race Ledge'), @@ -1620,11 +1585,15 @@ ow_connections = { ]), 0x29: ([ ('Kakariko Suburb Mirror Spot', 'Kakariko Suburb Area'), - ('Kakariko Suburb South Mirror Spot', 'Kakariko Suburb Area') + ('Kakariko Suburb South Mirror Spot', 'Kakariko Suburb Area'), + ('Suburb Cliff Ledge Drop', 'Kakariko Suburb Area'), # OWG + ('Archery Game Cliff Ledge Drop', 'Archery Game Area') # OWG ], [ ('Frog Mirror Spot', 'Frog Area'), ('Frog Prison Mirror Spot', 'Frog Prison'), - ('Archery Game Mirror Spot', 'Archery Game Area') + ('Archery Game Mirror Spot', 'Archery Game Area'), + ('Suburb Cliff Ledge Drop', 'Archery Game Area'), # OWG + ('Archery Game Cliff Ledge Drop', 'Kakariko Suburb Area') # OWG ]), 0x2a: ([ ('Flute Boy Mirror Spot', 'Flute Boy Area'), @@ -1634,28 +1603,48 @@ ow_connections = { ('Stumpy Pass Mirror Spot', 'Stumpy Pass') ]), 0x2b: ([ - ('Central Bonk Rocks Mirror Spot', 'Central Bonk Rocks Area') + ('Central Bonk Rocks Mirror Spot', 'Central Bonk Rocks Area'), + ('Central Bonk Rocks Cliff Ledge Drop', 'Central Bonk Rocks Area'), # OWG + ('Dark Bonk Rocks Cliff Ledge Drop', 'Dark Bonk Rocks Area') # OWG ], [ - ('Dark Bonk Rocks Mirror Spot', 'Dark Bonk Rocks Area') + ('Dark Bonk Rocks Mirror Spot', 'Dark Bonk Rocks Area'), + ('Central Bonk Rocks Cliff Ledge Drop', 'Dark Bonk Rocks Area'), # OWG + ('Dark Bonk Rocks Cliff Ledge Drop', 'Central Bonk Rocks Area') # OWG ]), 0x2c: ([ - ('Links House Mirror Spot', 'Links House Area') + ('Links House Mirror Spot', 'Links House Area'), + ('Links House Cliff Ledge Drop', 'Links House Area'), # OWG + ('Bomb Shop Cliff Ledge Drop', 'Big Bomb Shop Area') # OWG ], [ - ('Big Bomb Shop Mirror Spot', 'Big Bomb Shop Area') + ('Big Bomb Shop Mirror Spot', 'Big Bomb Shop Area'), + ('Links House Cliff Ledge Drop', 'Big Bomb Shop Area'), # OWG + ('Bomb Shop Cliff Ledge Drop', 'Links House Area') # OWG ]), 0x2d: ([ ('Stone Bridge Mirror Spot', 'Stone Bridge Area'), ('Stone Bridge South Mirror Spot', 'Stone Bridge Area'), - ('Hobo Mirror Spot', 'Stone Bridge Water') + ('Hobo Mirror Spot', 'Stone Bridge Water'), + ('Stone Bridge East Ledge Drop', 'Stone Bridge Area'), # OWG + ('Hammer Bridge North Ledge Drop', 'Hammer Bridge North Area'), # OWG + ('Stone Bridge Cliff Ledge Drop', 'Stone Bridge Area'), # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area') # OWG ], [ ('Hammer Bridge North Mirror Spot', 'Hammer Bridge North Area'), ('Hammer Bridge South Mirror Spot', 'Hammer Bridge South Area'), - ('Dark Hobo Mirror Spot', 'Hammer Bridge Water') + ('Dark Hobo Mirror Spot', 'Hammer Bridge Water'), + ('Stone Bridge East Ledge Drop', 'Hammer Bridge North Area'), # OWG + ('Hammer Bridge North Ledge Drop', 'Stone Bridge Area'), # OWG + ('Stone Bridge Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge Area') # OWG ]), 0x2e: ([ - ('Tree Line Mirror Spot', 'Tree Line Area') + ('Tree Line Mirror Spot', 'Tree Line Area'), + ('Tree Line Ledge Drop', 'Tree Line Area'), # OWG + ('Dark Tree Line Ledge Drop', 'Dark Tree Line Area') # OWG ], [ - ('Dark Tree Line Mirror Spot', 'Dark Tree Line Area') + ('Dark Tree Line Mirror Spot', 'Dark Tree Line Area'), + ('Tree Line Ledge Drop', 'Dark Tree Line Area'), # OWG + ('Dark Tree Line Ledge Drop', 'Tree Line Area') # OWG ]), 0x2f: ([ ('Eastern Nook Mirror Spot', 'Eastern Nook Area'), @@ -1671,7 +1660,10 @@ ow_connections = { ('DP Stairs Mirror Spot', 'Desert Palace Stairs'), ('DP Entrance (North) Mirror Spot', 'Desert Palace Entrance (North) Spot'), ('Bombos Tablet Ledge Mirror Spot', 'Bombos Tablet Ledge'), - ('Desert Teleporter', 'Misery Mire Teleporter Ledge') + ('Desert Teleporter', 'Misery Mire Teleporter Ledge'), + ('Desert Boss Cliff Ledge Drop', 'Desert Palace Entrance (North) Spot'), # OWG + ('Mire Cliff Ledge Drop', 'Misery Mire Area'), # OWG + ('Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge') # OWG ], [ ('Checkerboard Ledge Approach', 'Desert Checkerboard Ledge'), ('Checkerboard Ledge Leave', 'Desert Area'), @@ -1679,32 +1671,56 @@ ow_connections = { ('Misery Mire Ledge Mirror Spot', 'Misery Mire Area'), ('Misery Mire Blocked Mirror Spot', 'Misery Mire Area'), ('Misery Mire Main Mirror Spot', 'Misery Mire Area'), - ('Misery Mire Teleporter', 'Desert Palace Teleporter Ledge') + ('Misery Mire Teleporter', 'Desert Palace Teleporter Ledge'), + ('Desert Boss Cliff Ledge Drop', 'Misery Mire Area'), # OWG + ('Mire Cliff Ledge Drop', 'Desert Palace Entrance (North) Spot'), # OWG + ('Dark Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge') # OWG ]), 0x32: ([ ('Cave 45 Ledge Drop', 'Flute Boy Approach Area'), ('Flute Boy Entry Mirror Spot', 'Flute Boy Bush Entry'), - ('Cave 45 Mirror Spot', 'Cave 45 Ledge') + ('Cave 45 Mirror Spot', 'Cave 45 Ledge'), + ('Cave 45 Cliff Ledge Drop', 'Cave 45 Ledge'), # OWG + ('Stumpy Approach Cliff Ledge Drop', 'Stumpy Approach Area') # OWG ], [ ('Cave 45 Inverted Leave', 'Flute Boy Approach Area'), ('Cave 45 Inverted Approach', 'Cave 45 Ledge'), ('Stumpy Approach Mirror Spot', 'Stumpy Approach Area'), - ('Stumpy Bush Entry Mirror Spot', 'Stumpy Approach Bush Entry') + ('Stumpy Bush Entry Mirror Spot', 'Stumpy Approach Bush Entry'), + ('Cave 45 Cliff Ledge Drop', 'Stumpy Approach Area'), # OWG + ('Stumpy Approach Cliff Ledge Drop', 'Cave 45 Ledge') # OWG ]), 0x33: ([ ('C Whirlpool Mirror Spot', 'C Whirlpool Area'), ('C Whirlpool Outer Mirror Spot', 'C Whirlpool Outer Area'), - ('South Hyrule Teleporter', 'Dark C Whirlpool Area') + ('South Hyrule Teleporter', 'Dark C Whirlpool Area'), + ('South Teleporter Cliff Ledge Drop', 'Dark Central Cliffs'), # OWG only, needs glove + ('C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), # OWG + ('Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), # OWG + ('C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG + ('Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG + ('Desert C Whirlpool Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG + ('Mire C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Outer Area') # OWG ], [ ('Dark C Whirlpool Mirror Spot', 'Dark C Whirlpool Area'), ('Dark C Whirlpool Outer Mirror Spot', 'Dark C Whirlpool Outer Area'), ('South Dark World Teleporter', 'C Whirlpool Area'), - ('Dark South Teleporter Cliff Ledge Drop', 'Central Cliffs') #OWG only, needs glove + ('Dark South Teleporter Cliff Ledge Drop', 'Central Cliffs'), # OWG, needs glove/pearl + ('C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), # OWG + ('Dark C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), # OWG + ('C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG + ('Dark C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG + ('Desert C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG + ('Mire C Whirlpool Cliff Ledge Drop', 'C Whirlpool Outer Area') # OWG ]), 0x34: ([ - ('Statues Mirror Spot', 'Statues Area') + ('Statues Mirror Spot', 'Statues Area'), + ('Statues Cliff Ledge Drop', 'Statues Area'), # OWG + ('Hype Cliff Ledge Drop', 'Hype Cave Area') # OWG ], [ - ('Hype Cave Mirror Spot', 'Hype Cave Area') + ('Hype Cave Mirror Spot', 'Hype Cave Area'), + ('Statues Cliff Ledge Drop', 'Hype Cave Area'), # OWG + ('Hype Cliff Ledge Drop', 'Statues Area') # OWG ]), 0x35: ([ ('Lake Hylia Mirror Spot', 'Lake Hylia Area'), @@ -1715,7 +1731,12 @@ ow_connections = { ('Lake Hylia Central Island Mirror Spot', 'Lake Hylia Central Island'), ('Lake Hylia Water Mirror Spot', 'Lake Hylia Water'), ('Lake Hylia Water D Mirror Spot', 'Lake Hylia Water D'), - ('Lake Hylia Teleporter', 'Ice Palace Area') + ('Lake Hylia Teleporter', 'Ice Palace Area'), + #('Ice Palace Ledge Drop', 'Ice Lake Moat'), + ('Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Area'), # OWG + ('Ice Lake Area Cliff Ledge Drop', 'Ice Lake Area'), # OWG + ('Lake Hylia Island FAWT Ledge Drop', 'Lake Hylia Island'), # OWG + ('Ice Palace Island FAWT Ledge Drop', 'Ice Lake Moat') # OWG ], [ ('Lake Hylia Island Pier', 'Lake Hylia Island'), ('Ice Lake Mirror Spot', 'Ice Lake Area'), @@ -1724,7 +1745,11 @@ ow_connections = { ('Ice Lake Northeast Mirror Spot', 'Ice Lake Northeast Bank'), ('Ice Palace Mirror Spot', 'Ice Palace Area'), ('Ice Lake Moat Mirror Spot', 'Ice Lake Moat'), - ('Ice Palace Teleporter', 'Lake Hylia Water D') + ('Ice Palace Teleporter', 'Lake Hylia Water D'), + ('Lake Hylia Area Cliff Ledge Drop', 'Ice Lake Area'), # OWG + ('Ice Lake Area Cliff Ledge Drop', 'Lake Hylia Area'), # OWG + ('Lake Hylia Island FAWT Ledge Drop', 'Ice Lake Moat'), # OWG + ('Ice Palace Island FAWT Ledge Drop', 'Lake Hylia Island') # OWG ]), 0x37: ([ ('Ice Cave Mirror Spot', 'Ice Cave Area') @@ -1733,18 +1758,26 @@ ow_connections = { ]), 0x3a: ([ ('Desert Pass Ledge Mirror Spot', 'Desert Pass Ledge'), - ('Desert Pass Mirror Spot', 'Desert Pass Area') + ('Desert Pass Mirror Spot', 'Desert Pass Area'), + ('Desert Pass Cliff Ledge Drop', 'Desert Pass Area'), # OWG + ('Swamp Nook Cliff Ledge Drop', 'Swamp Nook Area') # OWG ], [ ('Desert Pass Ladder (North)', 'Desert Pass Area'), ('Desert Pass Ladder (South)', 'Desert Pass Ledge'), ('Swamp Nook Mirror Spot', 'Swamp Nook Area'), ('Swamp Nook Southeast Mirror Spot', 'Swamp Nook Area'), - ('Swamp Nook Pegs Mirror Spot', 'Swamp Nook Area') + ('Swamp Nook Pegs Mirror Spot', 'Swamp Nook Area'), + ('Desert Pass Cliff Ledge Drop', 'Swamp Nook Area'), # OWG + ('Swamp Nook Cliff Ledge Drop', 'Desert Pass Area') # OWG ]), 0x3b: ([ - ('Dam Mirror Spot', 'Dam Area') + ('Dam Mirror Spot', 'Dam Area'), + ('Dam Cliff Ledge Drop', 'Dam Area'), # OWG + ('Swamp Cliff Ledge Drop', 'Swamp Area') # OWG ], [ - ('Swamp Mirror Spot', 'Swamp Area') + ('Swamp Mirror Spot', 'Swamp Area'), + ('Dam Cliff Ledge Drop', 'Swamp Area'), # OWG + ('Swamp Cliff Ledge Drop', 'Dam Area') # OWG ]), 0x3c: ([ ('South Pass Mirror Spot', 'South Pass Area') diff --git a/Regions.py b/Regions.py index 0a463e98..a59f24ed 100644 --- a/Regions.py +++ b/Regions.py @@ -67,6 +67,7 @@ def create_regions(world, player): create_lw_region(player, 'Hyrule Castle Courtyard Northeast', None, ['Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Secret Entrance Stairs', 'Pyramid Uncle Mirror Spot']), create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Ledge Drop', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Entrance', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Entrance (East)', 'Inverted Pyramid Hole', 'Pyramid From Ledge Mirror Spot'], 'the castle rampart'), create_lw_region(player, 'Hyrule Castle East Entry', None, ['Hyrule Castle Outer East Rock', 'Pyramid Entry Mirror Spot', 'Hyrule Castle ES']), + create_dw_region(player, 'Hyrule Castle Water', None, ['Blacksmith Water Exit'], 'Dark World', Terrain.Water), create_lw_region(player, 'Wooden Bridge Area', None, ['Wooden Bridge Bush (South)', 'Wooden Bridge Water Drop', 'Broken Bridge West Mirror Spot', 'Broken Bridge East Mirror Spot', 'Wooden Bridge NW', 'Wooden Bridge SW']), create_lw_region(player, 'Wooden Bridge Northeast', None, ['Wooden Bridge Bush (North)', 'Wooden Bridge Northeast Water Drop', 'Broken Bridge Northeast Mirror Spot', 'Wooden Bridge NE']), create_lw_region(player, 'Wooden Bridge Water', None, ['Wooden Bridge NC'], 'Light World', Terrain.Water), @@ -97,7 +98,7 @@ def create_regions(world, player): create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)', 'Misery Mire Main Mirror Spot'], 'a sandy vista'), create_lw_region(player, 'Desert Palace Mouth', None, ['Desert Mouth Drop', 'Desert Palace Entrance (East)']), create_lw_region(player, 'Desert Palace Teleporter Ledge', None, ['Desert Teleporter Drop', 'Desert Teleporter']), - create_lw_region(player, 'Desert Northeast Cliffs', None, ['Desert Boss Cliff Ledge Drop', 'Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', 'Desert Pass Southeast Cliff Ledge Drop', 'Dam Cliff Ledge Drop']), + create_lw_region(player, 'Desert Northeast Cliffs', None, ['Desert Boss Cliff Ledge Drop', 'Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', 'Dam Cliff Ledge Drop']), create_lw_region(player, 'Bombos Tablet Ledge', ['Bombos Tablet'], ['Bombos Tablet Drop', 'Desert EC']), create_lw_region(player, 'Flute Boy Approach Area', None, ['Flute Boy Bush (South)', 'Cave 45 Inverted Approach', 'Stumpy Approach Mirror Spot', 'Flute Boy Approach NW', 'Flute Boy Approach EC']), create_lw_region(player, 'Flute Boy Bush Entry', None, ['Flute Boy Bush (North)', 'Stumpy Bush Entry Mirror Spot', 'Flute Boy Approach NC']), @@ -173,6 +174,7 @@ def create_regions(world, player): create_dw_region(player, 'Pyramid Crack', ['Pyramid Crack'], None), create_dw_region(player, 'Pyramid Exit Ledge', None, ['Pyramid Exit Ledge Drop', 'HC Courtyard Left Mirror Spot', 'Pyramid Entrance']), create_dw_region(player, 'Pyramid Pass', None, ['Post Aga Inverted Teleporter', 'HC Area South Mirror Spot', 'Pyramid SW', 'Pyramid SE']), + create_dw_region(player, 'Pyramid Water', None, ['Hammerpegs Water Exit', 'Big Bomb Shop Water Exit'], 'Dark World', Terrain.Water), create_dw_region(player, 'Broken Bridge Area', None, ['Broken Bridge Hammer Rock (South)', 'Broken Bridge Water Drop', 'Wooden Bridge Mirror Spot', 'Broken Bridge SW']), create_dw_region(player, 'Broken Bridge Northeast', None, ['Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', 'Broken Bridge Northeast Water Drop', 'Wooden Bridge Northeast Mirror Spot', 'Broken Bridge NE']), create_dw_region(player, 'Broken Bridge West', None, ['Broken Bridge West Water Drop', 'Wooden Bridge West Mirror Spot', 'Broken Bridge NW']), @@ -201,7 +203,7 @@ def create_regions(world, player): create_dw_region(player, 'Palace of Darkness Nook Area', None, ['East Dark World Hint', 'East Dark World Teleporter', 'Eastern Nook Mirror Spot', 'Palace of Darkness Nook NE']), create_dw_region(player, 'Misery Mire Area', None, ['Mire Shed', 'Misery Mire', 'Dark Desert Fairy', 'Dark Desert Hint', 'Desert Mirror Spot', 'Desert Ledge Mirror Spot', 'Checkerboard Mirror Spot', 'DP Stairs Mirror Spot', 'DP Entrance (North) Mirror Spot']), create_dw_region(player, 'Misery Mire Teleporter Ledge', None, ['Misery Mire Teleporter Ledge Drop', 'Misery Mire Teleporter']), - create_dw_region(player, 'Mire Northeast Cliffs', None, ['Mire Cliff Ledge Drop', 'Archery Game Cliff Ledge Drop', 'Stumpy Approach Cliff Ledge Drop', 'Mire C Whirlpool Cliff Ledge Drop', 'Swamp Nook Cliff Ledge Drop', 'Swamp Cliff Ledge Drop', 'Bombos Tablet Ledge Mirror Spot']), + create_dw_region(player, 'Mire Northeast Cliffs', None, ['Mire Cliff Ledge Drop', 'Dark Checkerboard Cliff Ledge Drop', 'Archery Game Cliff Ledge Drop', 'Stumpy Approach Cliff Ledge Drop', 'Mire C Whirlpool Cliff Ledge Drop', 'Swamp Nook Cliff Ledge Drop', 'Swamp Cliff Ledge Drop', 'Bombos Tablet Ledge Mirror Spot']), create_dw_region(player, 'Stumpy Approach Area', None, ['Stumpy Approach Bush (South)', 'Cave 45 Mirror Spot', 'Stumpy Approach NW', 'Stumpy Approach EC']), create_dw_region(player, 'Stumpy Approach Bush Entry', None, ['Stumpy Approach Bush (North)', 'Flute Boy Entry Mirror Spot', 'Stumpy Approach NC']), create_dw_region(player, 'Dark C Whirlpool Area', None, ['Dark C Whirlpool Rock (Bottom)', 'South Dark World Teleporter', 'C Whirlpool Mirror Spot', 'Dark C Whirlpool Water Entry', 'Dark C Whirlpool EN', 'Dark C Whirlpool ES', 'Dark C Whirlpool SC']), From 75462519116fd2c3a335c233044bccd6507ac4fa Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 17:48:25 -0500 Subject: [PATCH 13/59] Added Mixed OWG Logic --- EntranceShuffle.py | 3 ++- OWEdges.py | 2 -- OverworldGlitchRules.py | 9 ++++++++- OverworldShuffle.py | 6 +++--- Regions.py | 4 ++-- Rules.py | 2 -- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 96bc31d2..23d90597 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1508,7 +1508,8 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe for region_name in inaccessible_regions.copy(): region = world.get_region(region_name, player) if region.type not in [RegionType.LightWorld, RegionType.DarkWorld] or not any((not exit.connected_region and exit.spot_type == 'Entrance') for exit in region.exits) \ - or (region_name == 'Pyramid Exit Ledge' and world.shuffle[player] != 'insanity' or world.is_tile_swapped(0x1b, player)): + or (region_name == 'Pyramid Exit Ledge' and world.shuffle[player] != 'insanity' or world.is_tile_swapped(0x1b, player)) \ + or region_name in ['Hyrule Castle Water', 'Pyramid Water']: inaccessible_regions.remove(region_name) elif region.type == (RegionType.LightWorld if not invFlag else RegionType.DarkWorld): must_exit_regions.append(region_name) diff --git a/OWEdges.py b/OWEdges.py index 9a6a52cb..98736c08 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1481,7 +1481,6 @@ OWExitTypes = { 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', - 'South Teleporter Cliff Ledge Drop', 'Statues Cliff Ledge Drop', 'Desert Ledge Drop', 'Checkerboard Ledge Drop', @@ -1538,7 +1537,6 @@ OWExitTypes = { 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Hype Cliff Ledge Drop', - 'Dark South Teleporter Cliff Ledge Drop', 'Misery Mire Teleporter Ledge Drop', 'Mire Cliff Ledge Drop', 'Dark Checkerboard Cliff Ledge Drop', diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 291295c6..58f49b8f 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -297,7 +297,12 @@ def overworld_glitches_rules(world, player): add_additional_rule(world.get_entrance('VoO To Dig Game Hook Clip', player), lambda state: state.has('Hookshot', player)) add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) add_additional_rule(world.get_entrance('Dark Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) - + if not world.is_tile_swapped(0x33, player): + add_additional_rule(world.get_entrance('South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + world.get_entrance('Dark South Teleporter Cliff Ledge Drop', player).access_rule = lambda state: False + else: + add_additional_rule(world.get_entrance('Dark South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + world.get_entrance('South Teleporter Cliff Ledge Drop', player).access_rule = lambda state: False def add_alternate_rule(entrance, rule): old_rule = entrance.access_rule @@ -314,6 +319,7 @@ def create_no_logic_connections(player, world, connections): parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) connection = Entrance(player, entrance, parent) + connection.spot_type = 'Ledge' parent.exits.append(connection) connection.connect(target) @@ -457,6 +463,7 @@ boots_clips = [ (['C Whirlpool To Cliff Clip', 'Dark C Whirlpool To Cliff Clip'], ['C Whirlpool Area', 'Dark C Whirlpool Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['C Whirlpool Outer To Cliff Clip', 'Dark C Whirlpool Outer To Cliff Clip'], ['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], ['Central Cliffs', 'Dark Central Cliffs']), + (['South Teleporter Cliff Ledge Drop', 'Dark South Teleporter Cliff Ledge Drop'], ['C Whirlpool Area', 'Dark C Whirlpool Area'], ['Dark Central Cliffs', 'Central Cliffs']), # glove/pearl (['Statues To Cliff Clip', 'Hype To Cliff Clip'], ['Statues Area', 'Hype Cave Area'], ['Central Cliffs', 'Dark Central Cliffs']), diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 571b37ee..f42f156d 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1694,7 +1694,6 @@ ow_connections = { ('C Whirlpool Mirror Spot', 'C Whirlpool Area'), ('C Whirlpool Outer Mirror Spot', 'C Whirlpool Outer Area'), ('South Hyrule Teleporter', 'Dark C Whirlpool Area'), - ('South Teleporter Cliff Ledge Drop', 'Dark Central Cliffs'), # OWG only, needs glove ('C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), # OWG ('Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), # OWG ('C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG @@ -1705,7 +1704,6 @@ ow_connections = { ('Dark C Whirlpool Mirror Spot', 'Dark C Whirlpool Area'), ('Dark C Whirlpool Outer Mirror Spot', 'Dark C Whirlpool Outer Area'), ('South Dark World Teleporter', 'C Whirlpool Area'), - ('Dark South Teleporter Cliff Ledge Drop', 'Central Cliffs'), # OWG, needs glove/pearl ('C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), # OWG ('Dark C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), # OWG ('C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG @@ -1954,7 +1952,9 @@ isolated_regions = [ 'Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge', 'Bumper Cave Ledge', - 'Pyramid Exit Ledge' + 'Pyramid Exit Ledge', + 'Hyrule Castle Water', + 'Pyramid Water' ] flute_data = { diff --git a/Regions.py b/Regions.py index a59f24ed..d61efd69 100644 --- a/Regions.py +++ b/Regions.py @@ -87,7 +87,7 @@ def create_regions(world, player): create_lw_region(player, 'Stone Bridge Area', None, ['Hammer Bridge North Mirror Spot', 'Hammer Bridge South Mirror Spot', 'Stone Bridge NC', 'Stone Bridge EN', 'Stone Bridge WS', 'Stone Bridge SC']), create_lw_region(player, 'Stone Bridge Water', None, ['Dark Hobo Mirror Spot', 'Stone Bridge WC', 'Stone Bridge EC'], 'Light World', Terrain.Water), create_lw_region(player, 'Hobo Bridge', ['Hobo'], ['Hobo EC'], 'Light World', Terrain.Water), - create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'South Teleporter Cliff Ledge Drop', 'Statues Cliff Ledge Drop']), + create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'Statues Cliff Ledge Drop']), create_lw_region(player, 'Tree Line Area', None, ['Lake Hylia Fairy', 'Dark Tree Line Mirror Spot', 'Tree Line WN', 'Tree Line NW', 'Tree Line SE']), create_lw_region(player, 'Tree Line Water', None, ['Tree Line WC', 'Tree Line SC'], 'Light World', Terrain.Water), create_lw_region(player, 'Eastern Nook Area', None, ['Long Fairy Cave', 'Darkness Nook Mirror Spot', 'East Hyrule Teleporter', 'Eastern Nook NE']), @@ -197,7 +197,7 @@ def create_regions(world, player): create_dw_region(player, 'Hammer Bridge South Area', None, ['Hammer Bridge Pegs (South)', 'Stone Bridge South Mirror Spot', 'Hammer Bridge WS', 'Hammer Bridge SC']), create_dw_region(player, 'Hammer Bridge Water', None, ['Hammer Bridge Pier', 'Hobo Mirror Spot', 'Hammer Bridge EC'], 'Dark World', Terrain.Water), create_dw_region(player, 'Dark Central Cliffs', None, ['Dark Bonk Rocks Cliff Ledge Drop', 'Bomb Shop Cliff Ledge Drop', 'Hammer Bridge South Cliff Ledge Drop', 'Ice Lake Area Cliff Ledge Drop', 'Ice Palace Island FAWT Ledge Drop', - 'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Hype Cliff Ledge Drop', 'Dark South Teleporter Cliff Ledge Drop']), + 'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Hype Cliff Ledge Drop']), create_dw_region(player, 'Dark Tree Line Area', None, ['Dark Lake Hylia Fairy', 'Tree Line Mirror Spot', 'Dark Tree Line WN', 'Dark Tree Line NW', 'Dark Tree Line SE']), create_dw_region(player, 'Dark Tree Line Water', None, ['Dark Tree Line WC', 'Dark Tree Line SC'], 'Dark World', Terrain.Water), create_dw_region(player, 'Palace of Darkness Nook Area', None, ['East Dark World Hint', 'East Dark World Teleporter', 'Eastern Nook Mirror Spot', 'Palace of Darkness Nook NE']), diff --git a/Rules.py b/Rules.py index a0aafd7e..3fa42be2 100644 --- a/Rules.py +++ b/Rules.py @@ -1260,12 +1260,10 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('C Whirlpool Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('C Whirlpool Outer Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('South Hyrule 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 Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) # OWG only, can bomb clip out else: set_rule(world.get_entrance('Dark C Whirlpool Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark C Whirlpool Outer Mirror Spot', player), lambda state: state.has_Mirror(player)) 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)) - set_rule(world.get_entrance('Dark South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) if not world.is_tile_swapped(0x34, player): set_rule(world.get_entrance('Statues Mirror Spot', player), lambda state: state.has_Mirror(player)) From 53c6f2e64752e4e4adeaf0995c36b627fc1d5e44 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 17:49:58 -0500 Subject: [PATCH 14/59] Removing old Inverted 1.0 assumptions --- OverworldShuffle.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index f42f156d..d90846ee 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1021,7 +1021,11 @@ def validate_layout(world, player): 'Turtle Rock Area': ['Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge'], 'Dark Death Mountain Ledge': ['Turtle Rock Area'], - 'Dark Death Mountain Isolated Ledge': ['Turtle Rock Area'] + 'Dark Death Mountain Isolated Ledge': ['Turtle Rock Area'], + 'Mountain Entry Entrance': ['West Death Mountain (Bottom)'], + 'Mountain Entry Ledge': ['West Death Mountain (Bottom)'], + 'West Death Mountain (Bottom)': ['Mountain Entry Ledge'], + 'Bumper Cave Entrance': ['Bumper Cave Ledge'] } sane_connectors = { # guaranteed dungeon access @@ -1032,23 +1036,6 @@ def validate_layout(world, player): 'Pyramid Area': ['Pyramid Exit Ledge'] } - if not world.is_tile_swapped(0x0a, player): - if not world.is_tile_swapped(0x03, player): - entrance_connectors['Mountain Entry Entrance'] = ['West Death Mountain (Bottom)'] - entrance_connectors['Mountain Entry Ledge'] = ['West Death Mountain (Bottom)'] - entrance_connectors['West Death Mountain (Bottom)'] = ['Mountain Entry Ledge'] - else: - entrance_connectors['Mountain Entry Entrance'] = ['West Dark Death Mountain (Bottom)'] - entrance_connectors['Bumper Cave Entrance'] = ['Bumper Cave Ledge'] - else: - if not world.is_tile_swapped(0x03, player): - entrance_connectors['Bumper Cave Entrance'] = ['West Death Mountain (Bottom)'] - entrance_connectors['Bumper Cave Ledge'] = ['West Death Mountain (Bottom)'] - entrance_connectors['West Death Mountain (Bottom)'] = ['Bumper Cave Ledge'] - else: - entrance_connectors['Bumper Cave Entrance'] = ['West Dark Death Mountain (Bottom)'] - entrance_connectors['Mountain Entry Entrance'] = ['Mountain Entry Ledge'] - from Main import copy_world_limited from Utils import stack_size3a from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections From cf43762799b4109b76a43963416af32fbdbf2235 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 17:57:10 -0500 Subject: [PATCH 15/59] Fixed Murahduhla freeze issue --- Rom.py | 2 +- data/base2current.bps | Bin 104809 -> 104813 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 02219958..4e12827a 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'cb04e9b67920dcc8103f1e7a7fe86fe2' +RANDOMIZERBASEHASH = 'f27719e0f1f5e61fb159f6bbc58a9afb' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index ac1a5a02a429ff34144ed8a4d6e185bff031e9f1..168d67f9f7caefb4a6eef54c1759b743c67599dd 100644 GIT binary patch delta 1186 zcmWNPZA?>V6vywmg}&UDzECJ2Ah(UWR*_dhaiZnL0S>o0a2eP{hD^#ZM29*S!niFm zM7@;qSf98uZR8?e9V4MJcX3k&nkb@MnSNj<)1}l*#Yu5-Lru2b59jycocvDyIXRC< zrGJh}d)GmgJMTj{;O_fbQY8HW)qw!hD2WWg5CTaODft7XNSeXuN0Mj~64BYnVF>yw zrFEq2#XMKs)hS0~@}FS2zellwgkChIq~Q{Js$8lFJ>j0zbg-scq*paU8fsTnKr6bV zDuG)lF{%aD_>V`G%i}N3a^=^r(oWVyg*YE;f)HnOu%?q}G1e+Co8#miZj&Ba4P~(j z3*54!=-nNC>s+j9SI8G3PH1$>kg)c_0{3XdcjNnKGxNXM4zafG1+K+;`kJRxZPPvH z%tu*UzUUrvfpZy)ikt-FDn8Ac%>HMFEf$FI=UJDOz)n=zwF^%8zw4^kK#8Bm#(nSs zE|fz^{i`%cp9)0P1nEItAPHwEpe-&id+tyMJs~o)@!pa62AVkUBj{i7H3j5Q6SMIj z2Rnm}bO1{zChR`CMIb$>_2%Q1DoBJD{FVx~Lly2DUtmW?5>#t92Z|gRe!w8Ysmtqo57K_%pRA+VO3*D2DNz8aBu2fns=qfg>)W zvb{$M*w(CIP#iANfH6^C>>hrwX(l5q9TUm?blcm7puYV?9=QyhAb=k+-=q0%#i5bKrz0q z7jKb=H^)GFTJv+x0z@GB)&1OzuV2_E{#K72c+S0wZy@?R36W6BXA_;Iy;H(xduewy+zzofVT>cBCw}uckH+@xUG(m{YQxH3U89|vKH*Q O^4+Vy(7IkX^#2ctlfqK~ delta 1123 zcmWNPZA?>l6vppqp)a>@ytRPPaa$__z5otT9BM5v0m(K-%@hb@!bTnVf+SlvM!2nF z1u3QcDJKr5%_wMfNM+6x zD#c@8$_ZyxF3)zkz;1Aod^^Ru3QjxqhQK3DNh(l*T;qxzP)W>=TIdaY?Wk8letVi-G zHYa65^MOcq(l{J3BVH<=Q7#qENQ3F2h=FWeFNXv;ge7v=1!d@!gBFxHE{7X*>3olQ ziWvj48c!-9MQj*!HBaKJIIVyzD8T0mXoUrAQSx9N-ca)30xl|{FjfXM>!(=gbW!HE zZW*AtS;`U>?o@#eDBQ1tc&J2&3cgJhE%=s1KScc9$xi!Or%pGOfvsUu_MdY!y^rd5 zM&(y{87jm~MB)Aj_&l>3u5nKmzw4OLx6G#2iCONzUNMV>^+Q~E{Mku=o!=U49gf-7 zmRE6L@9YQ99;Bf$64ry7I~WN;kVn1J<5~13Qe!tsJnoE!RRU`^9*&0fYk+p!QgA~V zOLlK@+4fwq+bsO-3)p*Aja@{S-0NH7{v^1gF(V^072R3pV9hk*@zYQsn23D>=1>(XapHz<61lVTeF0BQx zK(Z^f!*(*7jpeG-As`66a@^NYT7<>#)JDY*8~aSD>wI%6}ZO) z^^k; Date: Mon, 24 Oct 2022 19:34:35 -0500 Subject: [PATCH 16/59] Fixed Rupee/Bomb logic in playthru --- ItemList.py | 28 ++++++++++++++++------------ Main.py | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/ItemList.py b/ItemList.py index bd0aec53..39c66fa8 100644 --- a/ItemList.py +++ b/ItemList.py @@ -552,19 +552,23 @@ def create_farm_locations(world, player): # and hopefully access to more permanent farm locations def create_and_fill_location(region_name, loc_description, item_name): - region = world.get_region(region_name, player) - loc = Location(player, f'{region_name} {loc_description}', 0, region) - loc.type = LocationType.Logical - loc.parent_region = region - loc.event = True - loc.locked = True - loc.address = None - - world.push_item(loc, ItemFactory(item_name, player), False) - - region.locations.append(loc) - world.dynamic_locations.append(loc) + loc = world.get_location_unsafe(f'{region_name} {loc_description}', player) + if loc: + loc.access_rule = lambda state: True + world.spoiler.suppress_spoiler_locations.append(loc.name) + else: + region = world.get_region(region_name, player) + loc = Location(player, f'{region_name} {loc_description}', 0, region) + loc.type = LocationType.Logical + loc.parent_region = region + loc.event = True + loc.locked = True + loc.address = None + world.push_item(loc, ItemFactory(item_name, player), False) + + region.locations.append(loc) + world.dynamic_locations.append(loc) return loc from Rules import set_rule, add_rule, add_bunny_rule diff --git a/Main.py b/Main.py index e7d296ce..8e7eaa97 100644 --- a/Main.py +++ b/Main.py @@ -554,6 +554,7 @@ def copy_world(world): from OverworldShuffle import categorize_world_regions for player in range(1, world.players + 1): categorize_world_regions(ret, player) + create_farm_locations(ret, player) set_rules(ret, player) return ret From 9b2d13bcd072780155440f34400361b4a4c43271 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 19:36:48 -0500 Subject: [PATCH 17/59] Fixed Rupee/Bomb logic in playthru --- ItemList.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ItemList.py b/ItemList.py index 39c66fa8..86dfd92f 100644 --- a/ItemList.py +++ b/ItemList.py @@ -555,7 +555,6 @@ def create_farm_locations(world, player): loc = world.get_location_unsafe(f'{region_name} {loc_description}', player) if loc: loc.access_rule = lambda state: True - world.spoiler.suppress_spoiler_locations.append(loc.name) else: region = world.get_region(region_name, player) loc = Location(player, f'{region_name} {loc_description}', 0, region) From 1912504c93bb16065a9028687734c0876ba01302 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 20:17:44 -0500 Subject: [PATCH 18/59] Moving OWG connection creation to after Mixed OWR but before Crossed/Layout OWR --- Main.py | 6 ++---- OverworldShuffle.py | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Main.py b/Main.py index 8e7eaa97..0efc38ff 100644 --- a/Main.py +++ b/Main.py @@ -187,8 +187,6 @@ def main(args, seed=None, fish=None): link_overworld(world, player) create_shops(world, player) update_world_regions(world, player) - if world.logic[player] in ('owglitches', 'nologic'): - create_owg_connections(world, player) create_flute_exits(world, player) logger.info(world.fish.translate("cli","cli","shuffling.world")) @@ -462,13 +460,13 @@ def copy_world(world): for player in range(1, world.players + 1): create_regions(ret, player) update_world_regions(ret, player) + if world.logic[player] in ('owglitches', 'nologic'): + create_owg_connections(ret, player) create_flute_exits(ret, player) create_dungeon_regions(ret, player) create_shops(ret, player) create_rooms(ret, player) create_dungeons(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): - create_owg_connections(ret, player) # there are region references here they must be migrated to preserve integrity # ret.exp_cache = world.exp_cache.copy() diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d90846ee..034c68aa 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -176,6 +176,9 @@ def link_overworld(world, player): connect_simple(world, exitname, regionname, player) categorize_world_regions(world, player) + + if world.logic[player] in ('owglitches', 'nologic'): + create_owg_connections(world, player) # crossed shuffle logging.getLogger('').debug('Crossing overworld edges') From 8517d70eba33fc1cb37f93962c3752917efd63ea Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 24 Oct 2022 20:22:20 -0500 Subject: [PATCH 19/59] Moving OWG connection creation to after Mixed OWR but before Crossed/Layout OWR --- OverworldShuffle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 034c68aa..939d2e53 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -4,6 +4,7 @@ from DungeonGenerator import GenerationException from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel +from OverworldGlitchRules import create_owg_connections from Utils import bidict version_number = '0.2.10.1' From 311b002b95991b481409dfea552e8b38ea225394 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 26 Oct 2022 07:54:33 -0500 Subject: [PATCH 20/59] Fixed fake world handling for IB transitions --- Rom.py | 2 +- asm/owrando.asm | 7 ++++++- data/base2current.bps | Bin 104813 -> 104824 bytes 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index 4e12827a..b229a7ac 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'f27719e0f1f5e61fb159f6bbc58a9afb' +RANDOMIZERBASEHASH = 'ff9c003ee6c1277437a4480d583282fd' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 3472bfae..a046b8da 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -631,7 +631,12 @@ OWShuffle: ;look up transitions in current area in table OWEdgeOffsets ;offset is (8bytes * OW Slot ID) + (2bytes * direction) asl : rep #$20 : and.w #$00ff : pha : sep #$20 ;2 bytes per direction - lda $8a : and #$40 : !add $700 : rep #$30 : and #$00ff : asl #3 + + lda $8a : tax : lda.l OWTileWorldAssoc,X : eor.l CurrentWorld : beq + + ; fake world, will treat this OW area as opposite world + txa : eor.b #$40 : tax + + txa : and #$40 : !add $700 : rep #$30 : and #$00ff : asl #3 + adc 1,S : tax asl $700 : pla ;x = offset to edgeoffsets table diff --git a/data/base2current.bps b/data/base2current.bps index 168d67f9f7caefb4a6eef54c1759b743c67599dd..66c714ea90eae5267ff6c92147cd0f6538ffb1f0 100644 GIT binary patch delta 323 zcmV-J0lfb0vIh9F2C!iP1XBF11G8lTKLZ}?m8t-Pks=d>C+hr>A}@p|>Hva`B07X8 zFocaFBZI;Ox55Mgd@}*0w?8-mRRaNgmz6sKj~z0StKu+go-}}gs+VVcqnYpyzp%Ng z&yy=?fMI}_K|BE*0i>5+JOLaRnO(iYY)CZ2$+GXCjN2hCBf+0h*Vz zJOR1_9%`3xJpp(DbC>Bo0Z0K%moh#9jRAR=z&-&i0X~=QJ^?%dTbC+70U`mUmrXwb zE)t}aV4GYk9e{xfpLm|p&;v|wmyJIGiVI9~rjPK67tjKmWtSpA0XGzGbEd6iUBCg* zJEdL81n@PJ#glcHZ9oAT0cw|rKmkG@FG3T5#pV%YuxqUR2Ji&U{03{^cEJL7%|>nx VfS*~n=s*E|SO{R)8P)uE`_D^Ye((SQ delta 331 zcmV-R0kr=3vIgz42C!iP1VaYA`LksKKLZ}ym8t-Pks=d>C+hr>A}@p|>Hva`B07X8 zFocaF6NAD8x55Mgd@})*w?8-mRRaNQmz6sKj~ybCtKu+go-}}gs+VVcqnYpyzp%Ng z&yy=?fB}J*K|BE*0hE_sJOLaGrHUy)7;OLtn{Y*$mytXHEdh#`zdQlD0)A}k&^x7F$pr8OKmkG@4?+`w#pV%YuxqUR2Ji&U{03{^ dcEJKS&PHwyfS-A{^FRT8SO{h Date: Fri, 28 Oct 2022 12:04:14 -0500 Subject: [PATCH 21/59] Fixed ER bug causing inaccessible entrances to not get resolved --- EntranceShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 23d90597..afdb34ca 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1508,7 +1508,7 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe for region_name in inaccessible_regions.copy(): region = world.get_region(region_name, player) if region.type not in [RegionType.LightWorld, RegionType.DarkWorld] or not any((not exit.connected_region and exit.spot_type == 'Entrance') for exit in region.exits) \ - or (region_name == 'Pyramid Exit Ledge' and world.shuffle[player] != 'insanity' or world.is_tile_swapped(0x1b, player)) \ + or (region_name == 'Pyramid Exit Ledge' and (world.shuffle[player] != 'insanity' or world.is_tile_swapped(0x1b, player))) \ or region_name in ['Hyrule Castle Water', 'Pyramid Water']: inaccessible_regions.remove(region_name) elif region.type == (RegionType.LightWorld if not invFlag else RegionType.DarkWorld): From 19176cea7223b23ff6410fdf00f32e0f15b04c52 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 28 Oct 2022 12:06:53 -0500 Subject: [PATCH 22/59] Making crossworld ER modes fill DW inaccessible entrances first rather than combined --- EntranceShuffle.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index afdb34ca..495be252 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1528,21 +1528,15 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, player, ignore_list) # connect one connector at a time to ensure multiple connectors aren't assigned to the same inaccessible set of regions - if world.shuffle[player] in ['lean', 'crossed', 'insanity']: - combined_must_exit_regions = list(must_exit_regions + otherworld_must_exit_regions) - if len(combined_must_exit_regions) > 0: - random.shuffle(combined_must_exit_regions) - connect_one(combined_must_exit_regions[0], [e for e in lw_entrances if e in entrance_pool]) - else: - pool = [e for e in dw_entrances if e in entrance_pool] - if len(otherworld_must_exit_regions) > 0 and len(pool): - random.shuffle(otherworld_must_exit_regions) - connect_one(otherworld_must_exit_regions[0], pool) - elif len(must_exit_regions) > 0: - pool = [e for e in lw_entrances if e in entrance_pool] - if len(pool): - random.shuffle(must_exit_regions) - connect_one(must_exit_regions[0], pool) + pool = [e for e in (lw_entrances if world.shuffle[player] in ['lean', 'crossed', 'insanity'] else dw_entrances) if e in entrance_pool] + if len(otherworld_must_exit_regions) > 0 and len(pool): + random.shuffle(otherworld_must_exit_regions) + connect_one(otherworld_must_exit_regions[0], pool) + elif len(must_exit_regions) > 0: + pool = [e for e in lw_entrances if e in entrance_pool] + if len(pool): + random.shuffle(must_exit_regions) + connect_one(must_exit_regions[0], pool) def unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits): From 5b7530c12ebef3c52dceafaf144c0f0dce3175cb Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 28 Oct 2022 13:16:48 -0500 Subject: [PATCH 23/59] Fixed Lite ER --- EntranceShuffle.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 495be252..201e8f35 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -395,21 +395,21 @@ def link_entrances(world, player): connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (dw_entrances if not invFlag else lw_entrances)] connect_inaccessible_regions(world, [], connector_entrances, caves, player) if invFlag: - lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) + lw_dungeons = [e for e in lw_dungeons if e in caves] else: - dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) + dw_dungeons = [e for e in dw_dungeons if e in caves] - caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + (lw_dungeons if not invFlag else dw_dungeons) + caves = [e for e in caves if e not in (dw_dungeons if not invFlag else lw_dungeons)] + (lw_dungeons if not invFlag else dw_dungeons) connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (lw_entrances if not invFlag else dw_entrances)] connect_inaccessible_regions(world, connector_entrances, [], caves, player) if not invFlag: - lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) + lw_dungeons = [e for e in lw_dungeons if e in caves] else: - dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) + dw_dungeons = [e for e in dw_dungeons if e in caves] + caves = [e for e in caves if e not in (lw_dungeons if not invFlag else dw_dungeons)] + DW_Mid_Dungeon_Exits lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else []) dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House) - caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + DW_Mid_Dungeon_Exits # place old man, has limited options lw_entrances = [e for e in lw_entrances if e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] and e in entrance_pool] From bba486f526014a0216a1ddf6f8fcbd5d00bd3cd4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 28 Oct 2022 13:19:49 -0500 Subject: [PATCH 24/59] Changing Lite ER to place Old Man House in any world per Inverted 2.0 changes --- EntranceShuffle.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 201e8f35..48f4d0f3 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -380,7 +380,7 @@ def link_entrances(world, player): place_blacksmith(world, links_house, player) # determine pools - Cave_Base = list(Cave_Exits + Cave_Three_Exits) + Cave_Base = list(Cave_Exits + Cave_Three_Exits + Old_Man_House) lw_entrances = list() dw_entrances = list() for e in entrance_pool: @@ -408,8 +408,6 @@ def link_entrances(world, player): dw_dungeons = [e for e in dw_dungeons if e in caves] caves = [e for e in caves if e not in (lw_dungeons if not invFlag else dw_dungeons)] + DW_Mid_Dungeon_Exits - lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else []) - dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House) # place old man, has limited options lw_entrances = [e for e in lw_entrances if e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] and e in entrance_pool] From 417609eb8dcadaf886e8095f54843489ca442555 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 28 Oct 2022 13:51:10 -0500 Subject: [PATCH 25/59] Increasing allowance on infinite loop detection --- BaseClasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 903f72e6..d9da6466 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1471,7 +1471,7 @@ class Region(object): def can_reach(self, state): from Utils import stack_size3a from DungeonGenerator import GenerationException - if stack_size3a() > self.world.players * 500: + if stack_size3a() > self.world.players * 1000: raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'') if state.stale[self.player]: From 5d294f91f0969662518ae4925a8d937f40b717a6 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 27 Oct 2022 13:56:37 -0600 Subject: [PATCH 26/59] Bumper cave fix --- EntranceShuffle.py | 6 ++++-- PotShuffle.py | 18 +++++++++--------- Regions.py | 3 ++- Rules.py | 8 ++++---- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 48f4d0f3..951c55f6 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2068,6 +2068,8 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'), ('Fairy Ascension Cave Pots', 'Fairy Ascension Cave (Bottom)'), ('Fairy Ascension Cave Drop', 'Fairy Ascension Cave (Drop)'), ('Missing Smith', 'Missing Smith'), + ('Bumper Cave Bottom to Top', 'Bumper Cave (top)'), + ('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'), ('Superbunny Cave Climb', 'Superbunny Cave (Top)'), ('Hookshot Cave Front to Middle', 'Hookshot Cave (Middle)'), ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), @@ -2136,8 +2138,8 @@ default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Exit (Wes ('Elder House (West)', 'Elder House Exit (West)'), ('Two Brothers House (East)', 'Two Brothers House Exit (East)'), ('Two Brothers House (West)', 'Two Brothers House Exit (West)'), - ('Bumper Cave (Bottom)', 'Bumper Cave Exit (Bottom)'), - ('Bumper Cave (Top)', 'Bumper Cave Exit (Top)'), + ('Bumper Cave (Top)', 'Bumper Cave (top)'), + ('Bumper Cave (Bottom)', 'Bumper Cave (bottom)'), ('Superbunny Cave (Top)', 'Superbunny Cave Exit (Top)'), ('Superbunny Cave (Bottom)', 'Superbunny Cave Exit (Bottom)'), ('Hookshot Cave', 'Hookshot Cave Front Exit'), diff --git a/PotShuffle.py b/PotShuffle.py index 7806f495..cdb829f4 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -745,11 +745,11 @@ vanilla_pots = { 0xE7: [Pot(68, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB389, [0x8B, 0x2B, 0xFA])), Pot(72, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB38C, [0x93, 0x2B, 0xFA]))], 0xE8: [Pot(96, 4, PotItem.Heart, 'Superbunny Cave (Bottom)', obj=RoomObject(0x0AA98E, [0xC3, 0x23, 0xFA]))], - 0xEB: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADE7, [0x9F, 0x47, 0xFA])), - Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADEA, [0xA7, 0x47, 0xFA])), - Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADED, [0xB3, 0x73, 0xFA])), - Pot(92, 14, PotItem.Heart, 'Bumper Cave', obj=RoomObject(0x0AADF0, [0xBB, 0x73, 0xFA])), - Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADF3, [0xC3, 0x73, 0xFA]))], + 0xEB: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADE7, [0x9F, 0x47, 0xFA])), + Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADEA, [0xA7, 0x47, 0xFA])), + Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADED, [0xB3, 0x73, 0xFA])), + Pot(92, 14, PotItem.Heart, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADF0, [0xBB, 0x73, 0xFA])), + Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADF3, [0xC3, 0x73, 0xFA]))], 0xF1: [Pot(64, 5, PotItem.Heart, 'Old Man Cave', obj=RoomObject(0x0AA6B2, [0x83, 0x2B, 0xFA]))], 0xF3: [Pot(0x28, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA76F, [0x53, 0xA3, 0xFA])), Pot(0x2C, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA772, [0x5B, 0xA3, 0xFA])), @@ -851,10 +851,10 @@ vanilla_pots = { Pot(100, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA])), Pot(88, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA])), Pot(100, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]))], - 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801A, [0x33, 0xCB, 0xFA])), - Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801D, [0x3B, 0xCB, 0xFA])), - Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8020, [0x43, 0xCB, 0xFA])), - Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8023, [0x4B, 0xCB, 0xFA]))], + 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B801A, [0x33, 0xCB, 0xFA])), + Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B801D, [0x3B, 0xCB, 0xFA])), + Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B8020, [0x43, 0xCB, 0xFA])), + Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B8023, [0x4B, 0xCB, 0xFA]))], } diff --git a/Regions.py b/Regions.py index d61efd69..027d4e1b 100644 --- a/Regions.py +++ b/Regions.py @@ -321,7 +321,8 @@ def create_regions(world, player): create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']), create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']), create_cave_region(player, 'Cave Shop (Dark Death Mountain)', 'a common shop', ['Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right']), - create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), + create_cave_region(player, 'Bumper Cave (bottom)', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Bottom to Top']), + create_cave_region(player, 'Bumper Cave (top)', 'a connector', None, ['Bumper Cave Exit (Top)', 'Bumper Cave Top To Bottom']), create_cave_region(player, 'Fortune Teller (Dark)', 'a fortune teller'), create_cave_region(player, 'Dark Sanctuary Hint', 'a storyteller', None, ['Dark Sanctuary Hint Exit']), create_cave_region(player, 'Dark World Potion Shop', 'a common shop', ['Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right']), diff --git a/Rules.py b/Rules.py index 3fa42be2..07c7e6af 100644 --- a/Rules.py +++ b/Rules.py @@ -818,8 +818,8 @@ def default_rules(world, player): # Underworld Logic set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) # can erase block, overwritten in noglitches - set_rule(world.get_entrance('Bumper Cave 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('Bumper Cave Bottom to Top', player), lambda state: state.has('Cape', player)) + set_rule(world.get_entrance('Bumper Cave Top To Bottom', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling # Item Access @@ -1801,8 +1801,8 @@ def set_bunny_rules(world, player, inverted): # 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 (Middle)', - 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] + bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', + 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', From ae1a775248741d396662ea0f61547f5a770fc9c0 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 29 Oct 2022 11:30:10 -0500 Subject: [PATCH 27/59] Reordering Crossed options to have Grouped as first option --- resources/app/cli/args.json | 2 +- resources/app/cli/lang/en.json | 2 ++ resources/app/gui/lang/en.json | 2 +- resources/app/gui/randomize/overworld/widgets.json | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 09f9d69b..95cf6ffb 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -152,8 +152,8 @@ "ow_crossed": { "choices": [ "none", - "polar", "grouped", + "polar", "limited", "chaos" ] diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index f1046bee..5869f144 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -219,6 +219,8 @@ "ow_crossed": [ "This allows cross-world connections to occur on the overworld.", "None: No transitions are cross-world connections.", + "Grouped: This ensures a two-plane separation so that you cannot", + " walk around and access the other plane version by walking.", "Polar: Only used when Mixed is enabled. This retains original", " connections even when overworld tiles are swapped.", "Limited: Exactly nine transitions are randomly chosen as", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 42607a14..9a92eb81 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -135,8 +135,8 @@ "randomizer.overworld.crossed": "Crossed", "randomizer.overworld.crossed.none": "None", - "randomizer.overworld.crossed.polar": "Polar", "randomizer.overworld.crossed.grouped": "Grouped", + "randomizer.overworld.crossed.polar": "Polar", "randomizer.overworld.crossed.limited": "Limited", "randomizer.overworld.crossed.chaos": "Chaos", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index 91540b27..d349cfc0 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -20,8 +20,8 @@ "default": "vanilla", "options": [ "none", - "polar", "grouped", + "polar", "limited", "chaos" ] From 4885ce3e4c2ad886ead3a4bb2c67c3b4dff6c2ce Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 29 Oct 2022 11:34:02 -0500 Subject: [PATCH 28/59] Updated Readme --- README.md | 134 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 106 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 59d3cd5f..7bd24a96 100644 --- a/README.md +++ b/README.md @@ -30,35 +30,89 @@ All feedback and dev conversation happens in the #ow-rando channel on the [ALTTP # Installation from Source -Download the source code from the repository directly and put it in a folder of your choosing. +1) Download the source code from the repository directly and put it in a folder of your choosing. -You must have Python installed (version 3.6 - 3.9 supported) +2) You must have Python installed (version 3.7 - 3.10 supported), and ensure PATH is included during the installation. -This program requires all python dependencies that are necessary to run Aerinon's Door Randomizer. Try running ```pip install missingdependency``` or ```python -m pip install missingdependency``` on the command line (replace ```missingdependency``` with the specific package that is missing) to install the dependency. +3) This program requires all python dependencies that are necessary to run OW Randomizer. There are multiple ways to install them: + - Try running ```pip install missingdependency``` or ```python -m pip install missingdependency``` on the command line (replace ```missingdependency``` with the specific package that is missing) to install the dependency. + - The simpler method, run (double-click) ```resources/ci/common/local_install.py``` to install all the missing dependencies as well. -Alternatively, run ```resources/ci/common/local_install.py``` to install all the missing dependencies as well. +4) Once installed, you should be able to run (double-click) ```Gui.py``` and the OWR program will appear, where you can select your desired settings. See the following link if you have additional trouble: https://github.com/codemann8/ALttPDoorRandomizer/blob/OverworldShuffle/docs/BUILDING.md # Running the Program -To use the CLI, run ```DungeonRandomizer.py```. +- Run (or double-click) ```Gui.py``` for a simple graphical user interface. +- Alternatively, you can generate thru the CLI, run ```DungeonRandomizer.py```. -Alternatively, run ```Gui.py``` for a simple graphical user interface. +# Terminology + +### OW / OWR +OW is shorthand for Overworld, referring to anywhere that is "outside" in the game: ie. not caves or dungeons, which are considered Underworld. + +OWR is shorthand for Overworld Rando/Shuffle, the concept of randomizing certain elements present in the Overworld. + +### LW / DW (**IMPORTANT TO READ**) +Light World and Dark World. Some of the references to LW and DW can be a bit ambiguous. Sometimes, if a LW tile is being referenced, it's usually referring to the normal LW tile that you already know of. Other times, I might be referring to a DW tile that has now become part of the LW, due to a certain way that the game can be shuffled. This can be particularly confusing when trying to understand these modes, and what they do, based on the text description of them, but it's important to keep in mind the context of which is being described. + +Things get extra confusing when adding Crossed OWR into the mix, due to the fluidity of navigating between LW and DW freely. It's often best to think of Crossed OWR like this: You've been used to a two-part Light and Dark World, but Crossed works more close to a Starting Plane and Other Plane, ie. You can walk around and never be able to reach the "other" plane until you have the items to enter portals, including using mirror. Both of these planes consist of Light and Dark World tiles. + +During gameplay and in the OW, there is an L or D in the upper left corner, indicating which *world* you are in. As complicated as all the rules can be, this L or D is the simplest way to digest it all. Whenever it says you are in DW, this means you are subject to being transformed to a bunny, cannot use the flute, and you will be able to use the mirror to gain access to the LW. When in LW, you are transformed to Link (if not already), and you're able to use the flute. + +### Tile +This refers to one OW screen, containing one or more transitions (or edges) that lead to another tile. Most tiles have a "other world" version of it, linked by either a portal or mirror; some do not like Zora's Domain or Master Sword Pedestal. + +### Edge / Transition +This refers to the sides/edges of the screen, where you walk into them and causes another tile to load, the camera pans over, and Link continues movement that destination tile. + +### Parallel (as a concept) +Various things in this readme are referred to as parallel or non-parallel. Parallel refers to elements that exist in similar orientation in the opposite world. For instance, Kakariko Village and Village of Outcasts each have 5 transitions, all in the same geographical area and all leading in the same direction, that makes all those transitions parallel. In another instance, The Purple Chest screen (where you turn it in) has transitions on the West side of the screen, but in the DW, those transitions do not exist, therefore, these West transitions are non-parallel, and the tile itself is not parallel. Determining whether something is parallel or not dictates whether something can be shuffled or not, or whether it requires something else to be grouped with it and shuffled together. + +### Tile Groups +These are groups of tiles that must be shuffled (or not shuffled) as a group. For instance, if Standard World State is enabled, Link's House, Hyrule Castle, and Sanctuary must be kept together, to ensure that the opening sequence can commence. There are a number of scenarios that can cause various tiles to be grouped, this is largely determined by the specific combination of OWR settings and is necessary to ensure correct logic. + +### Similar Edge +These refer to groups of edges that are near each other. For instance, at Link's House, there are 3 different edges on the West side of the screen, separated by the cliffside terrain. All 3 of these can potentially lead to 3 different places. But conversely, these 3 edges are also considered similar edges and can be made to all lead to the same area. + +Similar edges on the larger OW tiles are a bit harder to intuitively determine. For instance, in Kakariko Village, there are 3 North edges, but all 3 are NOT part of the same Similar Edge group. Instead, the 2 on the West End are deemed Similar, while the one to the far East is its own edge. The most definitive way to figure out which edges are actually grouped, look at a world map image with gridlines (one is linked above in `Trackers & Guides` labeled OW Rando Cheat Sheet), if you draw imaginary lines, splitting up large tiles into 2x2 parts (continuing the small tiles' patterns), all edges within these bounds are considered Similar Edges. + +Similar edges are used when `Keep Similar Edges Together` is enabled and have meaning when used with `Layout Shuffle` (with and without `Free Terrain`) and `Crossed` settings. + +# Inverted Changes + +The version of Inverted included in OWR varies quite a bit compared to the Door Rando and VT (main rando) forks. This is often referred to as Inverted 2.0, as this is a plan to shift it to a newer/updated concept, intended to either enhance/improve the Inverted experience or to restore some behaviors more closer to vanilla. Some of these changes are likely going to be added to all rando forks/branches and subject to change, but this will be based on user feedback. + +- Links House start now spawns inside the Big Bomb Shop, where there is now a bed +- Old Man Cave/Bumper Cave entrances are restored to their original state +- Mountain Cave S+Q now spawns in his usual Cave +- Removed the ladder that leads from West Dark Death Mountain up to the top near the GT entrance +- Flute Spot 1 is moved to the very Top of Dark Death Mountain, next to the GT entrance +- When finding Flute, it comes pre-activated (will hear SFX on collecting) +- Spiral and Mimic Cave are now bridged together +- TR Peg Puzzle is restored but instead reveals a ladder +- Houlihan now exits same place as the Link's House start does +- Ganon Hole Exit now exits out a new door on top of HC +- Ice Palace has been re-sealed to vanilla, portal moved to outer edge of moat (makes IP mirror locked) +- Glitched modes will now use vanilla terrain except where necessary + +Note: These changes do impact the logic. If you use `CodeTracker`, these Inverted 2.0 logic rules are automatically detected if using autotracker, indicated by a 2 in the corner of the World State mode icon. This can also be manually applied if you right-click the World State mode icon. # Settings -Only extra settings added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md) +Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md) ## Overworld Layout Shuffle (--ow_shuffle) +OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://media.discordapp.net/attachments/783989090017738753/857299555183362078/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Swap) ### Vanilla -OW is not shuffled. +OW Transitions are not shuffled. ### Parallel -OW Transitions are shuffled, but both worlds will have a matching layout. +OW Transitions are shuffled, but both worlds will have a matching layout, similar to that of vanilla. ### Full @@ -70,11 +124,11 @@ With OW Layout Shuffle, this allows land and water edges to be connected. ## Crossed Options (--ow_crossed) -This allows OW connections to be shuffled cross-world. +This allows OW connections to be shuffled cross-world. There are 2 main methodologies of Crossed OWR: -Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable +- Grouped and Polar both are guaranteed to result in two separated planes of tiles, similar to that of vanilla. This means you cannot simply walk around and be able to visit all the tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a tile that was previously unreachable -Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. +- Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. See each option to get more details on the differences. @@ -82,23 +136,23 @@ See each option to get more details on the differences. Transitions will remain same-world. -### Polar - -Only effective if Mixed/Tile Swap is enabled. Enabling Polar preserves the original/vanilla connections even when tiles are swapped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny (as per Mixed swapping some tiles to the other world). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there. (see Tile Swap/Mixed section for more details) - ### Grouped -This option shuffles connections cross-world in the same manner as Tile Swap/Mixed, the connections leading in and coming out of a group of tiles are crossed. Unlike Polar, this uses a different set of tile groups as a basis of crossing connections, albeit the same rule govern which groups of tiles must cross together (see Tile Swap/Mixed for more details) +This option shuffles connections cross-world in the same manner as Tile Swap/Mixed, the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR. + +### Polar + +Only effective if Mixed/Tile Swap is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are swapped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Mixed/Tile Swap works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Swap/Mixed` section for more details) ### Limited -Every transition independently is a candidate to be chosen as a cross-world connection, however only 9 transitions become crossed (in each world). This option abides by the Keep Similar Edges Together option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. +Every transition is independently a candidate to be chosen as a cross-world connection, however only 9 total transitions become crossed (to/from each world). This option abides by the `Keep Similar Edges Together` option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. Note: Only parallel connections (a connection that also exists in the opposite world) are considered for cross-world connections, which means that the same connection in the opposite world will also connect cross-world. Note: If Whirlpool Shuffle is enabled, those connections can be cross-world but do not count towards the 9 transitions that are crossed. -Motive: Why 9 connections? To imitate the effect of the 9 standard portals that exist. +Motive: Why 9 connections? To imitate the effect of the 9 existing standard portals. ### Chaos @@ -106,15 +160,21 @@ Same as Limited, except that there is no limit to the number of cross-world conn ## Keep Similar Edges Together (--ow_keepsimilar) -This keeps similar edge transitions together. ie. The 2 west land edges of Potion Shop will be paired to another set of two similar edges, unless Free Terrain is also enabled, in which case these 2 edges together with the west water edge form a group of 3 similar edges. +This keeps similar edge transitions together. ie. The 2 west land edges of Potion Shop will be paired to another set of two similar edges, unless Free Terrain is also enabled, in which case these 2 edges together with the west water edge form a group of 3 similar edges. See `Terminology` section above for a more detailed explanation of Similar Edges. Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW. ## Tile Swap / Mixed Overworld (--ow_mixed) -OW tiles are randomly chosen to become a part of the opposite world. When on the Overworld, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW. +Tile Swap (often referred to as Mixed OWR) can be thought of as a hybrid of Open and Inverted, where OW tiles are randomly chosen to be swapped to become a part of the opposite world. When this occurs, that tile will use the Inverted version of that tile. For instance, if the Cave 45 tile becomes swapped, that means while walking around in the LW, you will find the screen that's south of Stumpy instead, and Cave 45 will instead be found in the DW; but like Inverted, the Cave 45 tile is modified to not have a ledge, this ensures that it will be possible to access it. -Note: Tiles are put into groups that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to another tile, those tiles must swap together; (an exception to this is the Old Man Rescue cave which has been modified similar to how Inverted modifies it, Old Man Rescue is ALWAYS accessible from the Light World) +Being that this uses concepts from Inverted, it will be important to review the OWR-exclusive changes that have been made to Inverted (often referred to as Inverted 2.0). See `Inverted Changes` for more details. + +During gameplay: + - When on the OW, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW. + - When doing a map check (pressing X while on the OW), the tiles shown will reflect the swapped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world, beware of Desert/Mire and Eastern/PoD. Here is an image showing the difference of appearance when tiles are swapped on the [map check](https://media.discordapp.net/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif) screen. + +Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must swap together. ## Whirlpool Shuffle (--ow_whirlpool) @@ -156,6 +216,10 @@ This adds 41 new item locations to the game. These bonk locations are limited to - Rocks and statues are unable to be made to have a different color - Since Fairies and Apples are new items that can appear in plain sight, they don't have a proper graphic for them yet. For now, they show up as Power Stars +Here is a map that shows all the [Bonk Locations](https://media.discordapp.net/attachments/783989090017738753/1000880877548609607/unknown.png?width=1399&height=702). FYI, the 2-4 and 2-3-4 refer to the tree numbers that have the items. The 2 by Dark Fortune Teller indicate that there are 2 bonk items there. The stars with a green square are all Bonk Locations that are unlocked after you kill Aga 1. + +As far as map trackers, Bonk Locations are supported on `CodeTracker` when the Bonk Drops option is enabled. + Future Note: This does NOT include the Good Bee (Cold Bee) Cave Statue...yet. In the future, this could be an additional item location. #### Items Added To Pool: @@ -173,21 +237,23 @@ Future Note: This does NOT include the Good Bee (Cold Bee) Cave Statue...yet. In ### Trinity -This goal gives you the choice between 3 goals, only one of which the player needs to complete: Defeat Ganon (no Aga2), Pulling Pedestal, or turn in TF pieces to Murahdahla. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 5 crystals or Random crystals, although the player can flexibly change these settings as they seem fit. +This goal gives you the choice between 3 goals, only one of which the player needs to complete: Fast Ganon (no Aga2), pulling Pedestal, or turn in TF pieces to Murahdahla. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 4 or 5 crystals or Random crystals, although the player can flexibly change these settings as they see fit. ## New Entrance Shuffle Options (--shuffle) ### Lite -This mode is intended to be a beginner-friendly introduction to playing ER. It focuses on reducing low% world traversal in late-game dungeons while reducing the number of entrances needing to be checked. +This mode is intended to be a beginner-friendly introduction to playing ER, unlike that of Simple ER. It focuses on reducing Low% world traversal in late-game dungeons while reducing the number of entrances needing to be checked. This mode groups entrances into types and shuffles them freely within those groups. - Dungeons and Connectors (Multi-Entrance Caves) -- Item Locations (Single-Entrance Caves with an item, includes Potion Shop and Red Bomb Shop, includes Shops only if Shopsanity is enabled) +- Item Locations (Single-Entrance Caves with an item, includes Potion Shop and Red Bomb Shop) + - Includes Shops only if Shopsanity is enabled + - Includes caves with pots only if Pottery settings have them shuffled) - Dropdowns and their associated exits (Skull Woods dropdowns are handled the same as in Crossed) - Non-item locations (junk locations) all remain vanilla -Lite mode shuffles all connectors same-world, to limit bunny traversal. And to prevent Low% enemy and boss combat, some dungeons are confined to specific worlds. +Lite ER shuffles all connectors same-world, to limit bunny traversal. And to prevent Low% enemy and boss combat, some dungeons are confined to specific worlds. The following dungeons are guaranteed to be in the Light World: - Hyrule Castle @@ -202,16 +268,22 @@ The following are guaranteed to be in the Dark World: - Turtle Rock - Ganon's Tower +Both Lite and Lean ER modes are supported in `CodeTracker`, showing only the entrances that are shuffled. + ### Lean This mode is intended to be a more refined and more competitive format to Crossed ER. It focuses on reducing the number of entrances needing to be checked, while giving the player unique routing options based on the entrance pools defined below, as opposed to mindlessly checking all the remaining entrances. The Dungeons/Connectors can connect cross-world. This mode groups entrances into types and shuffles them freely within those groups. - Dungeons and Connectors (Multi-Entrance Caves) -- Item Locations (Single-Entrance Caves with an item, includes Potion Shop and Red Bomb Shop, includes Shops only if Shopsanity is enabled) +- Item Locations (Single-Entrance Caves with an item, includes Potion Shop and Red Bomb Shop) + - Includes Shops only if Shopsanity is enabled + - Includes caves with pots only if Pottery settings have them shuffled) - Dropdowns and their associated exits (Skull Woods dropdowns are handled the same as in Crossed) - Non-item locations (junk locations) all remain vanilla +Both Lite and Lean ER modes are supported in `CodeTracker`, showing only the entrances that are shuffled. + # Command Line Options ``` @@ -226,11 +298,17 @@ Show the help message and exit. For specifying the overworld layout shuffle you want as above. (default: vanilla) +``` +--ow_terrain +``` + +With OW Layout Shuffle, this allows land and water edges to be connected. + ``` --ow_crossed ``` -For specifying the type of cross-world connections you want on the overworld +For specifying the type of cross-world connections you want on the overworld ``` --ow_keepsimilar From 5c14a3f245d3eaf8dcb256ae69adf40a2aec105a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 29 Oct 2022 11:35:18 -0500 Subject: [PATCH 29/59] Version bump 0.2.11.0 --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 939d2e53..340152a6 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.2.10.1' +version_number = '0.2.11.0' # branch indicator is intentionally different across branches version_branch = '-u' From 6d434918e135e7479aabb2e717621f57fa6b4e3b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 29 Oct 2022 15:50:35 -0500 Subject: [PATCH 30/59] Updated Readme --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 7bd24a96..36abf9da 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,36 @@ See the following link if you have additional trouble: https://github.com/codema - Run (or double-click) ```Gui.py``` for a simple graphical user interface. - Alternatively, you can generate thru the CLI, run ```DungeonRandomizer.py```. +# Getting Started +## "I heard about OWR and want to give it a try, what do I do?" +This is a common sentiment among those who are unfamiliar with OWR's offerings. One might be tempted to simply turn on a bunch of the options and give it a go. I cannot express this enough: + +**^ DO NOT do this! ^** + +OWR definitely has a lot of options, and all of them by themselves are pretty simple to grasp, but combining multiple OWR options together increases the complexity and confusion in exponential fashion. Now, of course, some OWR options like Flute Shuffle can safely be combined at any level and isn't gonna make anything more complicated. But specifically, avoid combining these 3 options, at least when going for your first seed: +- OW Layout Shuffle +- OW Tile Swap (Mixed) +- Crossed OW + +## "Any recommendations for a first-timer?" +For a first (and second) seed... *and I say "second" because I feel like both of these recommendations I'm about to make have VERY different vibes, have different levels of challenge, but are both, of their own right, worthy of being tried at least once.* Your first OWR experience can be combined with any mode combination that you are already familiar with and have a lot of experience in playing. If you like Crosskeys and feel very comfortable running that, feel free to turn on all those settings in addition to ONE of these two options: +1. `OW Tile Swap (Mixed)` - Overly, a pretty easy-breezy mode, it doesn't require too much big brain, and is pretty managable even without proper logic tracking, as long as you at least have a standard map tracker. This is actually my favorite way to run OWR today + - DO NOT turn on Layout or Whirlpool Shuffle, leave this on `Vanilla` + - DO NOT turn on Crossed OWR + - `Flute Shuffle` or `Bonk Drops` could be enabled if desired, altho I'd recommend against it, at least for a fresh viewpoint of Mixed OWR +2. `OW Layout Shuffle` - Set to `Parallel`. This is the original spirit and vision of OWR from the time of its own founding. It's definitely much more complicated to run than OW Tile Swap, so keep that in mind. + - `Starting Boots` - Either actual boots or pseudoboots, you will be spending a lot of time navigating the OW, so it's best to do it with the ability to run fast. + - DO NOT turn on OW Tile Swap (Mixed) + - DO NOT turn on Crossed OWR + - Enable `Whirlpool Shuffle` - Recommended to always be enabled with Layout Shuffle + - Enable `Keep Similar Edges Together` - This just helps keep some of your sanity for a first experience + - Enable `Flute Shuffle` - I recommend setting this to `Balanced`, this helps space out the flute spots across the world. Being that the world itself is shuffled, the vanilla flute spots are likely NOT as conveniently located as they normally are. + - `Free Terrain` - Recommend to NOT turn this on for a first run, but definitely on a second run. + - `Bonk Drops` - Recommend to NOT turn this on, especially if you don't have starting boots, but you could enable this in future seeds if you've become more familiar with OWR, as you'll be visiting these screens anyways, you might as well grab the items on the way :) + +## "What tracker should I use?" +I personally use 2 trackers together, at least if Layout Shuffle is enabled. Currently, DunkaTracker is the ONLY tracker that is useful for tracking Layout Shuffle, can highly recommend. However, I personally don't like the main portion of the tracker and ignore the main window completely. For tracking everything else, I use `CodeTracker`, a tracker pack that is installable within `EmoTracker`, this handles ALL OWR modes EXCEPT Layout Shuffle (and generally Crossed OW, but missing that doesn't make much of a difference). I am unaware of ANY trackers outside of these 2 that handle OWR modes, so my advice would be to familiarize yourself with both trackers and pick and choose the parts you find most useful for yourself. + # Terminology ### OW / OWR From d532a579e8272b3d923985ec1fc339b9aaf0f785 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 30 Oct 2022 15:52:40 -0500 Subject: [PATCH 31/59] Changed copy_world_limited to premature and made it only for one player --- EntranceShuffle.py | 12 +++---- Main.py | 80 ++++++++++++++++++++++----------------------- OverworldShuffle.py | 12 +++---- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 951c55f6..5ac4a5ac 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1440,12 +1440,12 @@ def place_old_man(world, pool, player, ignore_list=[]): def junk_fill_inaccessible(world, player): - from Main import copy_world_limited + from Main import copy_world_premature find_inaccessible_regions(world, player) for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_limited(world) + base_world = copy_world_premature(world, player) base_world.override_bomb_check = True # remove regions that have a dungeon entrance @@ -1602,12 +1602,12 @@ def unbias_dungeons(Dungeon_Exits): def build_accessible_entrance_list(world, start_region, player, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False): - from Main import copy_world_limited + from Main import copy_world_premature from Items import ItemFactory for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_limited(world) + base_world = copy_world_premature(world, player) base_world.override_bomb_check = True connect_simple(base_world, 'Links House S&Q', start_region, player) @@ -1710,12 +1710,12 @@ def get_distant_entrances(world, start_entrance, player): def can_reach(world, entrance_name, region_name, player): - from Main import copy_world_limited + from Main import copy_world_premature from Items import ItemFactory for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_limited(world) + base_world = copy_world_premature(world, player) base_world.override_bomb_check = True entrance = world.get_entrance(entrance_name, player) diff --git a/Main.py b/Main.py index 0efc38ff..d7646f83 100644 --- a/Main.py +++ b/Main.py @@ -558,7 +558,7 @@ def copy_world(world): return ret -def copy_world_limited(world): +def copy_world_premature(world, player): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, @@ -621,63 +621,63 @@ def copy_world_limited(world): ret.is_copied_world = True - for player in range(1, world.players + 1): - create_regions(ret, player) - update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): - create_owg_connections(ret, player) - create_flute_exits(ret, player) - create_dungeon_regions(ret, player) - create_owedges(ret, player) - create_shops(ret, player) - create_doors(ret, player) - create_rooms(ret, player) - create_dungeons(ret, player) + create_regions(ret, player) + update_world_regions(ret, player) + if world.logic[player] in ('owglitches', 'nologic'): + create_owg_connections(ret, player) + create_flute_exits(ret, player) + create_dungeon_regions(ret, player) + create_owedges(ret, player) + create_shops(ret, player) + create_doors(ret, player) + create_rooms(ret, player) + create_dungeons(ret, player) - for player in range(1, world.players + 1): - if world.mode[player] == 'standard': - parent = ret.get_region('Menu', player) - target = ret.get_region('Hyrule Castle Secret Entrance', player) - connection = Entrance(player, 'Uncle S&Q', parent) - parent.exits.append(connection) - connection.connect(target) + if world.mode[player] == 'standard': + parent = ret.get_region('Menu', player) + target = ret.get_region('Hyrule Castle Secret Entrance', player) + connection = Entrance(player, 'Uncle S&Q', parent) + parent.exits.append(connection) + connection.connect(target) # connect copied world copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations for region in world.regions: - copied_region = ret.get_region(region.name, region.player) - copied_region.is_light_world = region.is_light_world - copied_region.is_dark_world = region.is_dark_world - copied_region.dungeon = region.dungeon - copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations] - for location in copied_region.locations: - location.parent_region = copied_region - for entrance in region.entrances: - ret.get_entrance(entrance.name, entrance.player).connect(copied_region) + if region.player == player: + copied_region = ret.get_region(region.name, region.player) + copied_region.is_light_world = region.is_light_world + copied_region.is_dark_world = region.is_dark_world + copied_region.dungeon = region.dungeon + copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations] + for location in copied_region.locations: + location.parent_region = copied_region + for entrance in region.entrances: + ret.get_entrance(entrance.name, entrance.player).connect(copied_region) for item in world.precollected_items: - ret.push_precollected(ItemFactory(item.name, item.player)) + if item.player == player: + ret.push_precollected(ItemFactory(item.name, item.player)) for edge in world.owedges: - if edge.dest is not None: + if edge.player == player and edge.dest: copiededge = ret.check_for_owedge(edge.name, edge.player) if copiededge is not None: copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) for door in world.doors: - entrance = ret.check_for_entrance(door.name, door.player) - if entrance is not None: - destdoor = ret.check_for_door(entrance.door.name, entrance.door.player) - entrance.door = destdoor - if destdoor is not None: - destdoor.entrance = entrance + if door.player == player: + entrance = ret.check_for_entrance(door.name, door.player) + if entrance is not None: + destdoor = ret.check_for_door(entrance.door.name, entrance.door.player) + entrance.door = destdoor + if destdoor is not None: + destdoor.entrance = entrance ret.key_logic = world.key_logic.copy() from OverworldShuffle import categorize_world_regions - for player in range(1, world.players + 1): - categorize_world_regions(ret, player) - set_rules(ret, player) + categorize_world_regions(ret, player) + set_rules(ret, player) return ret diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 340152a6..d88c8d58 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -904,13 +904,13 @@ def can_reach_smith(world, player): return found def build_sectors(world, player): - from Main import copy_world_limited + from Main import copy_world_premature from OWEdges import OWTileRegions # perform accessibility check on duplicate world for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_limited(world) + base_world = copy_world_premature(world, player) # build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances) regions = list(OWTileRegions.copy().keys()) @@ -970,7 +970,7 @@ def build_sectors(world, player): def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False): from BaseClasses import CollectionState - from Main import copy_world_limited + from Main import copy_world_premature from Items import ItemFactory from Utils import stack_size3a @@ -997,7 +997,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F if build_copy_world: for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_limited(world) + base_world = copy_world_premature(world, player) base_world.override_bomb_check = True else: base_world = world @@ -1040,7 +1040,7 @@ def validate_layout(world, player): 'Pyramid Area': ['Pyramid Exit Ledge'] } - from Main import copy_world_limited + from Main import copy_world_premature from Utils import stack_size3a from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections @@ -1073,7 +1073,7 @@ def validate_layout(world, player): for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_limited(world) + base_world = copy_world_premature(world, player) explored_regions = list() if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]: From e83d0fa8e48fcf2bbf3c230200c276ae8edcd546 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 30 Oct 2022 16:04:53 -0500 Subject: [PATCH 32/59] Bumper cave fix --- EntranceShuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 5ac4a5ac..86121e74 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2138,8 +2138,8 @@ default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Exit (Wes ('Elder House (West)', 'Elder House Exit (West)'), ('Two Brothers House (East)', 'Two Brothers House Exit (East)'), ('Two Brothers House (West)', 'Two Brothers House Exit (West)'), - ('Bumper Cave (Top)', 'Bumper Cave (top)'), - ('Bumper Cave (Bottom)', 'Bumper Cave (bottom)'), + ('Bumper Cave (Top)', 'Bumper Cave Exit (Top)'), + ('Bumper Cave (Bottom)', 'Bumper Cave Exit (Bottom)'), ('Superbunny Cave (Top)', 'Superbunny Cave Exit (Top)'), ('Superbunny Cave (Bottom)', 'Superbunny Cave Exit (Bottom)'), ('Hookshot Cave', 'Hookshot Cave Front Exit'), From 8919729262bc42c394ad9fab24add56daec7fbe3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 31 Oct 2022 13:43:18 -0500 Subject: [PATCH 33/59] Merged light/dark world marking --- Main.py | 7 ++-- OverworldShuffle.py | 5 ++- Regions.py | 83 +++++++++++++++++---------------------------- 3 files changed, 35 insertions(+), 60 deletions(-) diff --git a/Main.py b/Main.py index d7646f83..d375b24b 100644 --- a/Main.py +++ b/Main.py @@ -14,7 +14,7 @@ from Items import ItemFactory from KeyDoorShuffle import validate_key_placement from OverworldGlitchRules import create_owg_connections from PotShuffle import shuffle_pots, shuffle_pot_switches -from Regions import create_regions, create_shops, mark_light_world_regions, mark_dark_world_regions, create_dungeon_regions, adjust_locations +from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations from OWEdges import create_owedges from OverworldShuffle import link_overworld, update_world_regions, create_flute_exits from EntranceShuffle import link_entrances @@ -204,10 +204,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_doors(world, player) - if world.mode[player] != 'inverted': - mark_light_world_regions(world, player) - else: - mark_dark_world_regions(world, player) + mark_light_dark_world_regions(world, player) logger.info(world.fish.translate("cli", "cli", "generating.itempool")) for player in range(1, world.players + 1): diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d88c8d58..aa877f5c 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -2,7 +2,7 @@ import RaceRandom as random, logging, copy from collections import OrderedDict, defaultdict from DungeonGenerator import GenerationException from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance -from Regions import mark_dark_world_regions, mark_light_world_regions +from Regions import mark_light_dark_world_regions from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel from OverworldGlitchRules import create_owg_connections from Utils import bidict @@ -843,8 +843,7 @@ def categorize_world_regions(world, player): for exitname in OWExitTypes[type]: world.get_entrance(exitname, player).spot_type = type - mark_light_world_regions(world, player) - mark_dark_world_regions(world, player) + mark_light_dark_world_regions(world, player) def update_world_regions(world, player): if world.owMixed[player]: diff --git a/Regions.py b/Regions.py index 027d4e1b..e9755a8c 100644 --- a/Regions.py +++ b/Regions.py @@ -1076,67 +1076,46 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address)) return ret -def mark_light_world_regions(world, player): +def mark_light_dark_world_regions(world, player): # cross world caves may have some sections marked as both in_light_world, and in_dark_work. # That is ok. the bunny logic will check for this case and incorporate special rules. - queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) - seen = set(queue) - while queue: - current = queue.popleft() - current.is_light_world = True - for exit in current.exits: - if exit.connected_region is None or exit.connected_region.type == RegionType.DarkWorld: # todo: remove none check - # Don't venture into the dark world - continue - if exit.connected_region not in seen: - seen.add(exit.connected_region) - queue.append(exit.connected_region) + # Note: I don't see why the order would matter, but the original Inverted code reversed the order + if world.mode[player] != 'inverted': + mark_light() + mark_dark() + else: + mark_dark() + mark_light() - queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) - seen = set(queue) - while queue: - current = queue.popleft() - current.is_dark_world = True - for exit in current.exits: - if exit.connected_region is not None: - if exit.connected_region.type == RegionType.LightWorld: - # Don't venture into the light world - continue - if exit.connected_region not in seen: - seen.add(exit.connected_region) - queue.append(exit.connected_region) - - -def mark_dark_world_regions(world, player): - # cross world caves may have some sections marked as both in_light_world, and in_dark_work. - # That is ok. the bunny logic will check for this case and incorporate special rules. - queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) - seen = set(queue) - while queue: - current = queue.popleft() - current.is_dark_world = True - for exit in current.exits: - if exit.connected_region is None or exit.connected_region.type == RegionType.LightWorld: # todo: remove none check - # 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) - - queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) - seen = set(queue) - while queue: - current = queue.popleft() - current.is_light_world = True - for exit in current.exits: - if exit.connected_region is not None: - if exit.connected_region.type == RegionType.DarkWorld: + def mark_light(): + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) + seen = set(queue) + while queue: + current = queue.popleft() + current.is_light_world = True + for exit in current.exits: + if exit.connected_region is None or exit.connected_region.type == RegionType.DarkWorld: # todo: remove none check # Don't venture into the dark world continue if exit.connected_region not in seen: seen.add(exit.connected_region) queue.append(exit.connected_region) + def mark_dark(): + queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld) + seen = set(queue) + while queue: + current = queue.popleft() + current.is_dark_world = True + for exit in current.exits: + if exit.connected_region is not None: + if exit.connected_region.type == RegionType.LightWorld: + # Don't venture into the light world + continue + if exit.connected_region not in seen: + seen.add(exit.connected_region) + queue.append(exit.connected_region) + def create_shops(world, player): world.shops[player] = [] From 246006d3949a69f96689793889da825cdf031155 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 31 Oct 2022 14:10:03 -0500 Subject: [PATCH 34/59] Merged light/dark world marking --- Regions.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Regions.py b/Regions.py index e9755a8c..d568497c 100644 --- a/Regions.py +++ b/Regions.py @@ -1079,14 +1079,6 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None def mark_light_dark_world_regions(world, player): # cross world caves may have some sections marked as both in_light_world, and in_dark_work. # That is ok. the bunny logic will check for this case and incorporate special rules. - # Note: I don't see why the order would matter, but the original Inverted code reversed the order - if world.mode[player] != 'inverted': - mark_light() - mark_dark() - else: - mark_dark() - mark_light() - def mark_light(): queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) seen = set(queue) @@ -1116,6 +1108,14 @@ def mark_light_dark_world_regions(world, player): seen.add(exit.connected_region) queue.append(exit.connected_region) + # Note: I don't see why the order would matter, but the original Inverted code reversed the order + if world.mode[player] != 'inverted': + mark_light() + mark_dark() + else: + mark_dark() + mark_light() + def create_shops(world, player): world.shops[player] = [] From 4639db8b2a20e6519e5ffb772b7e9240b0531d52 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 31 Oct 2022 16:11:56 -0500 Subject: [PATCH 35/59] Marking light/dark regions after OWR --- Main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Main.py b/Main.py index d375b24b..64c83655 100644 --- a/Main.py +++ b/Main.py @@ -187,6 +187,7 @@ def main(args, seed=None, fish=None): link_overworld(world, player) create_shops(world, player) update_world_regions(world, player) + mark_light_dark_world_regions(world, player) create_flute_exits(world, player) logger.info(world.fish.translate("cli","cli","shuffling.world")) From e4d760a00c9ccf37641f443c773d6bb87a636ce7 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 31 Oct 2022 16:13:52 -0500 Subject: [PATCH 36/59] Improvement to copy_world_premature --- Main.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Main.py b/Main.py index 64c83655..c96f3dcd 100644 --- a/Main.py +++ b/Main.py @@ -639,18 +639,24 @@ def copy_world_premature(world, player): connection.connect(target) # connect copied world - copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations + copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations() if loc.player == player} # caches all locations for region in world.regions: if region.player == player: copied_region = ret.get_region(region.name, region.player) - copied_region.is_light_world = region.is_light_world - copied_region.is_dark_world = region.is_dark_world - copied_region.dungeon = region.dungeon + if region.dungeon: + copied_region.dungeon = ret.get_dungeon(region.dungeon.name, region.player) copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations] for location in copied_region.locations: location.parent_region = copied_region for entrance in region.entrances: - ret.get_entrance(entrance.name, entrance.player).connect(copied_region) + copied_region.entrances.append(ret.get_entrance(entrance.name, entrance.player)) + for exit in region.exits: + if exit.connected_region: + dest_region = ret.get_region(exit.connected_region.name, region.player) + ret.get_entrance(exit.name, exit.player).connect(dest_region) + + from OverworldShuffle import categorize_world_regions + categorize_world_regions(ret, player) for item in world.precollected_items: if item.player == player: @@ -659,22 +665,19 @@ def copy_world_premature(world, player): for edge in world.owedges: if edge.player == player and edge.dest: copiededge = ret.check_for_owedge(edge.name, edge.player) - if copiededge is not None: - copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) + copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) for door in world.doors: if door.player == player: - entrance = ret.check_for_entrance(door.name, door.player) - if entrance is not None: - destdoor = ret.check_for_door(entrance.door.name, entrance.door.player) - entrance.door = destdoor - if destdoor is not None: - destdoor.entrance = entrance + copied_door = ret.check_for_door(door.name, door.player) + copied_entrance = ret.check_for_entrance(door.name, door.player) + copied_door.entrance = copied_entrance + copied_entrance.door = copied_door - ret.key_logic = world.key_logic.copy() + for player, portals in world.dungeon_portals.items(): + for portal in portals: + connect_portal(portal, ret, player) - from OverworldShuffle import categorize_world_regions - categorize_world_regions(ret, player) set_rules(ret, player) return ret From 734740a0a7efa1e6e53adea29a5e699ea6b2fbb3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 11:45:43 -0500 Subject: [PATCH 37/59] Improvement to copy_world_premature --- Main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Main.py b/Main.py index c96f3dcd..aa9572c9 100644 --- a/Main.py +++ b/Main.py @@ -616,6 +616,7 @@ def copy_world_premature(world, player): ret.owflutespots = world.owflutespots.copy() ret.prizes = world.prizes.copy() ret.restrict_boss_items = world.restrict_boss_items.copy() + ret.key_logic = world.key_logic.copy() ret.is_copied_world = True From 3e17b61428603dd4d9478ea33196f66326e156dc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 12:36:00 -0500 Subject: [PATCH 38/59] Improvement to copy_world --- Main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index aa9572c9..b5635fde 100644 --- a/Main.py +++ b/Main.py @@ -437,6 +437,8 @@ def copy_world(world): ret.owFluteShuffle = world.owFluteShuffle.copy() ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy() ret.open_pyramid = world.open_pyramid.copy() + ret.shufflelinks = world.shufflelinks.copy() + ret.shuffle_ganon = world.shuffle_ganon.copy() ret.boss_shuffle = world.boss_shuffle.copy() ret.enemy_shuffle = world.enemy_shuffle.copy() ret.enemy_health = world.enemy_health.copy() @@ -513,6 +515,8 @@ def copy_world(world): new_location.event = True if location.locked: new_location.locked = True + if location.skip: + new_location.skip = True # these need to be modified properly by set_rules new_location.access_rule = lambda state: True new_location.item_rule = lambda state: True @@ -599,6 +603,8 @@ def copy_world_premature(world, player): ret.owFluteShuffle = world.owFluteShuffle.copy() ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy() ret.open_pyramid = world.open_pyramid.copy() + ret.shufflelinks = world.shufflelinks.copy() + ret.shuffle_ganon = world.shuffle_ganon.copy() ret.boss_shuffle = world.boss_shuffle.copy() ret.enemy_shuffle = world.enemy_shuffle.copy() ret.enemy_health = world.enemy_health.copy() @@ -671,7 +677,7 @@ def copy_world_premature(world, player): for door in world.doors: if door.player == player: copied_door = ret.check_for_door(door.name, door.player) - copied_entrance = ret.check_for_entrance(door.name, door.player) + copied_entrance = ret.check_for_entrance(door.entrance.name, door.player) copied_door.entrance = copied_entrance copied_entrance.door = copied_door From dadf88bcda2a283fe11c39357e58e139e3c1c9b0 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 15:26:11 -0500 Subject: [PATCH 39/59] Fixed sweep_for_events to better detect new locations --- BaseClasses.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index d9da6466..0cc91899 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1006,18 +1006,17 @@ class CollectionState(object): if locations is None: locations = self.world.get_filled_locations() new_locations = True - checked_locations = 0 while new_locations: reachable_events = [location for location in locations if location.event and (not key_only or (not self.world.keyshuffle[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey)) and location.can_reach(self)] reachable_events = self._do_not_flood_the_keys(reachable_events) + new_locations = False for event in reachable_events: if (event.name, event.player) not in self.events: self.events.append((event.name, event.player)) self.collect(event.item, True, event) - new_locations = len(reachable_events) > checked_locations - checked_locations = len(reachable_events) + new_locations = True def can_reach_blue(self, region, player): From 7b25e1931f0b3b60b7913eb9dbeeb89217978df8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 15:30:23 -0500 Subject: [PATCH 40/59] Aerinon's keylogic fix for Vanilla Mire case --- BaseClasses.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 0cc91899..c508b8d0 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -757,7 +757,7 @@ class CollectionState(object): child_states.append(child_state) else: terminal_states.append(next_child) - common_regions, common_bc, common_doors, first = {}, {}, set(), True + common_regions, common_bc, common_doors, common_r_doors, first = {}, {}, set(), set(), True bc = self.blocked_connections[player] for term_state in terminal_states: t_rrp = term_state.reachable_regions[player] @@ -768,6 +768,8 @@ class CollectionState(object): common_bc = {x: y for x, y in t_bc.items() if x not in bc} common_doors = {x for x in term_state.opened_doors[player] - self.opened_doors[player] if valid_d_door(x)} + common_r_doors = {x for x in term_state.reached_doors[player] - self.reached_doors[player] + if valid_d_door(x)} else: cm_rrp = {x: y for x, y in t_rrp.items() if x not in rrp or y != rrp[x]} common_regions = {k: self.comb_crys(v, cm_rrp[k]) for k, v in common_regions.items() @@ -775,15 +777,19 @@ class CollectionState(object): common_bc.update({x: y for x, y in t_bc.items() if x not in bc and x not in common_bc}) common_doors &= {x for x in term_state.opened_doors[player] - self.opened_doors[player] if valid_d_door(x)} + common_r_doors &= {x for x in term_state.reached_doors[player] - self.reached_doors[player] + if valid_d_door(x)} terminal_queue = deque() - for door in common_doors: + for door in common_r_doors: pair = self.find_door_pair(player, dungeon_name, door) if door not in self.reached_doors[player]: self.door_counter[player][0][dungeon_name] += 1 self.reached_doors[player].add(door) if pair not in self.reached_doors[player]: self.reached_doors[player].add(pair) + for door in common_doors: + pair = self.find_door_pair(player, dungeon_name, door) self.opened_doors[player].add(door) if door in checklist: terminal_queue.append(checklist[door]) @@ -813,8 +819,8 @@ class CollectionState(object): missing_bc[blocked] = crystal for k in missing_bc: bc[k] = missing_bc[k] - self.record_dungeon_exploration(player, dungeon_name, checklist, - common_doors, missing_regions, missing_bc, paths) + self.record_dungeon_exploration(player, dungeon_name, checklist, common_doors, common_r_doors, + missing_regions, missing_bc, paths) checklist.clear() @staticmethod @@ -895,15 +901,17 @@ class CollectionState(object): exp_key = (prog_set, frozenset(checklist)) if dungeon_name in ec and exp_key in ec[dungeon_name]: # apply - common_doors, missing_regions, missing_bc, paths = ec[dungeon_name][exp_key] + common_doors, common_r_doors, missing_regions, missing_bc, paths = ec[dungeon_name][exp_key] terminal_queue = deque() - for door in common_doors: + for door in common_r_doors: pair = self.find_door_pair(player, dungeon_name, door) if door not in self.reached_doors[player]: self.door_counter[player][0][dungeon_name] += 1 self.reached_doors[player].add(door) if pair not in self.reached_doors[player]: self.reached_doors[player].add(pair) + for door in common_doors: + pair = self.find_door_pair(player, dungeon_name, door) self.opened_doors[player].add(door) if door in checklist: terminal_queue.append(checklist[door]) @@ -930,11 +938,11 @@ class CollectionState(object): return False def record_dungeon_exploration(self, player, dungeon_name, checklist, - common_doors, missing_regions, missing_bc, paths): + common_doors, common_r_doors, missing_regions, missing_bc, paths): ec = self.world.exp_cache[player] prog_set = self.reduce_prog_items(player, dungeon_name) exp_key = (prog_set, frozenset(checklist)) - ec[dungeon_name][exp_key] = (common_doors, missing_regions, missing_bc, paths) + ec[dungeon_name][exp_key] = (common_doors, common_r_doors, missing_regions, missing_bc, paths) def reduce_prog_items(self, player, dungeon_name): # todo: possibly could include an analysis of dungeon items req. like Hammer, Hookshot, etc From 45065134cde8c6c9fc8cac80c854eeb571a091de Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 15:41:39 -0500 Subject: [PATCH 41/59] Improved OWEdges in copy_world --- Main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index b5635fde..e99b4000 100644 --- a/Main.py +++ b/Main.py @@ -464,6 +464,7 @@ def copy_world(world): create_owg_connections(ret, player) create_flute_exits(ret, player) create_dungeon_regions(ret, player) + create_owedges(ret, player) create_shops(ret, player) create_rooms(ret, player) create_dungeons(ret, player) @@ -534,7 +535,11 @@ def copy_world(world): ret.state.prog_items = world.state.prog_items.copy() ret.state.stale = {player: True for player in range(1, world.players + 1)} - ret.owedges = world.owedges + for edge in world.owedges: + if edge.dest: + copiededge = ret.check_for_owedge(edge.name, edge.player) + copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) + ret.doors = world.doors for door in ret.doors: entrance = ret.check_for_entrance(door.name, door.player) From 9d46aaec58dfa2c1a6e24c8cfd3f6053433d78fe Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 15:42:10 -0500 Subject: [PATCH 42/59] Adding pre-aga Farmable locations to optional location list --- Main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Main.py b/Main.py index e99b4000..2cc834d3 100644 --- a/Main.py +++ b/Main.py @@ -726,6 +726,8 @@ def create_playthrough(world): # get locations containing progress items prog_locations = [location for location in world.get_filled_locations() if location.item.advancement] optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] + optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls + optional_locations.extend(['Lumberjack Area Bush Crab', 'South Pass Area Bush Crab']) # adding pre-aga bush crabs state_cache = [None] collection_spheres = [] state = CollectionState(world) From 34d77653d340ef0bdfbd3e72ad8b65cd02ec1f61 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 1 Nov 2022 15:05:28 -0600 Subject: [PATCH 43/59] More robust sweep_for_events (supports events that later become unreachable) Minor fix for AllowSmall key logic --- BaseClasses.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c508b8d0..c2f28d16 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -862,7 +862,21 @@ class CollectionState(object): else: door_candidates.append(door.name) return door_candidates - return None + door_candidates, skip = [], set() + if state.world.accessibility[player] != 'locations' and remaining_keys == 0: + key_logic = state.world.key_logic[player][dungeon_name] + for door, paired in key_logic.sm_doors.items(): + if door.name in key_logic.door_rules: + rule = key_logic.door_rules[door.name] + key = KeyRuleType.AllowSmall + if (key in rule.new_rules and key_total >= rule.new_rules[key] and door.name not in skip + and door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]): + if paired: + door_candidates.append((door.name, paired.name)) + skip.add(paired.name) + else: + door_candidates.append(door.name) + return door_candidates if door_candidates else None @staticmethod def print_rrp(rrp): @@ -1003,11 +1017,13 @@ class CollectionState(object): checked_locations = set([l for l in locations if l in self.locations_checked]) reachable_events = [location for location in locations if location.event and location.can_reach(self)] reachable_events = self._do_not_flood_the_keys(reachable_events) + found_new = False for event in reachable_events: if event not in checked_locations: self.events.append((event.name, event.player)) self.collect(event.item, True, event) - return len(reachable_events) > len(checked_locations) + found_new = True + return found_new def sweep_for_events(self, key_only=False, locations=None): # this may need improvement From 29a1e1fc120493d1938eaab80a18feb409341f45 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 16:09:24 -0500 Subject: [PATCH 44/59] Revert "Aerinon's keylogic fix for Vanilla Mire case" This reverts commit 7b25e1931f0b3b60b7913eb9dbeeb89217978df8. --- BaseClasses.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c2f28d16..9195b60e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -757,7 +757,7 @@ class CollectionState(object): child_states.append(child_state) else: terminal_states.append(next_child) - common_regions, common_bc, common_doors, common_r_doors, first = {}, {}, set(), set(), True + common_regions, common_bc, common_doors, first = {}, {}, set(), True bc = self.blocked_connections[player] for term_state in terminal_states: t_rrp = term_state.reachable_regions[player] @@ -768,8 +768,6 @@ class CollectionState(object): common_bc = {x: y for x, y in t_bc.items() if x not in bc} common_doors = {x for x in term_state.opened_doors[player] - self.opened_doors[player] if valid_d_door(x)} - common_r_doors = {x for x in term_state.reached_doors[player] - self.reached_doors[player] - if valid_d_door(x)} else: cm_rrp = {x: y for x, y in t_rrp.items() if x not in rrp or y != rrp[x]} common_regions = {k: self.comb_crys(v, cm_rrp[k]) for k, v in common_regions.items() @@ -777,19 +775,15 @@ class CollectionState(object): common_bc.update({x: y for x, y in t_bc.items() if x not in bc and x not in common_bc}) common_doors &= {x for x in term_state.opened_doors[player] - self.opened_doors[player] if valid_d_door(x)} - common_r_doors &= {x for x in term_state.reached_doors[player] - self.reached_doors[player] - if valid_d_door(x)} terminal_queue = deque() - for door in common_r_doors: + for door in common_doors: pair = self.find_door_pair(player, dungeon_name, door) if door not in self.reached_doors[player]: self.door_counter[player][0][dungeon_name] += 1 self.reached_doors[player].add(door) if pair not in self.reached_doors[player]: self.reached_doors[player].add(pair) - for door in common_doors: - pair = self.find_door_pair(player, dungeon_name, door) self.opened_doors[player].add(door) if door in checklist: terminal_queue.append(checklist[door]) @@ -819,8 +813,8 @@ class CollectionState(object): missing_bc[blocked] = crystal for k in missing_bc: bc[k] = missing_bc[k] - self.record_dungeon_exploration(player, dungeon_name, checklist, common_doors, common_r_doors, - missing_regions, missing_bc, paths) + self.record_dungeon_exploration(player, dungeon_name, checklist, + common_doors, missing_regions, missing_bc, paths) checklist.clear() @staticmethod @@ -915,17 +909,15 @@ class CollectionState(object): exp_key = (prog_set, frozenset(checklist)) if dungeon_name in ec and exp_key in ec[dungeon_name]: # apply - common_doors, common_r_doors, missing_regions, missing_bc, paths = ec[dungeon_name][exp_key] + common_doors, missing_regions, missing_bc, paths = ec[dungeon_name][exp_key] terminal_queue = deque() - for door in common_r_doors: + for door in common_doors: pair = self.find_door_pair(player, dungeon_name, door) if door not in self.reached_doors[player]: self.door_counter[player][0][dungeon_name] += 1 self.reached_doors[player].add(door) if pair not in self.reached_doors[player]: self.reached_doors[player].add(pair) - for door in common_doors: - pair = self.find_door_pair(player, dungeon_name, door) self.opened_doors[player].add(door) if door in checklist: terminal_queue.append(checklist[door]) @@ -952,11 +944,11 @@ class CollectionState(object): return False def record_dungeon_exploration(self, player, dungeon_name, checklist, - common_doors, common_r_doors, missing_regions, missing_bc, paths): + common_doors, missing_regions, missing_bc, paths): ec = self.world.exp_cache[player] prog_set = self.reduce_prog_items(player, dungeon_name) exp_key = (prog_set, frozenset(checklist)) - ec[dungeon_name][exp_key] = (common_doors, common_r_doors, missing_regions, missing_bc, paths) + ec[dungeon_name][exp_key] = (common_doors, missing_regions, missing_bc, paths) def reduce_prog_items(self, player, dungeon_name): # todo: possibly could include an analysis of dungeon items req. like Hammer, Hookshot, etc From 06216d977ba54dfd45d9cce9236a606381e73b7b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 16:14:01 -0500 Subject: [PATCH 45/59] Potential key lock logic fix --- Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index 07c7e6af..a7941814 100644 --- a/Rules.py +++ b/Rules.py @@ -2111,7 +2111,7 @@ def eval_small_key_door_main(state, door_name, dungeon, player): elif ruleType == KeyRuleType.AllowSmall: if (door_rule.small_location.item and door_rule.small_location.item.name == key_logic.small_key_name and door_rule.small_location.item.player == player): - return True # always okay if allow small is on + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) elif isinstance(ruleType, tuple): lock, lock_item = ruleType # this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't From 35d9b48cad91775bd2a51c6fd8398b805845dfd5 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 17:06:11 -0500 Subject: [PATCH 46/59] "Should" be a better copied world Prior to this, it was as close to DR as could be, but I'm pretty sure this is more correct --- Main.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Main.py b/Main.py index 2cc834d3..6deb977e 100644 --- a/Main.py +++ b/Main.py @@ -456,6 +456,7 @@ def copy_world(world): ret.owflutespots = world.owflutespots.copy() ret.prizes = world.prizes.copy() ret.restrict_boss_items = world.restrict_boss_items.copy() + ret.inaccessible_regions = world.inaccessible_regions.copy() for player in range(1, world.players + 1): create_regions(ret, player) @@ -503,6 +504,10 @@ def copy_world(world): location.parent_region = copied_region for entrance in region.entrances: ret.get_entrance(entrance.name, entrance.player).connect(copied_region) + for exit in region.exits: + if exit.connected_region: + dest_region = ret.get_region(exit.connected_region.name, region.player) + ret.get_entrance(exit.name, exit.player).connect(dest_region) # fill locations for location in world.get_locations(): @@ -540,14 +545,16 @@ def copy_world(world): copiededge = ret.check_for_owedge(edge.name, edge.player) copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) + # everything below this line is changing the original object, seems to be complicated to replicate similar objects organically ret.doors = world.doors for door in ret.doors: - entrance = ret.check_for_entrance(door.name, door.player) - if entrance is not None: - entrance.door = door + copied_entrance = ret.check_for_entrance(door.entrance.name, door.player) + door.entrance = copied_entrance + if copied_entrance: + copied_entrance.door = door + ret.paired_doors = world.paired_doors ret.rooms = world.rooms - ret.inaccessible_regions = world.inaccessible_regions ret.dungeon_layouts = world.dungeon_layouts ret.key_logic = world.key_logic ret.dungeon_portals = world.dungeon_portals From 46786ca4891c6950da1df26df144031e5af6e4b7 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 20:16:01 -0500 Subject: [PATCH 47/59] Renamed Tile Swap to Tile Flip --- BaseClasses.py | 6 +++--- EntranceShuffle.py | 2 +- OverworldGlitchRules.py | 4 ++-- OverworldShuffle.py | 22 +++++++++++----------- README.md | 24 ++++++++++++------------ resources/app/cli/lang/en.json | 4 ++-- resources/app/gui/lang/en.json | 2 +- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 9195b60e..f59086dd 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -3020,7 +3020,7 @@ class Spoiler(object): outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none': outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player])) - outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) + outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player])) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player])) @@ -3112,10 +3112,10 @@ class Spoiler(object): outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(self.maps[('flute', player)]['text'] + '\n\n') - # overworld tile swaps + # overworld tile flips for player in range(1, self.world.players + 1): if ('swaps', player) in self.maps: - outfile.write('OW Tile Swaps:\n') + outfile.write('OW Tile Flips:\n') break for player in range(1, self.world.players + 1): if ('swaps', player) in self.maps: diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 86121e74..d2e493d6 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -182,7 +182,7 @@ def link_entrances(world, player): if 0x03 in world.owswaps[player][0] == 0x05 in world.owswaps[player][0]: # if WDM and EDM are in same world connect_caves(world, lw_wdm_entrances + lw_edm_entrances, [], caves, player) else: - # place Old Man House in WDM if not swapped + # place Old Man House in WDM if not flipped if not world.is_tile_swapped(0x03, player): connect_caves(world, lw_wdm_entrances, [], list(Old_Man_House), player) else: diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 58f49b8f..29986c80 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -334,7 +334,7 @@ def set_owg_rules(player, world, connections, default_rule): glitch_regions = (['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs'], ['Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs']) -# same screen clips, no OWR tile swap implications +# same screen clips, no Tile Flip OWR implications boots_clips_local = [ # (name, from_region, to_region) ('Hera Ascent Clip', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists ('WDDM Bomb Clip', 'West Dark Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists @@ -386,7 +386,7 @@ boots_clips_local = [ # (name, from_region, to_region) # Common structure for cross-screen connections: # (name, from_region, to_region) <- each three consists of [LW, DW] -# This is so OWR Tile Swap can properly connect both connections, and simultaneously be aware of which one requires pearl +# This is so Tile Flip OWR can properly connect both connections, and simultaneously be aware of which one requires pearl # Note: Some clips have no way to reach the OOB area, and others have no way to get from the OOB area # to a proper destination, these are marked with 'None'; these connections will not be made boots_clips = [ diff --git a/OverworldShuffle.py b/OverworldShuffle.py index aa877f5c..a6fd48d3 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -73,7 +73,7 @@ def link_overworld(world, player): raise Exception('Cannot move a parallel edge without the other') new_mode = OpenStd.Open if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)) not in new_groups.keys(): - # when Links House tile is swapped, the DW edges need to get put into existing Standard group + # when Links House tile is flipped, the DW edges need to get put into existing Standard group new_mode = OpenStd.Standard new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][0].append(forward_set) new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][1].append(back_set) @@ -103,7 +103,7 @@ def link_overworld(world, player): else: raise NotImplementedError('Cannot move one side of a non-parallel connection') else: - raise NotImplementedError('Invalid OW Edge swap scenario') + raise NotImplementedError('Invalid OW Edge flip scenario') return new_groups tile_groups = define_tile_groups(world, player, False) @@ -143,7 +143,7 @@ def link_overworld(world, player): trimmed_groups = reorganize_groups(world, trimmed_groups, player) # tile shuffle - logging.getLogger('').debug('Swapping overworld tiles') + logging.getLogger('').debug('Flipping overworld tiles') if world.owMixed[player]: swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player) @@ -199,8 +199,8 @@ def link_overworld(world, player): if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'): if world.owCrossed[player] == 'grouped': - # the idea is to XOR the new swaps with the ones from Mixed so that non-parallel edges still work - # Polar corresponds to Grouped with no swaps in ow_crossed_tiles_mask + # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work + # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask ow_crossed_tiles_mask = [[],[],[]] crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player) ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])] @@ -252,7 +252,7 @@ def link_overworld(world, player): elif edge in parallel_links_new.inverse: crossed_edges.append(parallel_links_new.inverse[edge][0]) - # after tile swap and crossed, determine edges that need to swap + # after tile flip and crossed, determine edges that need to flip edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)] # whirlpool shuffle @@ -307,9 +307,9 @@ def link_overworld(world, player): logging.getLogger('').debug('Shuffling overworld layout') if world.owShuffle[player] == 'vanilla': - # apply outstanding swaps + # apply outstanding flips trimmed_groups = performSwap(trimmed_groups, edges_to_swap) - assert len(edges_to_swap) == 0, 'Not all edges were swapped successfully: ' + ', '.join(edges_to_swap) + assert len(edges_to_swap) == 0, 'Not all edges were flipped successfully: ' + ', '.join(edges_to_swap) # vanilla transitions groups = list(trimmed_groups.values()) @@ -619,8 +619,8 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): attempts = 1000 while True: - if attempts == 0: # expected to only occur with custom swaps - raise GenerationException('Could not find valid tile swaps') + if attempts == 0: # expected to only occur with custom flips + raise GenerationException('Could not find valid tile flips') # tile shuffle happens here removed = list() @@ -702,7 +702,7 @@ def define_tile_groups(world, player, do_grouped): if world.mode[player] == 'standard' and (0x1b in group or 0x2b in group or 0x2c in group): return False - # sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen + # sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \ and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \ or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): diff --git a/README.md b/README.md index 36abf9da..bf39ece6 100644 --- a/README.md +++ b/README.md @@ -55,18 +55,18 @@ This is a common sentiment among those who are unfamiliar with OWR's offerings. OWR definitely has a lot of options, and all of them by themselves are pretty simple to grasp, but combining multiple OWR options together increases the complexity and confusion in exponential fashion. Now, of course, some OWR options like Flute Shuffle can safely be combined at any level and isn't gonna make anything more complicated. But specifically, avoid combining these 3 options, at least when going for your first seed: - OW Layout Shuffle -- OW Tile Swap (Mixed) +- OW Tile Flip (Mixed) - Crossed OW ## "Any recommendations for a first-timer?" For a first (and second) seed... *and I say "second" because I feel like both of these recommendations I'm about to make have VERY different vibes, have different levels of challenge, but are both, of their own right, worthy of being tried at least once.* Your first OWR experience can be combined with any mode combination that you are already familiar with and have a lot of experience in playing. If you like Crosskeys and feel very comfortable running that, feel free to turn on all those settings in addition to ONE of these two options: -1. `OW Tile Swap (Mixed)` - Overly, a pretty easy-breezy mode, it doesn't require too much big brain, and is pretty managable even without proper logic tracking, as long as you at least have a standard map tracker. This is actually my favorite way to run OWR today +1. `OW Tile Flip (Mixed)` - Overly, a pretty easy-breezy mode, it doesn't require too much big brain, and is pretty managable even without proper logic tracking, as long as you at least have a standard map tracker. This is actually my favorite way to run OWR today - DO NOT turn on Layout or Whirlpool Shuffle, leave this on `Vanilla` - DO NOT turn on Crossed OWR - `Flute Shuffle` or `Bonk Drops` could be enabled if desired, altho I'd recommend against it, at least for a fresh viewpoint of Mixed OWR -2. `OW Layout Shuffle` - Set to `Parallel`. This is the original spirit and vision of OWR from the time of its own founding. It's definitely much more complicated to run than OW Tile Swap, so keep that in mind. +2. `OW Layout Shuffle` - Set to `Parallel`. This is the original spirit and vision of OWR from the time of its own founding. It's definitely much more complicated to run than OW Tile Flip, so keep that in mind. - `Starting Boots` - Either actual boots or pseudoboots, you will be spending a lot of time navigating the OW, so it's best to do it with the ability to run fast. - - DO NOT turn on OW Tile Swap (Mixed) + - DO NOT turn on OW Tile Flip (Mixed) - DO NOT turn on Crossed OWR - Enable `Whirlpool Shuffle` - Recommended to always be enabled with Layout Shuffle - Enable `Keep Similar Edges Together` - This just helps keep some of your sanity for a first experience @@ -134,7 +134,7 @@ Note: These changes do impact the logic. If you use `CodeTracker`, these Inverte Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md) ## Overworld Layout Shuffle (--ow_shuffle) -OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://media.discordapp.net/attachments/783989090017738753/857299555183362078/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Swap) +OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://media.discordapp.net/attachments/783989090017738753/857299555183362078/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Flip) ### Vanilla @@ -168,11 +168,11 @@ Transitions will remain same-world. ### Grouped -This option shuffles connections cross-world in the same manner as Tile Swap/Mixed, the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR. +This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR. ### Polar -Only effective if Mixed/Tile Swap is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are swapped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Mixed/Tile Swap works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Swap/Mixed` section for more details) +Only effective if Tile Flip (Mixed) is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are flipped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Tile Flip works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Flip / Mixed` section for more details) ### Limited @@ -194,17 +194,17 @@ This keeps similar edge transitions together. ie. The 2 west land edges of Potio Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW. -## Tile Swap / Mixed Overworld (--ow_mixed) +## Tile Flip / Mixed Overworld (--ow_mixed) -Tile Swap (often referred to as Mixed OWR) can be thought of as a hybrid of Open and Inverted, where OW tiles are randomly chosen to be swapped to become a part of the opposite world. When this occurs, that tile will use the Inverted version of that tile. For instance, if the Cave 45 tile becomes swapped, that means while walking around in the LW, you will find the screen that's south of Stumpy instead, and Cave 45 will instead be found in the DW; but like Inverted, the Cave 45 tile is modified to not have a ledge, this ensures that it will be possible to access it. +Tile Flip (often referred to as Mixed OWR) can be thought of as a hybrid of Open and Inverted, where OW tiles are randomly chosen to be flipped to become a part of the opposite world. When this occurs, that tile will use the Inverted version of that tile. For instance, if the Cave 45 tile becomes flipped, that means while walking around in the LW, you will find the screen that's south of Stumpy instead, and Cave 45 will instead be found in the DW; but like Inverted, the Cave 45 tile is modified to not have a ledge, this ensures that it will be possible to access it. Being that this uses concepts from Inverted, it will be important to review the OWR-exclusive changes that have been made to Inverted (often referred to as Inverted 2.0). See `Inverted Changes` for more details. During gameplay: - When on the OW, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW. - - When doing a map check (pressing X while on the OW), the tiles shown will reflect the swapped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world, beware of Desert/Mire and Eastern/PoD. Here is an image showing the difference of appearance when tiles are swapped on the [map check](https://media.discordapp.net/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif) screen. + - When doing a map check (pressing X while on the OW), the tiles shown will reflect the flipped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world, beware of Desert/Mire and Eastern/PoD. Here is an image showing the difference of appearance when tiles are flipped on the [map check](https://media.discordapp.net/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif) screen. -Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must swap together. +Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must flip together. ## Whirlpool Shuffle (--ow_whirlpool) @@ -350,7 +350,7 @@ This keeps similar edge transitions paired together with other pairs of transiti --ow_mixed ``` -This gives each OW tile a random chance to be swapped to the opposite world +This gives each OW tile a random chance to be flipped to the opposite world ``` --ow_fluteshuffle diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 5869f144..afc77cba 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -221,8 +221,8 @@ "None: No transitions are cross-world connections.", "Grouped: This ensures a two-plane separation so that you cannot", " walk around and access the other plane version by walking.", - "Polar: Only used when Mixed is enabled. This retains original", - " connections even when overworld tiles are swapped.", + "Polar: Only used when Tile Flip is enabled. This retains original", + " connections even when overworld tiles are flipped.", "Limited: Exactly nine transitions are randomly chosen as", " cross-world connections (to emulate the nine portals).", "Chaos: Every transition has a 50/50 chance to become a", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 9a92eb81..bcfba383 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -142,7 +142,7 @@ "randomizer.overworld.keepsimilar": "Keep Similar Edges Together", - "randomizer.overworld.mixed": "Tile Swap (Mixed)", + "randomizer.overworld.mixed": "Tile Flip (Mixed)", "randomizer.overworld.whirlpool": "Whirlpool Shuffle", From 2e72a306e733e770984e41c517d211c306b0656d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 1 Nov 2022 20:39:45 -0500 Subject: [PATCH 48/59] Updated Readme --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index bf39ece6..34d195ce 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,21 @@ During gameplay: Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must flip together. +## Tile Flip vs Crossed Explained +The above OWR options are very difficult to describe. The above descriptions are written in a way that is most correct even when these options are combined with more complicated modes. But, this section aims to simplify the explanation by assuming the user chooses a normal 'Open 7/7 Defeat Ganon' seed but with just one OWR setting enabled. + +Both of these options are very similar and often confused from each other. +- Tile Flip is a mode where some DW tiles are moved and BECOME part of the LW (and the LW counterparts become part of the DW). What does it mean to "become" part of a world? It means that it will inherit (NOT bring over) the properties of that world it is moving to (such as being able to flute, ability to use the mirror, or being susceptible to bunnying). +- Crossed on the other hand doesn't change the properties of tiles, instead it transports Link *physically?* across worlds upon transitioning. This also means that Link can be transformed into a bunny moving from tile to tile. +tldr: Tile Flip moves the tiles, Crossed moves Link + +So, let's run an example of 2 tiles, Link's House and the screen to the right of it. Transitioning right from Link's House: In vanilla, you get the Stone Bridge screen and Link stays his normal self and is just normal LW behavior. Now, let's assume Links House screen stays vanilla, but the tile to the right is getting Flipped or Crossed. +- In Tile Flip, you'd get the Hammer Bridge screen and Link would stay as Link and you'd be able to flute away from this screen if you had a flute. +- In Crossed, you'd get the same Hammer Bridge Screen, but this time Link would be transformed into a bunny, just like he'd normally be when on that tile. +- In Polar Crossed (when both Mixed and Crossed effects are applied together), you get the normal Stone Bridge screen, but Link is transformed to a bunny (because the Stone Bridge screen has moved to the DW AND Link is also moving across worlds). + +As you can see, things get pretty complicated when mixing modes together. Doing this can definitely create a very unique and interesting experience, but one that is very hard to grasp. And then beyond that there is OW Layout Shuffle, which is where transition destinations are shuffled, so Link will get transported to a different tile entirely, but the same rules apply when you eventually find the Stone/Hammer Bridge screen, you just likely won't find that screen thru a transition on Link's House screen. + ## Whirlpool Shuffle (--ow_whirlpool) When enabled, the whirlpool connections are shuffled. If Crossed OW is enabled, the whirlpools can also be cross-world as well. For Limited Crossed OW, this doesn't count towards the limited number of crossed edge transitions. From df897c53ff55d23d3e58f897165d990c64a953e4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 3 Nov 2022 12:35:06 -0500 Subject: [PATCH 49/59] Bandaid fix for universal key error --- BaseClasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index f59086dd..61cede10 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -857,7 +857,7 @@ class CollectionState(object): door_candidates.append(door.name) return door_candidates door_candidates, skip = [], set() - if state.world.accessibility[player] != 'locations' and remaining_keys == 0: + if state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name in state.world.key_logic[player]: key_logic = state.world.key_logic[player][dungeon_name] for door, paired in key_logic.sm_doors.items(): if door.name in key_logic.door_rules: From 254df9703bf7ff0f59da28b017e4cd826fa6cb70 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 3 Nov 2022 13:24:19 -0500 Subject: [PATCH 50/59] Improved copy_world_premature --- Main.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Main.py b/Main.py index 6deb977e..386ddf6e 100644 --- a/Main.py +++ b/Main.py @@ -688,11 +688,10 @@ def copy_world_premature(world, player): for door in world.doors: if door.player == player: - copied_door = ret.check_for_door(door.name, door.player) copied_entrance = ret.check_for_entrance(door.entrance.name, door.player) - copied_door.entrance = copied_entrance - copied_entrance.door = copied_door - + door.entrance = copied_entrance + if copied_entrance: + copied_entrance.door = door for player, portals in world.dungeon_portals.items(): for portal in portals: connect_portal(portal, ret, player) From a901234d9892b7cf2d9aea0d525a090a538b7414 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 3 Nov 2022 13:47:32 -0500 Subject: [PATCH 51/59] Version bump 0.2.11.1 --- CHANGELOG.md | 16 ++++++++++++++++ OverworldShuffle.py | 2 +- README.md | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a059de..211f24a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.2.11.1 +- Renamed mode: Tile Swap (Mixed) is now called Tile Flip (Mixed) +- Fixed generation errors due to issue with new Farmable item locations + +## 0.2.11.0 +- New OWR mode option: Free Terrain + - When used with OW Layout Shuffle, land and water transitions are combined into one pool and shuffled, this means land transitions can lead to water and vice versa. There is already tracker support for this change on DunkaTracker. Thanks @Catobat for the work on all of this. +- Glitched modes now have correct fake world behavior in all modes, including Inverted and even Mixed OWR +- Glitched + Mixed OWR now has correct logic (previously it was completely unimplemented) +- Lite ER is back and working! +- There was an issue with ER resulting in regions being inaccessible, this has been fixed. +- Changed Crossworld ER modes so that DW inaccessible areas are resolved before considering LW inaccessible areas, to give the algo a chance to make some of the LW areas accessible thru the DW +- Added new Bomb/Rupee farm logic, which uses pseudo-items, simplifies the graph searching and they even show up in the Playthru Calc (shows a logical path to Farmable Bombs if you ever question how you are able to get early bombs when the opening area is limited) +- Fixed issue with grabbing an item near Murahduhla and freezing the game +- Various logic corrections, including the DR Bumper Cave fix for pottery logic + ## 0.2.10.1 - Merged DR v1.0.1.3 - Fixed Zelda despawn in TT Prison diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a6fd48d3..a2254434 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.2.11.0' +version_number = '0.2.11.1' # branch indicator is intentionally different across branches version_branch = '-u' diff --git a/README.md b/README.md index 34d195ce..93ad893e 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ tldr: Tile Flip moves the tiles, Crossed moves Link So, let's run an example of 2 tiles, Link's House and the screen to the right of it. Transitioning right from Link's House: In vanilla, you get the Stone Bridge screen and Link stays his normal self and is just normal LW behavior. Now, let's assume Links House screen stays vanilla, but the tile to the right is getting Flipped or Crossed. - In Tile Flip, you'd get the Hammer Bridge screen and Link would stay as Link and you'd be able to flute away from this screen if you had a flute. - In Crossed, you'd get the same Hammer Bridge Screen, but this time Link would be transformed into a bunny, just like he'd normally be when on that tile. -- In Polar Crossed (when both Mixed and Crossed effects are applied together), you get the normal Stone Bridge screen, but Link is transformed to a bunny (because the Stone Bridge screen has moved to the DW AND Link is also moving across worlds). +- In Polar Crossed (when both Tile Flip and Crossed effects are applied together), you get the normal Stone Bridge screen, but Link is transformed to a bunny (because the Stone Bridge screen has moved to the DW AND Link is also moving across worlds). As you can see, things get pretty complicated when mixing modes together. Doing this can definitely create a very unique and interesting experience, but one that is very hard to grasp. And then beyond that there is OW Layout Shuffle, which is where transition destinations are shuffled, so Link will get transported to a different tile entirely, but the same rules apply when you eventually find the Stone/Hammer Bridge screen, you just likely won't find that screen thru a transition on Link's House screen. From 6fc4b046358cc19eeb3129bb59eca85356d92e2a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 3 Nov 2022 19:36:08 -0500 Subject: [PATCH 52/59] Yet another copy_world_premature fix --- Main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index 386ddf6e..48218a27 100644 --- a/Main.py +++ b/Main.py @@ -688,10 +688,12 @@ def copy_world_premature(world, player): for door in world.doors: if door.player == player: + copied_door = ret.check_for_door(door.name, door.player) copied_entrance = ret.check_for_entrance(door.entrance.name, door.player) - door.entrance = copied_entrance if copied_entrance: - copied_entrance.door = door + copied_entrance.door = copied_door + if copied_door: + copied_door.entrance = copied_entrance for player, portals in world.dungeon_portals.items(): for portal in portals: connect_portal(portal, ret, player) From 50c40cedd59dd0b6ff2c2a26eaa8d6fd9bc331dd Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 4 Nov 2022 11:14:08 -0500 Subject: [PATCH 53/59] Splitting up Stone Bridge region in prep for Mixed Districts --- OWEdges.py | 5 ++++- OverworldGlitchRules.py | 6 +++--- OverworldShuffle.py | 16 +++++++++------- Regions.py | 3 ++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/OWEdges.py b/OWEdges.py index 98736c08..4bdd95bd 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1113,7 +1113,8 @@ OWTileRegions = bidict({ 'Links House Area': 0x2c, - 'Stone Bridge Area': 0x2d, + 'Stone Bridge North Area': 0x2d, + 'Stone Bridge South Area': 0x2d, 'Stone Bridge Water': 0x2d, 'Tree Line Area': 0x2e, @@ -1606,6 +1607,8 @@ OWExitTypes = { 'Bat Cave Ledge Peg', 'Bat Cave Ledge Peg (East)', 'Maze Race Game', + 'Stone Bridge Northbound', + 'Stone Bridge Southbound', 'Desert Palace Statue Move', 'Checkerboard Ledge Approach', 'Desert Ledge Outer Rocks', diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 29986c80..f0aecbef 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -447,13 +447,13 @@ boots_clips = [ (['Suburb To Cliff Clip', 'Archery Game To Cliff Clip'], ['Kakariko Suburb Area', 'Archery Game Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), (['Central Bonk Rocks To Cliff Clip', 'Dark Bonk Rocks To Cliff Clip'], ['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['Links House To Cliff Clip', 'Bomb Shop To Cliff Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Central Cliffs', 'Dark Central Cliffs']), - (['Stone Bridge To Cliff Clip', 'Hammer Bridge To Cliff Clip'], ['Stone Bridge Area', 'Hammer Bridge South Area'], ['Central Cliffs', 'Dark Central Cliffs']), + (['Stone Bridge To Cliff Clip', 'Hammer Bridge To Cliff Clip'], ['Stone Bridge South Area', 'Hammer Bridge South Area'], ['Central Cliffs', 'Dark Central Cliffs']), (['Eastern Nook To Eastern Clip', None], ['Eastern Nook Area', None], ['Eastern Palace Area', 'Palace of Darkness Area']), (['Eastern Nook To Ice Cave FAWT Clip', 'PoD Nook To Shopping Mall FAWT Clip'], ['Eastern Nook Area', 'Palace of Darkness Nook Area'], ['Ice Cave Area', 'Shopping Mall Area']), - (['Links To Bridge FAWT Clip', 'Bomb Shop To Hammer Bridge FAWT Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Stone Bridge Area', 'Hammer Bridge North Area']), #fake flipper + (['Links To Bridge FAWT Clip', 'Bomb Shop To Hammer Bridge FAWT Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Stone Bridge North Area', 'Hammer Bridge North Area']), #fake flipper - (['Stone Bridge To Water Clip', 'Hammer Bridge To Water Clip'], ['Stone Bridge Area', 'Hammer Bridge North Area'], [None, 'Pyramid Water']), #fake flipper + (['Stone Bridge To Water Clip', 'Hammer Bridge To Water Clip'], ['Stone Bridge North Area', 'Hammer Bridge North Area'], [None, 'Pyramid Water']), #fake flipper (['Desert To Maze Race Clip', None], ['Desert Ledge', None], ['Maze Race Area', 'Dig Game Area']), (['Desert To Cliff Clip', 'Mire To Cliff Clip'], ['Desert Area', 'Misery Mire Area'], ['Desert Northeast Cliffs', 'Mire Northeast Cliffs']), diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a2254434..4329bdc4 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1219,6 +1219,8 @@ mandatory_connections = [# Intra-tile OW Connections ('Bat Cave Ledge Peg (East)', 'Blacksmith Area'), #hammer ('Maze Race Game', 'Maze Race Prize'), #pearl ('Maze Race Ledge Drop', 'Maze Race Area'), + ('Stone Bridge Southbound', 'Stone Bridge South Area'), + ('Stone Bridge Northbound', 'Stone Bridge North Area'), ('Desert Palace Statue Move', 'Desert Palace Stairs'), #book ('Desert Ledge Drop', 'Desert Area'), ('Desert Ledge Outer Rocks', 'Desert Palace Entrance (North) Spot'), #glove @@ -1611,21 +1613,21 @@ ow_connections = { ('Bomb Shop Cliff Ledge Drop', 'Links House Area') # OWG ]), 0x2d: ([ - ('Stone Bridge Mirror Spot', 'Stone Bridge Area'), - ('Stone Bridge South Mirror Spot', 'Stone Bridge Area'), + ('Stone Bridge Mirror Spot', 'Stone Bridge North Area'), + ('Stone Bridge South Mirror Spot', 'Stone Bridge South Area'), ('Hobo Mirror Spot', 'Stone Bridge Water'), - ('Stone Bridge East Ledge Drop', 'Stone Bridge Area'), # OWG + ('Stone Bridge East Ledge Drop', 'Stone Bridge North Area'), # OWG ('Hammer Bridge North Ledge Drop', 'Hammer Bridge North Area'), # OWG - ('Stone Bridge Cliff Ledge Drop', 'Stone Bridge Area'), # OWG + ('Stone Bridge Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG ('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area') # OWG ], [ ('Hammer Bridge North Mirror Spot', 'Hammer Bridge North Area'), ('Hammer Bridge South Mirror Spot', 'Hammer Bridge South Area'), ('Dark Hobo Mirror Spot', 'Hammer Bridge Water'), ('Stone Bridge East Ledge Drop', 'Hammer Bridge North Area'), # OWG - ('Hammer Bridge North Ledge Drop', 'Stone Bridge Area'), # OWG + ('Hammer Bridge North Ledge Drop', 'Stone Bridge North Area'), # OWG ('Stone Bridge Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG - ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge Area') # OWG + ('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge South Area') # OWG ]), 0x2e: ([ ('Tree Line Mirror Spot', 'Tree Line Area'), @@ -1976,7 +1978,7 @@ flute_data = { 0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b10, 0x04e8), 0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b30, 0x0770), 0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0b20, 0x08b8), - 0x2d: (['Stone Bridge Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0), + 0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0), 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58), 0x2f: (['Eastern Nook Area', 'Palace of Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30), 0x38: (['Desert Palace Teleporter Ledge', 'Misery Mire Teleporter Ledge'], 0x30, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070), diff --git a/Regions.py b/Regions.py index d568497c..cb3eef6d 100644 --- a/Regions.py +++ b/Regions.py @@ -84,7 +84,8 @@ def create_regions(world, player): create_lw_region(player, 'Flute Boy Pass', None, ['Stumpy Pass Mirror Spot', 'Flute Boy WS', 'Flute Boy SW']), create_lw_region(player, 'Central Bonk Rocks Area', None, ['Bonk Fairy (Light)', 'Dark Bonk Rocks Mirror Spot', 'Central Bonk Rocks NW', 'Central Bonk Rocks SW', 'Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES']), create_lw_region(player, 'Links House Area', None, ['Links House', 'Big Bomb Shop Mirror Spot', 'Links House NE', 'Links House WN', 'Links House WC', 'Links House WS', 'Links House SC', 'Links House ES']), - create_lw_region(player, 'Stone Bridge Area', None, ['Hammer Bridge North Mirror Spot', 'Hammer Bridge South Mirror Spot', 'Stone Bridge NC', 'Stone Bridge EN', 'Stone Bridge WS', 'Stone Bridge SC']), + create_lw_region(player, 'Stone Bridge North Area', None, ['Stone Bridge Southbound', 'Hammer Bridge North Mirror Spot', 'Stone Bridge NC', 'Stone Bridge EN']), + create_lw_region(player, 'Stone Bridge South Area', None, ['Stone Bridge Northbound', 'Hammer Bridge South Mirror Spot', 'Stone Bridge WS', 'Stone Bridge SC']), create_lw_region(player, 'Stone Bridge Water', None, ['Dark Hobo Mirror Spot', 'Stone Bridge WC', 'Stone Bridge EC'], 'Light World', Terrain.Water), create_lw_region(player, 'Hobo Bridge', ['Hobo'], ['Hobo EC'], 'Light World', Terrain.Water), create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'Statues Cliff Ledge Drop']), From f762d982a2e062a4809af8180cf698139a778a0d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 5 Nov 2022 18:26:52 -0500 Subject: [PATCH 54/59] Mixed Districts Initial Implementation --- OWEdges.py | 53 ++++++++++++++++ source/item/District.py | 137 +++++++++++++++++----------------------- 2 files changed, 111 insertions(+), 79 deletions(-) diff --git a/OWEdges.py b/OWEdges.py index 4bdd95bd..320f555b 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1910,4 +1910,57 @@ OWExitTypes = { 'South Pass Mirror Spot', 'Octoballoon Mirror Spot' ] +} + +OWTileDistricts = { + 0x00: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x02: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x03: (None, None, ('Death Mountain', 'Dark Death Mountain')), + 0x05: (None, None, ('Death Mountain', 'Dark Death Mountain')), + 0x07: (None, None, ('Death Mountain', 'Dark Death Mountain')), + 0x0a: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x0f: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x10: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x11: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x12: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x13: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x14: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x15: (['River Bend Area', 'Qirn Jump Area'], # if region in this list + ('Northwest Hyrule', 'Northwest Dark World'), # use this district + ('Eastern Hyrule', 'East Dark World')), # else this district + 0x16: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x17: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x18: (None, None, ('Kakariko', 'Northwest Dark World')), + 0x1a: (None, None, ('Northwest Hyrule', 'Northwest Dark World')), + 0x1b: (None, None, ('Central Hyrule', 'East Dark World')), + 0x1d: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x1e: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x22: (None, None, ('Kakariko', 'Northwest Dark World')), + 0x25: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x28: (None, None, ('Kakariko', 'South Dark World')), + 0x29: (None, None, ('Kakariko', 'South Dark World')), + 0x2a: (None, None, ('Central Hyrule', 'South Dark World')), + 0x2b: (None, None, ('Central Hyrule', 'South Dark World')), + 0x2c: (None, None, ('Central Hyrule', 'South Dark World')), + 0x2d: (['Stone Bridge North Area', 'Hammer Bridge North Area'], + ('Eastern Hyrule', 'East Dark World'), + ('Lake Hylia', 'South Dark World')), + 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], + ('Eastern Hyrule', 'East Dark World'), + ('Lake Hylia', 'South Dark World')), + 0x2f: (None, None, ('Eastern Hyrule', 'East Dark World')), + 0x30: (None, None, ('The Desert Area', 'The Mire Area')), + 0x32: (None, None, ('Central Hyrule', 'South Dark World')), + 0x33: (None, None, ('Central Hyrule', 'South Dark World')), + 0x34: (None, None, ('Central Hyrule', 'South Dark World')), + 0x35: (None, None, ('Lake Hylia', 'South Dark World')), + 0x37: (None, None, ('Lake Hylia', 'South Dark World')), + 0x3a: (None, None, ('The Desert Area', 'South Dark World')), + 0x3b: (None, None, ('Central Hyrule', 'South Dark World')), + 0x3c: (None, None, ('Central Hyrule', 'South Dark World')), + 0x3f: (None, None, ('Lake Hylia', 'South Dark World')), + 0x80: (['Master Sword Meadow'], + ('Northwest Hyrule', 'Northwest Dark World'), + ('Lake Hylia', 'South Dark World')), + 0x81: (None, None, ('Eastern Hyrule', 'East Dark World')) } \ No newline at end of file diff --git a/source/item/District.py b/source/item/District.py index 375138a3..d9e9e23e 100644 --- a/source/item/District.py +++ b/source/item/District.py @@ -2,15 +2,16 @@ from collections import deque from BaseClasses import CollectionState, RegionType from Dungeons import dungeon_table -from OWEdges import OWTileRegions +from OWEdges import OWTileRegions, OWTileDistricts class District(object): - def __init__(self, name, locations, entrances=None, dungeon=None): + def __init__(self, name, dungeon=None): self.name = name self.dungeon = dungeon - self.locations = locations - self.entrances = entrances if entrances else [] + self.regions = list() + self.locations = set() + self.entrances = list() self.sphere_one = False self.dungeons = set() @@ -25,86 +26,33 @@ def create_districts(world): def create_district_helper(world, player): districts = {} - kak_locations = {'Bottle Merchant', 'Kakariko Tavern', 'Maze Race'} - nw_lw_locations = {'Mushroom', 'Master Sword Pedestal'} - central_lw_locations = {'Sunken Treasure', 'Flute Spot'} - desert_locations = {'Purple Chest', 'Desert Ledge', 'Bombos Tablet'} - lake_locations = {'Hobo', 'Lake Hylia Island'} - east_lw_locations = {"Zora's Ledge", 'King Zora'} - lw_dm_locations = {'Old Man', 'Spectacle Rock', 'Ether Tablet', 'Floating Island'} - east_dw_locations = {'Pyramid', 'Catfish'} - south_dw_locations = {'Stumpy', 'Digging Game'} - voo_north_locations = {'Bumper Cave Ledge'} - ddm_locations = set() - - kak_entrances = ['Kakariko Well Cave', 'Bat Cave Cave', 'Elder House (East)', 'Elder House (West)', - 'Two Brothers House (East)', 'Two Brothers House (West)', 'Blinds Hideout', 'Chicken House', - 'Blacksmiths Hut', 'Sick Kids House', 'Snitch Lady (East)', 'Snitch Lady (West)', - 'Bush Covered House', 'Tavern (Front)', 'Light World Bomb Hut', 'Kakariko Shop', 'Library', - 'Kakariko Gamble Game', 'Kakariko Well Drop', 'Bat Cave Drop', 'Tavern North'] - nw_lw_entrances = ['North Fairy Cave', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', - 'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Kings Grave', 'Lost Woods Gamble', - 'Fortune Teller (Light)', 'Bonk Rock Cave', 'Lumberjack House', 'North Fairy Cave Drop', - 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'Graveyard Cave'] - central_lw_entrances = ['Links House', 'Hyrule Castle Entrance (South)', 'Hyrule Castle Entrance (West)', - 'Hyrule Castle Entrance (East)', 'Agahnims Tower', 'Hyrule Castle Secret Entrance Stairs', - 'Dam', 'Bonk Fairy (Light)', 'Light Hype Fairy', 'Hyrule Castle Secret Entrance Drop', - 'Cave 45'] - desert_entrances = ['Desert Palace Entrance (South)', 'Desert Palace Entrance (West)', - 'Desert Palace Entrance (North)', 'Desert Palace Entrance (East)', 'Desert Fairy', - 'Aginahs Cave', '50 Rupee Cave', 'Checkerboard Cave'] - lake_entrances = ['Capacity Upgrade', 'Mini Moldorm Cave', 'Good Bee Cave', '20 Rupee Cave', 'Ice Rod Cave', - 'Cave Shop (Lake Hylia)', 'Lake Hylia Fortune Teller'] - east_lw_entrances = ['Eastern Palace', 'Waterfall of Wishing', 'Lake Hylia Fairy', 'Sahasrahlas Hut', - 'Long Fairy Cave', 'Potion Shop'] - lw_dm_entrances = ['Tower of Hera', '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)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', - 'Paradox Cave (Top)', 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', - 'Spiral Cave', 'Spiral Cave (Bottom)', 'Hookshot Fairy', 'Mimic Cave'] - east_dw_entrances = ['Palace of Darkness', 'Pyramid Entrance', 'Pyramid Fairy', 'East Dark World Hint', - 'Palace of Darkness Hint', 'Dark Lake Hylia Fairy', 'Dark World Potion Shop', 'Pyramid Hole'] - south_dw_entrances = ['Ice Palace', 'Swamp Palace', 'Dark Lake Hylia Ledge Fairy', - 'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Hype Cave', - 'Bonk Fairy (Dark)', 'Archery Game', 'Big Bomb Shop', 'Dark Lake Hylia Shop', ] - voo_north_entrances = ['Thieves Town', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', - 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', - 'Bumper Cave (Bottom)', 'Bumper Cave (Top)', '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', - 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', - 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] - mire_entrances = ['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy'] - ddm_entrances = ['Turtle Rock', 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', - 'Turtle Rock Isolated Ledge Entrance', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)', - 'Hookshot Cave', 'Hookshot Cave Back Entrance', 'Ganons Tower', 'Spike Cave', - 'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Fairy'] - - if world.is_tile_swapped(0x1b, player): - east_dw_entrances.remove('Pyramid Entrance') - east_dw_entrances.remove('Pyramid Hole') - central_lw_entrances.append('Inverted Pyramid Entrance') - central_lw_entrances.append('Inverted Pyramid Hole') - - districts['Kakariko'] = District('Kakariko', kak_locations, entrances=kak_entrances) - districts['Northwest Hyrule'] = District('Northwest Hyrule', nw_lw_locations, entrances=nw_lw_entrances) - districts['Central Hyrule'] = District('Central Hyrule', central_lw_locations, entrances=central_lw_entrances) - districts['The Desert Area'] = District('Desert', desert_locations, entrances=desert_entrances) - districts['Lake Hylia'] = District('Lake Hylia', lake_locations, entrances=lake_entrances) - districts['Eastern Hyrule'] = District('Eastern Hyrule', east_lw_locations, entrances=east_lw_entrances) - districts['Death Mountain'] = District('Death Mountain', lw_dm_locations, entrances=lw_dm_entrances) - districts['East Dark World'] = District('East Dark World', east_dw_locations, entrances=east_dw_entrances) - districts['South Dark World'] = District('South Dark World', south_dw_locations, entrances=south_dw_entrances) - districts['Northwest Dark World'] = District('Northwest Dark World', voo_north_locations, - entrances=voo_north_entrances) - districts['The Mire Area'] = District('The Mire', set(), entrances=mire_entrances) - districts['Dark Death Mountain'] = District('Dark Death Mountain', ddm_locations, entrances=ddm_entrances) - districts.update({x: District(x, set(), dungeon=x) for x in dungeon_table.keys()}) + districts['Kakariko'] = District('Kakariko') + districts['Northwest Hyrule'] = District('Northwest Hyrule') + districts['Central Hyrule'] = District('Central Hyrule') + districts['The Desert Area'] = District('Desert') + districts['Lake Hylia'] = District('Lake Hylia') + districts['Eastern Hyrule'] = District('Eastern Hyrule') + districts['Death Mountain'] = District('Death Mountain') + districts['East Dark World'] = District('East Dark World') + districts['South Dark World'] = District('South Dark World') + districts['Northwest Dark World'] = District('Northwest Dark World') + districts['The Mire Area'] = District('The Mire') + districts['Dark Death Mountain'] = District('Dark Death Mountain') + districts.update({x: District(x, dungeon=x) for x in dungeon_table.keys()}) world.districts[player] = districts def resolve_districts(world): + def exclude_area(world, owid, area, player): + # area can be a region or entrancecurrently, could potentially be a problem later if name collision + std_regions = ['Pyramid Ledge', 'Pyramid Hole', 'Pyramid Entrance'] + inv_regions = ['Spiral Mimic Ledge Extend', 'Inverted Pyramid Hole', 'Inverted Pyramid Entrance'] + if (area in inv_regions and not world.is_tile_swapped(owid, player)) \ + or (area in std_regions and world.is_tile_swapped(owid, player)): + return True + return False + create_districts(world) state = CollectionState(world) state.sweep_for_events() @@ -113,18 +61,49 @@ def resolve_districts(world): inaccessible = [r for r in inaccessible_regions_std if not world.is_tile_swapped(OWTileRegions[r], player)] inaccessible = inaccessible + [r for r in inaccessible_regions_inv if world.is_tile_swapped(OWTileRegions[r], player)] check_set = find_reachable_locations(state, player) + + # adding regions to districts + for owid, (alt_regions, alt_districts, default_districts) in OWTileDistricts.items(): + idx = 0 if (world.mode[player] == 'inverted') == world.is_tile_swapped(owid, player) else 1 + if owid in OWTileRegions.inverse.keys(): + for region in OWTileRegions.inverse[owid]: + if exclude_area(world, owid, region, player): + continue + if alt_regions and region in alt_regions: + world.districts[player][alt_districts[idx]].regions.append(region) + else: + world.districts[player][default_districts[idx]].regions.append(region) + if owid + 0x40 in OWTileRegions.inverse.keys(): + for region in OWTileRegions.inverse[owid + 0x40]: + if exclude_area(world, owid, region, player): + continue + if alt_regions and region in alt_regions: + world.districts[player][alt_districts[(idx + 1) % 2]].regions.append(region) + else: + world.districts[player][default_districts[(idx + 1) % 2]].regions.append(region) + for name, district in world.districts[player].items(): if district.dungeon: layout = world.dungeon_layouts[player][district.dungeon] district.locations.update([l.name for r in layout.master_sector.regions for l in r.locations if not l.item and l.real]) else: + for region_name in district.regions: + region = world.get_region(region_name, player) + for location in region.locations: + if not location.item and location.real: + district.locations.add(location.name) + for exit in region.exits: + if exit.spot_type == 'Entrance' and not exclude_area(world, OWTileRegions[region.name], exit.name, player): + district.entrances.append(exit.name) for entrance in district.entrances: ent = world.get_entrance(entrance, player) queue = deque([ent.connected_region]) visited = set() while len(queue) > 0: region = queue.pop() + if not region: + RuntimeError(f'No region connected to entrance: {ent.name} Likely a missing entry in OWExitTypes') visited.add(region) if region.type == RegionType.Cave: for location in region.locations: From 47b92997df38c6444356f75fc86e562f4810051d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 5 Nov 2022 18:27:44 -0500 Subject: [PATCH 55/59] Adding missing OWG exits to classification list --- OWEdges.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OWEdges.py b/OWEdges.py index 320f555b..0a7126b6 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1602,6 +1602,7 @@ OWExitTypes = { 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Outer East Rock', + 'Blacksmith Water Exit', 'Wooden Bridge Bush (South)', 'Wooden Bridge Bush (North)', 'Bat Cave Ledge Peg', @@ -1664,6 +1665,7 @@ OWExitTypes = { 'Village of Outcasts Pegs', 'Grassy Lawn Pegs', 'Pyramid Crack', + 'Hammerpegs Water Exit', 'Broken Bridge Hammer Rock (South)', 'Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', @@ -1673,6 +1675,7 @@ OWExitTypes = { 'Archery Game Rock (North)', 'Frog Rock (Inner)', 'Archery Game Rock (South)', + 'Big Bomb Shop Water Exit', 'Hammer Bridge Pegs (North)', 'Hammer Bridge Pegs (South)', 'Hammer Bridge Pier', From 757fee822062224422c55dd2645cf47c5d594403 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 5 Nov 2022 21:56:40 -0500 Subject: [PATCH 56/59] Fixing spoiler log suppression for OW locations --- BaseClasses.py | 4 ++-- Fill.py | 2 +- ItemList.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 61cede10..b773438f 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2876,11 +2876,11 @@ class Spoiler(object): self.locations = OrderedDict() listed_locations = set() - lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld] + lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and not loc.skip] self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) listed_locations.update(lw_locations) - dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld] + dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and not loc.skip] self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) listed_locations.update(dw_locations) diff --git a/Fill.py b/Fill.py index a4468e7d..701a2c7c 100644 --- a/Fill.py +++ b/Fill.py @@ -511,7 +511,7 @@ def ensure_good_pots(world, write_skips=False): loc.item = ItemFactory('Rupees (5)', loc.item.player) # don't write out all pots to spoiler if write_skips: - if loc.type == LocationType.Pot and loc.item.name in valid_pot_items: + if loc.type in [LocationType.Pot, LocationType.Bonk] and loc.item.name in valid_pot_items: loc.skip = True diff --git a/ItemList.py b/ItemList.py index 86dfd92f..6be2606c 100644 --- a/ItemList.py +++ b/ItemList.py @@ -563,6 +563,7 @@ def create_farm_locations(world, player): loc.event = True loc.locked = True loc.address = None + loc.skip = True world.push_item(loc, ItemFactory(item_name, player), False) From 1756d22b5d1118c1a83f1eab5e6315a910bfcded Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 5 Nov 2022 21:57:15 -0500 Subject: [PATCH 57/59] Fixed Bonk Drop Locations in Districts --- ItemList.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ItemList.py b/ItemList.py index 6be2606c..5115f7fa 100644 --- a/ItemList.py +++ b/ItemList.py @@ -620,6 +620,7 @@ def create_dynamic_bonkdrop_locations(world, player): region = world.get_region(region_name, player) loc = Location(player, bonk_location, 0, region, hint_text) loc.type = LocationType.Bonk + loc.real = True loc.parent_region = region loc.address = 0x2abb00 + (bonk_prize_table[loc.name][0] * 6) + 3 From 371fe9f24fec3088c910ef34875d13c7cca60716 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 5 Nov 2022 22:18:11 -0500 Subject: [PATCH 58/59] Adding pre-aga Farmable locations to optional location list --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 48218a27..a5708194 100644 --- a/Main.py +++ b/Main.py @@ -735,7 +735,7 @@ def create_playthrough(world): prog_locations = [location for location in world.get_filled_locations() if location.item.advancement] optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile'] optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls - optional_locations.extend(['Lumberjack Area Bush Crab', 'South Pass Area Bush Crab']) # adding pre-aga bush crabs + optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs state_cache = [None] collection_spheres = [] state = CollectionState(world) From 4872f78b94e8f2da0ddca2ed25026520b9ee60f1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 7 Nov 2022 10:59:56 -0600 Subject: [PATCH 59/59] Version bump 0.2.11.2 --- CHANGELOG.md | 4 ++++ OverworldShuffle.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 211f24a2..bfb1bf50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.11.2 +- Implemented proper Districting with Mixed OWR, affecting Hints and District modes +- Suppressed item locations in spoiler log that are junk or logical + ## 0.2.11.1 - Renamed mode: Tile Swap (Mixed) is now called Tile Flip (Mixed) - Fixed generation errors due to issue with new Farmable item locations diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 4329bdc4..9f8152e6 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.2.11.1' +version_number = '0.2.11.2' # branch indicator is intentionally different across branches version_branch = '-u'