diff --git a/BaseClasses.py b/BaseClasses.py index 58cfed79..d9da6466 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() @@ -1086,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]: @@ -1238,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): @@ -1387,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 region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world + 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]]: @@ -1608,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]: @@ -1634,6 +1497,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__()) @@ -1829,6 +1698,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 @@ -2680,6 +2552,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__()) @@ -2912,6 +2787,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 +3000,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/EntranceShuffle.py b/EntranceShuffle.py index d6c207f2..951c55f6 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: @@ -395,21 +395,19 @@ 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] - 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 + caves = [e for e in caves if e not in (lw_dungeons if not invFlag else dw_dungeons)] + 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] @@ -1137,6 +1135,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 +1162,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() @@ -1506,7 +1506,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) @@ -1525,21 +1526,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): @@ -2073,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)'), @@ -2141,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/ItemList.py b/ItemList.py index 7c252859..86dfd92f 100644 --- a/ItemList.py +++ b/ItemList.py @@ -505,6 +505,114 @@ 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): + loc = world.get_location_unsafe(f'{region_name} {loc_description}', player) + if loc: + loc.access_rule = lambda state: True + 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 + 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 f6465a08..0efc38ff 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 @@ -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() @@ -162,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) @@ -211,6 +210,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) @@ -228,9 +231,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")) @@ -433,6 +433,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() @@ -459,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() @@ -551,6 +552,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 @@ -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..98736c08 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, @@ -769,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, @@ -909,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, @@ -1161,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', @@ -1173,7 +1492,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', @@ -1219,9 +1537,9 @@ 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', '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..58f49b8f 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,20 +293,16 @@ 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)) - + 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 @@ -408,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) @@ -417,3 +329,172 @@ 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']), + (['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']), + + (['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 6cac6e75..28056169 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -3,10 +3,11 @@ 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 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 = '' @@ -106,7 +107,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 @@ -176,6 +177,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') @@ -639,9 +643,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 +779,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]: @@ -1019,7 +1025,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 @@ -1030,23 +1040,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 @@ -1237,7 +1230,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 @@ -1339,58 +1331,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')), @@ -1593,9 +1542,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'), @@ -1606,9 +1559,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'), @@ -1619,11 +1576,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'), @@ -1633,28 +1594,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'), @@ -1670,7 +1651,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'), @@ -1678,31 +1662,54 @@ 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'), + ('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 + ('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'), @@ -1713,7 +1720,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'), @@ -1722,7 +1734,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') @@ -1731,18 +1747,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') @@ -1919,7 +1943,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/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/README.md b/README.md index be396995..7bd24a96 100644 --- a/README.md +++ b/README.md @@ -30,47 +30,105 @@ 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 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. +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. @@ -78,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 @@ -102,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 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. 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) @@ -152,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: @@ -169,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 @@ -198,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 ``` @@ -222,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 diff --git a/Regions.py b/Regions.py index a37b7da5..027d4e1b 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), @@ -86,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']), @@ -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']), @@ -195,13 +197,13 @@ 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']), 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']), @@ -214,8 +216,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']), @@ -319,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/Rom.py b/Rom.py index a552ef90..b229a7ac 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '0aa072c020b0c1167c3e618b571efbbe' +RANDOMIZERBASEHASH = 'ff9c003ee6c1277437a4480d583282fd' 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/Rules.py b/Rules.py index d18c9ae5..07c7e6af 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) @@ -132,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): @@ -814,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 @@ -1256,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('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)) @@ -1474,6 +1476,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)) @@ -1784,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', diff --git a/asm/owrando.asm b/asm/owrando.asm index c709fc43..a046b8da 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 @@ -95,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 @@ -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 @@ -321,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 @@ -378,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 + @@ -507,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 @@ -525,45 +547,48 @@ 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: { - LDA.l OWMode : ORA.l OWMode+1 : BEQ .normal + LDA.l OWMode : ORA.l OWMode+1 : BEQ .unshuffled LDY.w $06FA : CPY.b #$7F - BEQ .normal + BEQ .unshuffled REP #$10 LDX.w $06F8 PHB : PHK : PLB @@ -571,15 +596,28 @@ OWEdgeTransition: PLB SEP #$30 RTL - .normal + + .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: { - 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 } @@ -593,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 @@ -611,7 +654,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 +670,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 +681,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 +690,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 +699,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 +707,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 +746,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 +801,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 @@ -778,22 +846,91 @@ OWWorldUpdate: ; x = owid of destination screen ; 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 - ; turn into bunny - lda $5d : cmp #$04 : beq + ; if swimming, continue - lda #$17 : sta $5d - + lda #$01 : sta $02e0 : sta $56 - bra .return + lda CurrentWorld : bra + + .inverted lda CurrentWorld : eor #$40 + + and #$40 : beq .nobunny + + LDA.w $0703 : BEQ + ; check if forced transition + CPY.b #$03 : BEQ .end_forced_whirlpool + LDA.b #$17 : STA.b $5D + LDA.b #$01 : STA.w $02E0 : STA.b $56 + LDA.w $0703 : BRA .end_forced_edge + + + CPY.b #$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 + 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 + 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 : STZ.w $0340 + 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 + 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 $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 : STZ.w $0340 : 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 +944,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 +1118,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 +1133,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 +1147,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 +1165,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 +1179,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 +1198,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 +1212,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 +1230,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 +1244,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 +1263,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 +1276,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 +1290,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 +1302,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 +1317,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 +1339,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 +1354,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 +1365,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 +1377,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 +1392,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 aa3ecc07..66c714ea 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ 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..95cf6ffb 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -145,11 +145,15 @@ "full" ] }, + "ow_terrain": { + "action": "store_true", + "type": "bool" + }, "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 aabea6c0..5869f144 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -214,9 +214,13 @@ "Full: Overworld transitions are shuffled, but both worlds", " will have an independent map shape." ], + "ow_terrain": [ + "With OW Layout Shuffle, this allows land and water edges to be connected." ], "ow_crossed": [ "This allows cross-world connections to occur on the overworld.", "None: No transitions are cross-world connections.", + "Grouped: This ensures a two-plane separation so that you cannot", + " walk around and access the other plane version by walking.", "Polar: Only used when Mixed is enabled. This retains original", " connections even when overworld tiles are swapped.", "Limited: Exactly nine transitions are randomly chosen as", @@ -226,7 +230,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..9a92eb81 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -131,10 +131,12 @@ "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", "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 dc0363c6..d349cfc0 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -20,15 +20,15 @@ "default": "vanilla", "options": [ "none", - "polar", "grouped", + "polar", "limited", "chaos" ] }, "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"]: