From 08ab32537bd6fae1ae0b2c44c928b8acd850a6e2 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Oct 2021 01:19:01 -0500 Subject: [PATCH 1/5] Implemented Whirlpool Shuffle --- BaseClasses.py | 6 + CLI.py | 3 +- Main.py | 2 + Mystery.py | 1 + OWEdges.py | 104 +++++++---- OverworldShuffle.py | 166 +++++++++++++----- Rom.py | 7 +- data/base2current.bps | Bin 141069 -> 141107 bytes mystery_example.yml | 3 + resources/app/cli/args.json | 4 + resources/app/cli/lang/en.json | 3 + resources/app/gui/lang/en.json | 5 + .../app/gui/randomize/overworld/widgets.json | 4 + source/classes/constants.py | 1 + source/gui/randomize/overworld.py | 2 +- 15 files changed, 225 insertions(+), 86 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 9f75f702..b6484e83 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -26,6 +26,7 @@ class World(object): self.owCrossed = owCrossed.copy() self.owKeepSimilar = {} self.owMixed = owMixed.copy() + self.owWhirlpoolShuffle = {} self.owFluteShuffle = {} self.shuffle = shuffle.copy() self.doorShuffle = doorShuffle.copy() @@ -76,6 +77,7 @@ class World(object): self.spoiler = Spoiler(self) self.lamps_needed_for_dark_rooms = 1 self.owswaps = {} + self.owwhirlpools = {} self.owedges = [] self._owedge_cache = {} self.owflutespots = {} @@ -105,6 +107,7 @@ class World(object): set_player_attr('_region_cache', {}) set_player_attr('player_names', []) set_player_attr('owswaps', [[],[],[]]) + set_player_attr('owwhirlpools', []) set_player_attr('remote_items', False) set_player_attr('required_medallions', ['Ether', 'Quake']) set_player_attr('swamp_patch_required', False) @@ -2693,6 +2696,7 @@ class Spoiler(object): 'ow_crossed': self.world.owCrossed, 'ow_keepsimilar': self.world.owKeepSimilar, 'ow_mixed': self.world.owMixed, + 'ow_whirlpool': self.world.owWhirlpoolShuffle, 'ow_fluteshuffle': self.world.owFluteShuffle, 'shuffle': self.world.shuffle, 'shuffleganon': self.world.shuffle_ganon, @@ -2784,6 +2788,7 @@ class Spoiler(object): outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No')) outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player]) outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No')) + outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_whirlpool'][player] else 'No')) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shuffleganon'][player] else 'No')) @@ -2807,6 +2812,7 @@ class Spoiler(object): if self.startinventory: outfile.write('Starting Inventory:'.ljust(line_width)) outfile.write('\n'.ljust(line_width+1).join(self.startinventory)) + outfile.write('\n\nRequirements:\n\n') for dungeon, medallion in self.medallions.items(): outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion) diff --git a/CLI.py b/CLI.py index 41b812f8..198325b7 100644 --- a/CLI.py +++ b/CLI.py @@ -94,7 +94,7 @@ def parse_cli(argv, no_defaults=False): 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_fluteshuffle', + 'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'bombbag', 'shuffleganon', @@ -149,6 +149,7 @@ def parse_settings(): "ow_crossed": "none", "ow_keepsimilar": False, "ow_mixed": False, + "ow_whirlpool": False, "ow_fluteshuffle": "vanilla", "shuffle": "vanilla", "shufflelinks": False, diff --git a/Main.py b/Main.py index 34bcc656..9827efb1 100644 --- a/Main.py +++ b/Main.py @@ -87,6 +87,7 @@ def main(args, seed=None, fish=None): world.crystals_ganon_orig = args.crystals_ganon.copy() world.crystals_gt_orig = args.crystals_gt.copy() world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owWhirlpoolShuffle = args.ow_whirlpool.copy() world.owFluteShuffle = args.ow_fluteshuffle.copy() world.open_pyramid = args.openpyramid.copy() world.boss_shuffle = args.shufflebosses.copy() @@ -406,6 +407,7 @@ def copy_world(world): ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() ret.owKeepSimilar = world.owKeepSimilar.copy() + ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy() ret.owFluteShuffle = world.owFluteShuffle.copy() ret.open_pyramid = world.open_pyramid.copy() ret.boss_shuffle = world.boss_shuffle.copy() diff --git a/Mystery.py b/Mystery.py index 933a4fc1..8f1b5158 100644 --- a/Mystery.py +++ b/Mystery.py @@ -138,6 +138,7 @@ def roll_settings(weights): ret.ow_crossed = get_choice('overworld_crossed') ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on' ret.ow_mixed = get_choice('overworld_swap') == 'on' + ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on' overworld_flute = get_choice('flute_shuffle') ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla' entrance_shuffle = get_choice('entrance_shuffle') diff --git a/OWEdges.py b/OWEdges.py index cfaa9c2c..b9064281 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -969,7 +969,7 @@ OWTileRegions = bidict({ }) OWTileGroups = { - ("Woods", "Regular"): ( + ("Woods", "Regular", "None"): ( [ 0x00, 0x2d, 0x80 ], @@ -977,7 +977,7 @@ OWTileGroups = { 0x40, 0x6d ] ), - ("Lumberjack", "Regular"): ( + ("Lumberjack", "Regular", "None"): ( [ 0x02 ], @@ -985,7 +985,7 @@ OWTileGroups = { 0x42 ] ), - ("West Mountain", "Regular"): ( + ("West Mountain", "Regular", "None"): ( [ 0x03 ], @@ -993,7 +993,7 @@ OWTileGroups = { 0x43 ] ), - ("East Mountain", "Regular"): ( + ("East Mountain", "Regular", "None"): ( [ 0x05 ], @@ -1001,23 +1001,31 @@ OWTileGroups = { 0x45 ] ), - ("East Mountain", "Entrance"): ( + ("East Mountain", "Entrance", "None"): ( [ - 0x07, + 0x07 ], [ 0x47 ] ), - ("Lake", "Regular"): ( + ("Lake", "Regular", "Zora"): ( [ - 0x0f, 0x35, 0x81 + 0x0f, 0x81 ], [ - 0x4f, 0x75 + 0x4f ] ), - ("Mountain Entry", "Regular"): ( + ("Lake", "Regular", "Lake"): ( + [ + 0x35 + ], + [ + 0x75 + ] + ), + ("Mountain Entry", "Regular", "None"): ( [ 0x0a ], @@ -1025,7 +1033,7 @@ OWTileGroups = { 0x4a ] ), - ("Woods Pass", "Regular"): ( + ("Woods Pass", "Regular", "None"): ( [ 0x10 ], @@ -1033,7 +1041,7 @@ OWTileGroups = { 0x50 ] ), - ("Fortune", "Regular"): ( + ("Fortune", "Regular", "None"): ( [ 0x11 ], @@ -1041,15 +1049,39 @@ OWTileGroups = { 0x51 ] ), - ("Whirlpools", "Regular"): ( + ("Whirlpools", "Regular", "Pond"): ( [ - 0x12, 0x15, 0x33, 0x3f + 0x12 ], [ - 0x52, 0x55, 0x73, 0x7f + 0x52 ] ), - ("Castle", "Entrance"): ( + ("Whirlpools", "Regular", "Witch"): ( + [ + 0x15 + ], + [ + 0x55 + ] + ), + ("Whirlpools", "Regular", "CWhirlpool"): ( + [ + 0x33 + ], + [ + 0x73 + ] + ), + ("Whirlpools", "Regular", "Southeast"): ( + [ + 0x3f + ], + [ + 0x7f + ] + ), + ("Castle", "Entrance", "None"): ( [ 0x13, 0x14 ], @@ -1057,7 +1089,7 @@ OWTileGroups = { 0x53, 0x54 ] ), - ("Castle", "Regular"): ( + ("Castle", "Regular", "None"): ( [ 0x1a, 0x1b ], @@ -1065,7 +1097,7 @@ OWTileGroups = { 0x5a, 0x5b ] ), - ("Witch", "Regular"): ( + ("Witch", "Regular", "None"): ( [ 0x16 ], @@ -1073,7 +1105,7 @@ OWTileGroups = { 0x56 ] ), - ("Water Approach", "Regular"): ( + ("Water Approach", "Regular", "None"): ( [ 0x17 ], @@ -1081,7 +1113,7 @@ OWTileGroups = { 0x57 ] ), - ("Village", "Regular"): ( + ("Village", "Regular", "None"): ( [ 0x18 ], @@ -1089,7 +1121,7 @@ OWTileGroups = { 0x58 ] ), - ("Wooden Bridge", "Regular"): ( + ("Wooden Bridge", "Regular", "None"): ( [ 0x1d ], @@ -1097,7 +1129,7 @@ OWTileGroups = { 0x5d ] ), - ("Eastern", "Regular"): ( + ("Eastern", "Regular", "None"): ( [ 0x1e ], @@ -1105,7 +1137,7 @@ OWTileGroups = { 0x5e ] ), - ("Blacksmith", "Regular"): ( + ("Blacksmith", "Regular", "None"): ( [ 0x22 ], @@ -1113,7 +1145,7 @@ OWTileGroups = { 0x62 ] ), - ("Dunes", "Regular"): ( + ("Dunes", "Regular", "None"): ( [ 0x25 ], @@ -1121,7 +1153,7 @@ OWTileGroups = { 0x65 ] ), - ("Game", "Regular"): ( + ("Game", "Regular", "None"): ( [ 0x28, 0x29 ], @@ -1129,7 +1161,7 @@ OWTileGroups = { 0x68, 0x69 ] ), - ("Grove", "Regular"): ( + ("Grove", "Regular", "None"): ( [ 0x2a ], @@ -1137,7 +1169,7 @@ OWTileGroups = { 0x6a ] ), - ("Central Bonk Rocks", "Regular"): ( + ("Central Bonk Rocks", "Regular", "None"): ( [ 0x2b ], @@ -1145,7 +1177,7 @@ OWTileGroups = { 0x6b ] ), - # ("Links", "Regular"): ( + # ("Links", "Regular", "None"): ( # [ # 0x2c # ], @@ -1153,7 +1185,7 @@ OWTileGroups = { # 0x6c # ] # ), - ("Tree Line", "Regular"): ( + ("Tree Line", "Regular", "None"): ( [ 0x2e ], @@ -1161,7 +1193,7 @@ OWTileGroups = { 0x6e ] ), - ("Nook", "Regular"): ( + ("Nook", "Regular", "None"): ( [ 0x2f ], @@ -1169,7 +1201,7 @@ OWTileGroups = { 0x6f ] ), - ("Desert", "Regular"): ( + ("Desert", "Regular", "None"): ( [ 0x30, 0x3a ], @@ -1177,7 +1209,7 @@ OWTileGroups = { 0x70, 0x7a ] ), - ("Grove Approach", "Regular"): ( + ("Grove Approach", "Regular", "None"): ( [ 0x32 ], @@ -1185,7 +1217,7 @@ OWTileGroups = { 0x72 ] ), - ("Hype", "Regular"): ( + ("Hype", "Regular", "None"): ( [ 0x34 ], @@ -1193,7 +1225,7 @@ OWTileGroups = { 0x74 ] ), - ("Shopping Mall", "Regular"): ( + ("Shopping Mall", "Regular", "None"): ( [ 0x37 ], @@ -1201,7 +1233,7 @@ OWTileGroups = { 0x77 ] ), - ("Swamp", "Regular"): ( + ("Swamp", "Regular", "None"): ( [ 0x3b ], @@ -1209,7 +1241,7 @@ OWTileGroups = { 0x7b ] ), - ("South Pass", "Regular"): ( + ("South Pass", "Regular", "None"): ( [ 0x3c ], diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 89ada285..9ed24210 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -187,6 +187,44 @@ def link_overworld(world, player): trimmed_groups = performSwap(trimmed_groups, crossed_edges) assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges) + # whirlpool shuffle + logging.getLogger('').debug('Shuffling whirlpools') + + if not world.owWhirlpoolShuffle[player]: + for (_, from_whirlpool, from_region), (_, to_whirlpool, to_region) in default_whirlpool_connections: + connect_simple(world, from_whirlpool, to_region, player) + connect_simple(world, to_whirlpool, from_region, player) + else: + whirlpool_candidates = [[],[]] + for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections: + if world.owCrossed[player] != 'none': + whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region))) + whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region))) + else: + if world.get_region(from_region, player).type == RegionType.LightWorld: + whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region))) + else: + whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region))) + + if world.get_region(to_region, player).type == RegionType.LightWorld: + whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region))) + else: + whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region))) + + # shuffle happens here + world.owwhirlpools[player] = [None] * 8 + whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ] + for whirlpools in whirlpool_candidates: + random.shuffle(whirlpools) + while len(whirlpools): + from_owid, from_whirlpool, from_region = whirlpools.pop() + to_owid, to_whirlpool, to_region = whirlpools.pop() + connect_simple(world, from_whirlpool, to_region, player) + connect_simple(world, to_whirlpool, from_region, player) + world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid + world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid + world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player) + # layout shuffle logging.getLogger('').debug('Shuffling overworld layout') connected_edges = [] @@ -387,22 +425,33 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): def shuffle_tiles(world, groups, result_list, player): swapped_edges = list() + valid_whirlpool_parity = False - # tile shuffle happens here - removed = list() - for group in groups.keys(): - if random.randint(0, 1): - removed.append(group) - - # save shuffled tiles to list - for group in groups.keys(): - if group not in removed: - (owids, lw_regions, dw_regions) = groups[group] - (exist_owids, exist_lw_regions, exist_dw_regions) = result_list - exist_owids.extend(owids) - exist_lw_regions.extend(lw_regions) - exist_dw_regions.extend(dw_regions) - result_list = [exist_owids, exist_lw_regions, exist_dw_regions] + while not valid_whirlpool_parity: + # tile shuffle happens here + removed = list() + for group in groups.keys(): + # if group[0] in ['Links', 'Central Bonk Rocks', 'Castle']: # TODO: Standard + Inverted + if random.randint(0, 1): + removed.append(group) + + # save shuffled tiles to list + new_results = [[],[],[]] + for group in groups.keys(): + if group not in removed: + (owids, lw_regions, dw_regions) = groups[group] + (exist_owids, exist_lw_regions, exist_dw_regions) = new_results + exist_owids.extend(owids) + exist_lw_regions.extend(lw_regions) + exist_dw_regions.extend(dw_regions) + + # check whirlpool parity + valid_whirlpool_parity = world.owCrossed[player] != 'none' or len(set(new_results[0]) & set({0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f})) % 2 == 0 + + (exist_owids, exist_lw_regions, exist_dw_regions) = result_list + exist_owids.extend(new_results[0]) + exist_lw_regions.extend(new_results[1]) + exist_dw_regions.extend(new_results[2]) # replace LW edges with DW ignore_list = list() #TODO: Remove ignore_list when special OW areas are included in pool @@ -426,36 +475,62 @@ def shuffle_tiles(world, groups, result_list, player): def reorganize_tile_groups(world, player): groups = {} - for (name, groupType) in OWTileGroups.keys(): + for (name, groupType, whirlpoolGroup) in OWTileGroups.keys(): if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \ or (world.mode[player] == 'standard' and world.shuffle[player] in ['lite', 'lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'): if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']: - groups[(name,)] = ([], [], []) + if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none': + groups[(name, whirlpoolGroup)] = ([], [], []) + else: + groups[(name,)] = ([], [], []) else: - groups[(name, groupType)] = ([], [], []) + if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none': + groups[(name, groupType, whirlpoolGroup)] = ([], [], []) + else: + groups[(name, groupType)] = ([], [], []) - for (name, groupType) in OWTileGroups.keys(): + for (name, groupType, whirlpoolGroup) in OWTileGroups.keys(): if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \ or (world.mode[player] == 'standard' and world.shuffle[player] in ['lite', 'lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'): - (lw_owids, dw_owids) = OWTileGroups[(name, groupType,)] + (lw_owids, dw_owids) = OWTileGroups[(name, groupType, whirlpoolGroup)] if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']: - (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name,)] - exist_owids.extend(lw_owids) - exist_owids.extend(dw_owids) - for owid in lw_owids: - exist_lw_regions.extend(OWTileRegions.inverse[owid]) - for owid in dw_owids: - exist_dw_regions.extend(OWTileRegions.inverse[owid]) - groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions) + if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none': + (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, whirlpoolGroup)] + exist_owids.extend(lw_owids) + exist_owids.extend(dw_owids) + for owid in lw_owids: + exist_lw_regions.extend(OWTileRegions.inverse[owid]) + for owid in dw_owids: + exist_dw_regions.extend(OWTileRegions.inverse[owid]) + groups[(name, whirlpoolGroup)] = (exist_owids, exist_lw_regions, exist_dw_regions) + else: + (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name,)] + exist_owids.extend(lw_owids) + exist_owids.extend(dw_owids) + for owid in lw_owids: + exist_lw_regions.extend(OWTileRegions.inverse[owid]) + for owid in dw_owids: + exist_dw_regions.extend(OWTileRegions.inverse[owid]) + groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions) else: - (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType)] - exist_owids.extend(lw_owids) - exist_owids.extend(dw_owids) - for owid in lw_owids: - exist_lw_regions.extend(OWTileRegions.inverse[owid]) - for owid in dw_owids: - exist_dw_regions.extend(OWTileRegions.inverse[owid]) - groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions) + if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none': + (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType, whirlpoolGroup)] + exist_owids.extend(lw_owids) + exist_owids.extend(dw_owids) + for owid in lw_owids: + exist_lw_regions.extend(OWTileRegions.inverse[owid]) + for owid in dw_owids: + exist_dw_regions.extend(OWTileRegions.inverse[owid]) + groups[(name, groupType, whirlpoolGroup)] = (exist_owids, exist_lw_regions, exist_dw_regions) + else: + (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType)] + exist_owids.extend(lw_owids) + exist_owids.extend(dw_owids) + for owid in lw_owids: + exist_lw_regions.extend(OWTileRegions.inverse[owid]) + for owid in dw_owids: + exist_dw_regions.extend(OWTileRegions.inverse[owid]) + groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions) return groups def remove_reserved(world, groupedlist, connected_edges, player): @@ -733,17 +808,7 @@ temporary_mandatory_connections = [ ] # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions -mandatory_connections = [# Whirlpool Connections - ('C Whirlpool', 'River Bend Water'), - ('River Bend Whirlpool', 'C Whirlpool Water'), - ('Lake Hylia Whirlpool', 'Zora Waterfall Water'), - ('Zora Whirlpool', 'Lake Hylia Water'), - ('Kakariko Pond Whirlpool', 'Octoballoon Water'), - ('Octoballoon Whirlpool', 'Kakariko Pond Area'), - ('Qirn Jump Whirlpool', 'Bomber Corner Water'), - ('Bomber Corner Whirlpool', 'Qirn Jump Water'), - - # Intra-tile OW Connections +mandatory_connections = [# Intra-tile OW Connections ('Lost Woods Bush (West)', 'Lost Woods East Area'), #pearl ('Lost Woods Bush (East)', 'Lost Woods West Area'), #pearl ('West Death Mountain Drop', 'West Death Mountain (Bottom)'), @@ -967,6 +1032,13 @@ mandatory_connections = [# Whirlpool Connections ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') #fake flipper ] +default_whirlpool_connections = [ + ((0x33, 'C Whirlpool', 'C Whirlpool Water'), (0x15, 'River Bend Whirlpool', 'River Bend Water')), + ((0x35, 'Lake Hylia Whirlpool', 'Lake Hylia Water'), (0x0f, 'Zora Whirlpool', 'Zora Waterfall Water')), + ((0x12, 'Kakariko Pond Whirlpool', 'Kakariko Pond Area'), (0x3f, 'Octoballoon Whirlpool', 'Octoballoon Water')), + ((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water')) +] + default_flute_connections = [ 0x0b, 0x16, 0x18, 0x2c, 0x2f, 0x38, 0x3b, 0x3f ] diff --git a/Rom.py b/Rom.py index 58cd90bc..5be364ec 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'c6c2a2d5d89a3c84871f58806bbb3acf' +RANDOMIZERBASEHASH = 'e9dea70e0a0b15bfa0ff7ecd63228a0c' class JsonRom(object): @@ -644,6 +644,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(snes_to_pc(0x0AB793 + o), data[11] & 0xff) # Y low byte rom.write_byte(snes_to_pc(0x0AB79B + o), data[11] // 0x100) # Y high byte + # patch whirlpools + if world.owWhirlpoolShuffle[player]: + owFlags |= 0x01 + write_int16s(rom, snes_to_pc(0x02EA5C), world.owwhirlpools[player]) + # patch overworld edges inverted_buffer = [0] * 0x82 owMode = 0 diff --git a/data/base2current.bps b/data/base2current.bps index daf77e69ccf89d2044d3fcf39dae204306a0edd0..aa914956d578657a299b8406d23a1e7e69766b86 100644 GIT binary patch delta 293 zcmV+=0owkJ&Iq&42(U>31oI6H^0Q3=kpc)Pf{8_lsu+&5-~;gk0h_bZ2m1s8cazQx z7XeqZ<_xO!6-|HuYa(pw0E$$w1HssR5I! zuW3c5i5P1*SGkwn0s%q-1Xs5q0|A@@0eP3c1py!p1*QcchLfcQg^I#3V3*+q0VO9M z5T>n3K*>PR6Qy0r1n>l_UwEdSfDNTx$rsQ9p9_GaB}(*j-$b`H1_8VR2RIL44=4ak zm-q+)ISZ`>0B{eG4v delta 273 zcmV+s0q*{@&Ipap2(U>31V9b7%kpcoxk+bFl@dE*gv)Bjw1Q}(Bs_H<9og$u? zpdwbsq?R2=n(Gx-l8HsL>bj=oB{z+m(~RVAOlsV1(yQ`0VOXA5T>n3K*>PR6Qy0r1n>l_ zOL(T8fDNTx$rsQ9n+t%Mr3HnG!Z2W?BTDoG;5@fa1_8VR2XGIN4 Date: Wed, 27 Oct 2021 01:19:55 -0500 Subject: [PATCH 2/5] Unnecessary tautology check --- BaseClasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index b6484e83..dd4f4133 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -115,7 +115,7 @@ class World(object): set_player_attr('ganon_at_pyramid', True) set_player_attr('ganonstower_vanilla', True) set_player_attr('sewer_light_cone', self.mode[player] == 'standard') - set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or ((self.mode[player] == 'inverted') != (0x05 in self.owswaps[player][0] and self.owMixed[player]))) + set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or ((self.mode[player] == 'inverted') != 0x05 in self.owswaps[player][0])) set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] or self.doorShuffle[player] not in ['vanilla']) set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) set_player_attr('fix_trock_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) From c8e04ff8b69ef8d8128e038f3a0da9ecea058dad Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Oct 2021 01:20:56 -0500 Subject: [PATCH 3/5] Fixing some Lite ER to not be treated as cross-world --- BaseClasses.py | 2 +- OverworldShuffle.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index dd4f4133..49310325 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -123,7 +123,7 @@ class World(object): set_player_attr('can_access_trock_front', None) set_player_attr('can_access_trock_big_chest', None) set_player_attr('can_access_trock_middle', None) - set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lite', 'lean', 'crossed', 'insanity', 'madness_legacy']) + set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lean', 'crossed', 'insanity', 'madness_legacy']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) set_player_attr('keyshuffle', False) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 9ed24210..6f9cf404 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -477,7 +477,7 @@ def reorganize_tile_groups(world, player): groups = {} for (name, groupType, whirlpoolGroup) in OWTileGroups.keys(): if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \ - or (world.mode[player] == 'standard' and world.shuffle[player] in ['lite', 'lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'): + or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'): if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']: if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none': groups[(name, whirlpoolGroup)] = ([], [], []) @@ -491,7 +491,7 @@ def reorganize_tile_groups(world, player): for (name, groupType, whirlpoolGroup) in OWTileGroups.keys(): if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \ - or (world.mode[player] == 'standard' and world.shuffle[player] in ['lite', 'lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'): + or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'): (lw_owids, dw_owids) = OWTileGroups[(name, groupType, whirlpoolGroup)] if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']: if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none': From 34e115a2639822bbbb041c7a2bc9cd5be84e1ff4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Oct 2021 01:22:03 -0500 Subject: [PATCH 4/5] Implemented Whirlpool Shuffle --- asm/owrando.asm | 63 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/asm/owrando.asm b/asm/owrando.asm index 8082d38f..d4a5ebd1 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -14,6 +14,10 @@ jsl OWEdgeTransition : nop #4 ;LDA $02A4E3,X : ORA $7EF3CA ;org $02e238 ;LDX #$9E : - DEX : DEX : CMP $DAEE,X : BNE - ;jsl OWSpecialTransition : nop #5 +; whirlpool shuffle cross world change +org $02b3bd +jsl OWWhirlpoolUpdate ;JSL $02EA6C + ; flute menu cancel org $0ab7af ;LDA $F2 : ORA $F0 : AND #$C0 jml OWFluteCancel2 : nop @@ -123,6 +127,14 @@ OWWorldCheck16: plx : and.w #$00ff : rtl } +OWWhirlpoolUpdate: +{ + jsl $02ea6c ; what we wrote over + lda.l OWFlags : and #$01 : beq + + ldx $8a : jsr OWWorldUpdate + + rtl +} + OWFluteCancel: { lda.l OWFlags+1 : and #$01 : bne + @@ -341,32 +353,39 @@ OWNewDestination: sep #$30 : lda OWOppSlotOffset,y : !add $04 : asl : and #$7f : sta $700 ; crossed OW shuffle - LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : beq .return - ldx $05 : lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return - sta.l $7ef3ca ; change world - lda #$38 : sta $012f ; play sfx - #$3b is an alternative - - ; toggle bunny mode - + lda $7ef357 : bne .nobunny - lda.l InvertedMode : bne .inverted - lda $7ef3ca : and.b #$40 : bra + - .inverted lda $7ef3ca : 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 - - .nobunny - lda $5d : cmp #$17 : bne + ; retain current state unless bunny - stz $5d - + stz $02e0 : stz $56 + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return + ldx $05 : jsr OWWorldUpdate .return lda $05 : sta $8a rep #$30 : rts } +OWWorldUpdate: ; x = owid of destination screen +{ + lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return + sta.l $7ef3ca ; change world + lda #$38 : sta $012f ; play sfx - #$3b is an alternative + + ; toggle bunny mode + + lda $7ef357 : bne .nobunny + lda.l InvertedMode : bne .inverted + lda $7ef3ca : and.b #$40 : bra + + .inverted lda $7ef3ca : 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 + + .nobunny + lda $5d : cmp #$17 : bne + ; retain current state unless bunny + stz $5d + + stz $02e0 : stz $56 + + .return + rts +} OWSpecialTransition: { LDX #$9E @@ -531,7 +550,7 @@ OWNorthEdges: ; Min Max Width Mid OW Slot/OWID VRAM *FREE* Dest Index dw $00a0, $00a0, $0000, $00a0, $0000, $0000, $0000, $0040 ;Lost Woods dw $0458, $0540, $00e8, $04cc, $0a0a, $0000, $0000, $0000 -dw $0f70, $0f90, $0020, $0f80, $0f0f, $0000, $0000, $0041 +dw $0f38, $0f60, $0028, $0f4c, $0f0f, $0000, $0000, $0041 dw $0058, $0058, $0000, $0058, $1010, $0000, $0000, $0001 dw $0178, $0178, $0000, $0178, $1010, $0000, $0000, $0002 dw $0388, $0388, $0000, $0388, $1111, $0000, $0000, $0003 From 152adde3cb071634d5d935947e77ec46f9a402cc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Oct 2021 01:42:32 -0500 Subject: [PATCH 5/5] Version bump 0.2.1.0 --- CHANGELOG.md | 3 +++ OverworldShuffle.py | 2 +- README.md | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f56cf7a6..f76fb7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 0.2.1.0 +- Implemented Whirlpool Shuffle + ### 0.2.0.0 - Massive overhaul of ER algorithm - Added 2 new ER modes (Lite and Lean) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 6f9cf404..6e3f52f2 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -3,7 +3,7 @@ from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSl from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel -__version__ = '0.2.0.0-u' +__version__ = '0.2.1.0-u' def link_overworld(world, player): # setup mandatory connections diff --git a/README.md b/README.md index edd0fd79..da4f5dad 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ Every transition independently is a candidate to be chosen as a cross-world conn 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. ### Chaos @@ -111,6 +113,10 @@ OW tiles are randomly chosen to become a part of the opposite world. When on the 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) +## Whirlpool Shuffle (--ow_whirlpool) + +When enabled, the whirlpool connections are shuffled. If Crossed OW is enabled, the whirlpools can also be cross-world as well. For Limited Crossed OW, this doesn't count towards the limited number of crossed edge transitions. + ## Flute Shuffle (--ow_fluteshuffle) When enabled, new flute spots are generated and gives the player the option to cancel out of the flute menu by pressing X.