From e6c51589f4d31c832fc91364982ff2c13a0012f3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 11 May 2022 14:35:44 -0500 Subject: [PATCH 01/17] Adding Special OW areas to spoiler log --- OverworldShuffle.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 4f391dc6..0ab55738 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -145,7 +145,7 @@ def link_overworld(world, player): update_world_regions(world, player) # update spoiler - s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40)])) + s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40, 0x82)])) text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], s[0x00], s[0x03], s[0x05], s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], @@ -157,9 +157,9 @@ def link_overworld(world, player): s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25], s[0x3a],s[0x3b],s[0x3c], s[0x3f], s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], - s[0x32],s[0x33],s[0x34], s[0x37], + s[0x40], s[0x32],s[0x33],s[0x34], s[0x37], s[0x30], s[0x35], - s[0x3a],s[0x3b],s[0x3c], s[0x3f]) + s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('swaps', text_output, world.owswaps[player][0], player) # apply tile logical connections @@ -429,7 +429,7 @@ def link_overworld(world, player): # update spoiler new_spots = list(map(lambda o: flute_data[o][1], new_spots)) s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)])) - text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], + text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], s[0x00], s[0x03], s[0x05], s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], s[0x0a], s[0x0f], @@ -1807,6 +1807,26 @@ D(18)|s ss ss | +-+-+-+-+-+-+-+-+ E(20)| s s | D(18)| |s| |s| | F(28)|ssssssss| | s +-+ s +-+ s | G(30)|s ssss s| E(20)| |s| |s| | +H(38)| sss s| +-+-+-+-+-+-+-+-+ + +--------+ F(28)|s|s|s|s|s|s|s|s| + +-+ +-+-+-+-+-+-+-+-+ + Ped/Hobo: |s| G(30)| |s|s|s| |s| + +-+ | s +-+-+-+ s +-+ + Zora: |s| H(38)| |s|s|s| |s| + +-+ +---+-+-+-+---+-+""" + +flute_spoiler_table = \ +""" 0 1 2 3 4 5 6 7 + +---+-+---+---+-+ + 01234567 A(00)| |s| | |s| + +--------+ | s +-+ s | s +-+ +A(00)|s ss s s| B(08)| |s| | |s| +B(08)| s s| +-+-+-+-+-+-+-+-+ +C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s| +D(18)|s ss ss | +-+-+-+-+-+-+-+-+ +E(20)| s s | D(18)| |s| |s| | +F(28)|ssssssss| | s +-+ s +-+ s | +G(30)|s ssss s| E(20)| |s| |s| | H(38)| sss s| +-+-+-+-+-+-+-+-+ +--------+ F(28)|s|s|s|s|s|s|s|s| +-+-+-+-+-+-+-+-+ From 3993c210c0c007793c6d605ce74fa86faa81428c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 11 May 2022 14:37:37 -0500 Subject: [PATCH 02/17] Added Crossed OW to spoiler log when Grouped --- BaseClasses.py | 13 ++++++++++++- OverworldShuffle.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index d8e34b6e..ad8675a3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -3141,7 +3141,18 @@ class Spoiler(object): if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(self.maps[('swaps', player)]['text'] + '\n\n') - + + # crossed groups + for player in range(1, self.world.players + 1): + if ('groups', player) in self.maps: + outfile.write('OW Crossed Groups:\n') + break + for player in range(1, self.world.players + 1): + if ('groups', player) in self.maps: + if self.world.players > 1: + outfile.write(str('(Player ' + str(player) + ')\n')) # player name + outfile.write(self.maps[('groups', player)]['text'] + '\n\n') + if self.overworlds: # overworld transitions outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 0ab55738..13599dd1 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -179,6 +179,24 @@ def link_overworld(world, player): if world.owCrossed[player] == 'grouped': ow_crossed_tiles = [[],[],[]] crossed_edges = shuffle_tiles(world, tile_groups, ow_crossed_tiles, player) + + # update spoiler + s = list(map(lambda x: 'O' if x not in ow_crossed_tiles[0] else 'X', [i for i in range(0x40, 0x82)])) + text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], + s[0x00], s[0x03], s[0x05], + s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], + s[0x0a], s[0x0f], + s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], + s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e], + s[0x22], s[0x25], s[0x1a], s[0x1d], + s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e], + s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25], + s[0x3a],s[0x3b],s[0x3c], s[0x3f], + s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], + s[0x40], s[0x32],s[0x33],s[0x34], s[0x37], + s[0x30], s[0x35], + s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f]) + world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player) elif world.owCrossed[player] in ['limited', 'chaos']: crossed_edges = list() crossed_candidates = list() From ce1e3c23b04b55e9d0abebdc9dc8372754ae0fb9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 11 May 2022 14:38:33 -0500 Subject: [PATCH 03/17] Including other OW modes that affect the output filename --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 0489b8bd..8453c90a 100644 --- a/Main.py +++ b/Main.py @@ -135,7 +135,7 @@ def main(args, seed=None, fish=None): world.player_names[player].append(name) logger.info('') - if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] not in ['none', 'polar'] or world.owMixed[1] or str(args.outputname).startswith('M'): + if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] not in ['none', 'polar'] or world.owMixed[1] or world.owWhirlpoolShuffle[1] or world.owFluteShuffle[1] != 'vanilla' or str(args.outputname).startswith('M'): outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' else: outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' From 377660a1f5b2b4c098eb2468086f787d58708dfc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 14 May 2022 11:23:30 -0500 Subject: [PATCH 04/17] Merge branch 'pr/8' into OverworldShuffleDev --- OWEdges.py | 283 --------------------------------------- OverworldShuffle.py | 315 +++++++++++++++++++++++++++++++------------- 2 files changed, 221 insertions(+), 377 deletions(-) diff --git a/OWEdges.py b/OWEdges.py index b79445a7..69c26b90 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -984,289 +984,6 @@ OWTileRegions = bidict({ 'Zoras Domain': 0x81 }) -OWTileGroups = { - ("Woods", "Regular", "None"): ( - [ - 0x00, 0x2d, 0x80 - ], - [ - 0x40, 0x6d - ] - ), - ("Lumberjack", "Regular", "None"): ( - [ - 0x02 - ], - [ - 0x42 - ] - ), - ("Mountain Entry", "Entrance", "None"): ( - [ - 0x03 - ], - [ - 0x43 - ] - ), - ("East Mountain", "Regular", "None"): ( - [ - 0x05 - ], - [ - 0x45 - ] - ), - ("East Mountain", "Entrance", "None"): ( - [ - 0x07 - ], - [ - 0x47 - ] - ), - ("Lake", "Regular", "Zora"): ( - [ - 0x0f, 0x81 - ], - [ - 0x4f - ] - ), - ("Lake", "Regular", "Lake"): ( - [ - 0x35 - ], - [ - 0x75 - ] - ), - ("Mountain Entry", "Regular", "None"): ( - [ - 0x0a - ], - [ - 0x4a - ] - ), - ("Woods Pass", "Regular", "None"): ( - [ - 0x10 - ], - [ - 0x50 - ] - ), - ("Fortune", "Regular", "None"): ( - [ - 0x11 - ], - [ - 0x51 - ] - ), - ("Whirlpools", "Regular", "Pond"): ( - [ - 0x12 - ], - [ - 0x52 - ] - ), - ("Whirlpools", "Regular", "Witch"): ( - [ - 0x15 - ], - [ - 0x55 - ] - ), - ("Whirlpools", "Regular", "CWhirlpool"): ( - [ - 0x33 - ], - [ - 0x73 - ] - ), - ("Whirlpools", "Regular", "Southeast"): ( - [ - 0x3f - ], - [ - 0x7f - ] - ), - ("Castle", "Entrance", "None"): ( - [ - 0x13, 0x14 - ], - [ - 0x53, 0x54 - ] - ), - ("Castle", "Regular", "None"): ( - [ - 0x1a, 0x1b - ], - [ - 0x5a, 0x5b - ] - ), - ("Witch", "Regular", "None"): ( - [ - 0x16 - ], - [ - 0x56 - ] - ), - ("Water Approach", "Regular", "None"): ( - [ - 0x17 - ], - [ - 0x57 - ] - ), - ("Village", "Regular", "None"): ( - [ - 0x18 - ], - [ - 0x58 - ] - ), - ("Wooden Bridge", "Regular", "None"): ( - [ - 0x1d - ], - [ - 0x5d - ] - ), - ("Eastern", "Regular", "None"): ( - [ - 0x1e - ], - [ - 0x5e - ] - ), - ("Blacksmith", "Regular", "None"): ( - [ - 0x22 - ], - [ - 0x62 - ] - ), - ("Dunes", "Regular", "None"): ( - [ - 0x25 - ], - [ - 0x65 - ] - ), - ("Game", "Regular", "None"): ( - [ - 0x28, 0x29 - ], - [ - 0x68, 0x69 - ] - ), - ("Grove", "Regular", "None"): ( - [ - 0x2a - ], - [ - 0x6a - ] - ), - ("Central Bonk Rocks", "Regular", "None"): ( - [ - 0x2b - ], - [ - 0x6b - ] - ), - ("Links", "Regular", "None"): ( - [ - 0x2c - ], - [ - 0x6c - ] - ), - ("Tree Line", "Regular", "None"): ( - [ - 0x2e - ], - [ - 0x6e - ] - ), - ("Nook", "Regular", "None"): ( - [ - 0x2f - ], - [ - 0x6f - ] - ), - ("Desert", "Regular", "None"): ( - [ - 0x30, 0x3a - ], - [ - 0x70, 0x7a - ] - ), - ("Grove Approach", "Regular", "None"): ( - [ - 0x32 - ], - [ - 0x72 - ] - ), - ("Hype", "Regular", "None"): ( - [ - 0x34 - ], - [ - 0x74 - ] - ), - ("Shopping Mall", "Regular", "None"): ( - [ - 0x37 - ], - [ - 0x77 - ] - ), - ("Swamp", "Regular", "None"): ( - [ - 0x3b - ], - [ - 0x7b - ] - ), - ("South Pass", "Regular", "None"): ( - [ - 0x3c - ], - [ - 0x7c - ] - ) -} - parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW', 'Lost Woods SC': 'Skull Woods SC', 'Lost Woods SE': 'Skull Woods SE', diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 13599dd1..83758f16 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1,9 +1,10 @@ import RaceRandom as random, logging, copy -from collections import OrderedDict +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, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel +from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel +from Utils import bidict version_number = '0.2.7.2' version_branch = '-u' @@ -101,8 +102,8 @@ def link_overworld(world, player): else: raise NotImplementedError('Invalid OW Edge swap scenario') return new_groups - - tile_groups = reorganize_tile_groups(world, player) + + tile_groups = define_tile_groups(world, player, False) trimmed_groups = copy.deepcopy(OWEdgeGroups) swapped_edges = list() @@ -133,14 +134,15 @@ def link_overworld(world, player): trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC']) trimmed_groups[group] = (forward_edges, back_edges) + connected_edges = [] + if world.owShuffle[player] != 'vanilla': + trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) + trimmed_groups = reorganize_groups(world, trimmed_groups, player) + # tile shuffle logging.getLogger('').debug('Swapping overworld tiles') if world.owMixed[player]: - swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], player) - - # move swapped regions/edges to other world - trimmed_groups = performSwap(trimmed_groups, swapped_edges) - assert len(swapped_edges) == 0, 'Not all edges were swapped successfully: ' + ', '.join(swapped_edges ) + swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player) update_world_regions(world, player) @@ -175,13 +177,30 @@ def link_overworld(world, player): # crossed shuffle logging.getLogger('').debug('Crossing overworld edges') - if world.owCrossed[player] in ['grouped', 'limited', 'chaos']: + crossed_edges = list() + + # more Maze Race/Suburb/Frog/Dig Game fixes + parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken) + if world.owKeepSimilar[player]: + del parallel_links_new['Maze Race ES'] + del parallel_links_new['Kakariko Suburb WS'] + + #TODO: Revisit with changes to Limited/Allowed + if world.owCrossed[player] not in ['none', 'grouped', 'polar', 'chaos']: + for edge in swapped_edges: + if edge not in parallel_links_new and edge not in parallel_links_new.inverse: + crossed_edges.append(edge) + + if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'): if world.owCrossed[player] == 'grouped': - ow_crossed_tiles = [[],[],[]] - crossed_edges = shuffle_tiles(world, tile_groups, ow_crossed_tiles, player) + # the idea is to XOR the new swaps with the ones from Mixed so that non-parallel edges still work + # Polar corresponds to Grouped with no swaps in ow_crossed_tiles_mask + ow_crossed_tiles_mask = [[],[],[]] + crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player) + ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])] # update spoiler - s = list(map(lambda x: 'O' if x not in ow_crossed_tiles[0] else 'X', [i for i in range(0x40, 0x82)])) + s = list(map(lambda x: 'O' if x not in ow_crossed_tiles else 'X', [i for i in range(0x40, 0x82)])) text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], s[0x00], s[0x03], s[0x05], s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], @@ -197,38 +216,38 @@ def link_overworld(world, player): s[0x30], s[0x35], s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player) - elif world.owCrossed[player] in ['limited', 'chaos']: - crossed_edges = list() + else: crossed_candidates = list() for group in trimmed_groups.keys(): (mode, wrld, dir, terrain, parallel, count) = group - if parallel == IsParallel.Yes and wrld == WorldType.Light and (mode == OpenStd.Open or world.mode[player] != 'standard'): + if wrld == WorldType.Light and mode != OpenStd.Standard: for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): - if world.owKeepSimilar[player]: - if world.owCrossed[player] == 'chaos' and random.randint(0, 1): - for edge in forward_set: - crossed_edges.append(edge) - elif world.owCrossed[player] == 'limited': - crossed_candidates.append(forward_set) - else: - for edge in forward_set: + if forward_set[0] in parallel_links_new or forward_set[0] in parallel_links_new.inverse: + if world.owKeepSimilar[player]: if world.owCrossed[player] == 'chaos' and random.randint(0, 1): - crossed_edges.append(edge) + for edge in forward_set: + crossed_edges.append(edge) elif world.owCrossed[player] == 'limited': - crossed_candidates.append([edge]) + crossed_candidates.append(forward_set) + else: + for edge in forward_set: + if world.owCrossed[player] == 'chaos' and random.randint(0, 1): + crossed_edges.append(edge) + elif world.owCrossed[player] == 'limited': + crossed_candidates.append([edge]) if world.owCrossed[player] == 'limited': random.shuffle(crossed_candidates) for edge_set in crossed_candidates[:9]: for edge in edge_set: crossed_edges.append(edge) for edge in copy.deepcopy(crossed_edges): - if edge in parallel_links: - crossed_edges.append(parallel_links[edge]) - elif edge in parallel_links.inverse: - crossed_edges.append(parallel_links.inverse[edge][0]) - - trimmed_groups = performSwap(trimmed_groups, crossed_edges) - assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges) + if edge in parallel_links_new: + crossed_edges.append(parallel_links_new[edge]) + elif edge in parallel_links_new.inverse: + crossed_edges.append(parallel_links_new.inverse[edge][0]) + + # after tile swap and crossed, determine edges that need to swap + edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)] # whirlpool shuffle logging.getLogger('').debug('Shuffling whirlpools') @@ -251,14 +270,14 @@ def link_overworld(world, player): else: if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \ or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \ - or (world.owCrossed[player] == 'grouped' and ((from_owid < 0x40) == (from_owid not in ow_crossed_tiles[0]))): + or (world.owCrossed[player] == 'grouped' and ((world.get_region(from_region, player).type == RegionType.LightWorld) == (from_owid not in ow_crossed_tiles))): 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.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(to_region, player).type == RegionType.LightWorld)) \ or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \ - or (world.owCrossed[player] == 'grouped' and ((to_owid < 0x40) == (to_owid not in ow_crossed_tiles[0]))): + or (world.owCrossed[player] == 'grouped' and ((world.get_region(to_region, player).type == RegionType.LightWorld) == (to_owid not in ow_crossed_tiles))): whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region))) else: whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region))) @@ -278,9 +297,12 @@ def link_overworld(world, player): # layout shuffle logging.getLogger('').debug('Shuffling overworld layout') - connected_edges = [] if world.owShuffle[player] == 'vanilla': + # apply outstanding swaps + trimmed_groups = performSwap(trimmed_groups, edges_to_swap) + assert len(edges_to_swap) == 0, 'Not all edges were swapped successfully: ' + ', '.join(edges_to_swap) + # vanilla transitions groups = list(trimmed_groups.values()) for (forward_edge_sets, back_edge_sets) in groups: @@ -303,8 +325,7 @@ def link_overworld(world, player): connect_custom(world, connected_edges, player) # layout shuffle - trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) - groups = reorganize_groups(world, trimmed_groups, player) + groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) tries = 20 valid_layout = False @@ -523,33 +544,98 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): if not (parallel_forward_edge in connected_edges) and not (parallel_back_edge in connected_edges): connect_two_way(world, parallel_forward_edge, parallel_back_edge, player, connected_edges) except KeyError: - # TODO: Figure out why non-parallel edges are getting into parallel groups raise KeyError('No parallel edge for edge %s' % edgename2) -def shuffle_tiles(world, groups, result_list, player): +def shuffle_tiles(world, groups, result_list, do_grouped, player): swapped_edges = list() - valid_whirlpool_parity = False + if not do_grouped: + group_parity = {} + for group_data in groups: + group = group_data[0] + parity = [0, 0, 0, 0, 0] + # vertical land + if 0x00 in group: + parity[0] += 1 + if 0x0f in group: + parity[0] += 1 + if 0x80 in group: + parity[0] -= 1 + if 0x81 in group: + parity[0] -= 1 + # horizontal land + if 0x1a in group: + parity[1] -= 1 + if 0x1b in group: + parity[1] += 1 + if 0x28 in group: + parity[1] += 1 + if 0x29 in group: + parity[1] -= 1 + if 0x30 in group: + parity[1] -= 2 + if 0x3a in group: + parity[1] += 2 + # horizontal water + if 0x2d in group: + parity[2] += 1 + if 0x80 in group: + parity[2] -= 1 + # whirlpool + if 0x0f in group: + parity[3] += 1 + if 0x12 in group: + parity[3] += 1 + if 0x33 in group: + parity[3] += 1 + if 0x35 in group: + parity[3] += 1 + # dropdown exit + if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group: + parity[4] += 1 + if 0x1b in group and world.mode[player] != 'standard': + parity[4] += 1 + if 0x1b in group and world.shuffle_ganon: + parity[4] -= 1 + group_parity[group[0]] = parity + + attempts = 1000 + while True: + if attempts == 0: # expected to only occur with custom swaps + raise GenerationException('Could not find valid tile swaps') - 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 + for group in groups: + # if 0x1b in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted if random.randint(0, 1): removed.append(group) - + # save shuffled tiles to list new_results = [[],[],[]] - for group in groups.keys(): + for group in groups: if group not in removed: - (owids, lw_regions, dw_regions) = groups[group] + (owids, lw_regions, dw_regions) = 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] not in ['none', 'grouped'] or len([o for o in new_results[0] if o in [0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f]]) % 2 == 0 + if not do_grouped: + parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(5)] + parity[3] %= 2 # actual parity + if world.owCrossed[player] == 'none' and parity[:4] != [0, 0, 0, 0]: + attempts -= 1 + continue + # ensure sanc can be placed in LW in certain modes + if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] \ + and world.mode[player] != 'inverted' \ + and (world.mode[player] == 'standard' or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3): + free_dw_drops = parity[4] + (1 if world.shuffle_ganon else 0) + free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0) + if free_dw_drops == free_drops: + attempts -= 1 + continue + break (exist_owids, exist_lw_regions, exist_dw_regions) = result_list exist_owids.extend(new_results[0]) @@ -557,7 +643,7 @@ def shuffle_tiles(world, groups, result_list, player): exist_dw_regions.extend(new_results[2]) # replace LW edges with DW - if world.owCrossed[player] != 'polar': + if world.owCrossed[player] not in ['polar', 'grouped', 'chaos']: # in polar, the actual edge connections remain vanilla def getSwappedEdges(world, lst, player): for regionname in lst: @@ -571,43 +657,75 @@ def shuffle_tiles(world, groups, result_list, player): return swapped_edges -def reorganize_tile_groups(world, player): - def get_group_key(group): - #(name, groupType, whirlpoolGroup) = group - new_group = list(group) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']: - new_group[1] = None - if not world.owWhirlpoolShuffle[player] and world.owCrossed[player] == 'none': - new_group[2] = None - return tuple(new_group) +def define_tile_groups(world, player, do_grouped): + groups = [[i, i + 0x40] for i in range(0x40)] + + def get_group(id): + for group in groups: + if id in group: + return group + + def merge_groups(tile_links): + for link in tile_links: + merged_group = [] + for id in link: + if id not in merged_group: + group = get_group(id) + groups.remove(group) + merged_group += group + groups.append(merged_group) def can_shuffle_group(group): - (name, groupType, whirlpoolGroup) = group - return name not in ['Castle', 'Links', 'Central Bonk Rocks'] \ - or (world.mode[player] != 'standard' and (name != 'Castle' \ - or world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \ - or (world.mode[player] == 'open' and world.doorShuffle[player] == 'crossed') \ - or world.owCrossed[player] in ['grouped', 'polar', 'chaos'])) \ - or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance') - - groups = {} - for group in OWTileGroups.keys(): - if can_shuffle_group(group): - groups[get_group_key(group)] = ([], [], []) + # escape sequence should stay normal in standard + if world.mode[player] == 'standard' and (0x1b in group or 0x2b in group or 0x2c in group): + return False + + # sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen + if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \ + and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \ + or (world.shuffle[player] == 'lite' and world.mode[player] == 'inverted')): + return False + + return True - for group in OWTileGroups.keys(): + for i in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]: + groups.remove(get_group(i + 1)) + groups.remove(get_group(i + 8)) + groups.remove(get_group(i + 9)) + groups.append([0x80]) + groups.append([0x81]) + + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple']: + merge_groups([[0x03, 0x0a], [0x28, 0x29]]) + + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']: + merge_groups([[0x13, 0x14]]) + + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted']: + merge_groups([[0x05, 0x07]]) + + if world.shuffle[player] == 'vanilla' or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']): + merge_groups([[0x13, 0x14, 0x1b]]) + + if (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'none') or do_grouped: + merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]]) + + if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and world.owCrossed[player] == 'none': + merge_groups([[0x28, 0x29]]) + + if (not world.owWhirlpoolShuffle[player] and world.owCrossed[player] == 'none') or do_grouped: + merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) + + tile_groups = [] + for group in groups: if can_shuffle_group(group): - (lw_owids, dw_owids) = OWTileGroups[group] - (exist_owids, exist_lw_regions, exist_dw_regions) = groups[get_group_key(group)] - 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[get_group_key(group)] = (exist_owids, exist_lw_regions, exist_dw_regions) - - return groups + lw_regions = [] + dw_regions = [] + for id in group: + (lw_regions if id < 0x40 or id >= 0x80 else dw_regions).extend(OWTileRegions.inverse[id]) + tile_groups.append((group, lw_regions, dw_regions)) + + return tile_groups def remove_reserved(world, groupedlist, connected_edges, player): new_grouping = {} @@ -615,7 +733,6 @@ def remove_reserved(world, groupedlist, connected_edges, player): new_grouping[group] = ([], []) for group in groupedlist.keys(): - (_, region, _, _, _, _) = group (forward_edges, back_edges) = groupedlist[group] # remove edges already connected (thru plando and other forced connections) @@ -623,15 +740,6 @@ def remove_reserved(world, groupedlist, connected_edges, player): forward_edges = list(list(filter((edge).__ne__, i)) for i in forward_edges) back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges) - # remove parallel edges from pool, since they get added during shuffle - if world.owShuffle[player] == 'parallel' and region == WorldType.Dark: - for edge in parallel_links: - forward_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in forward_edges) - back_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in back_edges) - for edge in parallel_links.inverse: - forward_edges = list(list(filter((parallel_links.inverse[edge][0]).__ne__, i)) for i in forward_edges) - back_edges = list(list(filter((parallel_links.inverse[edge][0]).__ne__, i)) for i in back_edges) - forward_edges = list(filter(([]).__ne__, forward_edges)) back_edges = list(filter(([]).__ne__, back_edges)) @@ -674,7 +782,26 @@ def reorganize_groups(world, groups, player): exist_back_edges.extend(back_edges) new_grouping[new_group] = (exist_forward_edges, exist_back_edges) - return list(new_grouping.values()) + return new_grouping + +def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): + groups = defaultdict(lambda: ([],[])) + for (key, group) in trimmed_groups.items(): + (mode, wrld, dir, terrain, parallel, count) = key + if mode == OpenStd.Standard: + groups[key] = group + else: + if world.owCrossed[player] == 'chaos': + groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0]) + groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1]) + else: + for i in range(2): + for edge_set in group[i]: + new_world = int(wrld) + if edge_set[0] in edges_to_swap: + new_world += 1 + groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count)][i].append(edge_set) + return list(groups.values()) def create_flute_exits(world, player): for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']): From 4d550a38e8d45fe0875502d8104f8a7a4a0e884c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 May 2022 14:01:23 -0500 Subject: [PATCH 05/17] Fixed default TF pieces in Trinity --- Mystery.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Mystery.py b/Mystery.py index faf5d16a..c0ee497f 100644 --- a/Mystery.py +++ b/Mystery.py @@ -208,12 +208,16 @@ def roll_settings(weights): ret.crystals_gt = get_choice('tower_open') ret.crystals_ganon = get_choice('ganon_open') - goal_min = get_choice_default('triforce_goal_min', default=20) - goal_max = get_choice_default('triforce_goal_max', default=20) - pool_min = get_choice_default('triforce_pool_min', default=30) - pool_max = get_choice_default('triforce_pool_max', default=30) + from ItemList import set_default_triforce + default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0) + goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal) + goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal) + pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool) + pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool) + logging.getLogger('').warning(goal_min) + logging.getLogger('').warning(goal_max) ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) - min_diff = get_choice_default('triforce_min_difference', default=10) + min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal)) ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) ret.mode = get_choice('world_state') From f708a3155eef4f85fbc1b1bf700182ca2354ed37 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 May 2022 14:02:30 -0500 Subject: [PATCH 06/17] Fixed default TF pieces in Trinity --- ItemList.py | 6 +- TestSuiteRandom.py | 136 +++++++++++++++++++ TestSuiteStat.py | 330 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 TestSuiteRandom.py create mode 100644 TestSuiteStat.py diff --git a/ItemList.py b/ItemList.py index 7ff27436..f1f9e2c9 100644 --- a/ItemList.py +++ b/ItemList.py @@ -759,9 +759,11 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, placed_items = {} precollected_items = [] clock_mode = None - if goal in ['triforcehunt', 'trinity']: - if treasure_hunt_total == 0: + if treasure_hunt_total == 0: + if goal == 'triforcehunt': treasure_hunt_total = 30 + elif goal == 'trinity': + treasure_hunt_total = 10 triforcepool = ['Triforce Piece'] * int(treasure_hunt_total) pool.extend(alwaysitems) diff --git a/TestSuiteRandom.py b/TestSuiteRandom.py new file mode 100644 index 00000000..50cbb737 --- /dev/null +++ b/TestSuiteRandom.py @@ -0,0 +1,136 @@ +import subprocess +import sys +import multiprocessing +import concurrent.futures +import argparse +import sqlite3 +from collections import OrderedDict +from datetime import datetime + +cpu_threads = multiprocessing.cpu_count() +py_version = f"{sys.version_info.major}.{sys.version_info.minor}" + + +def main(args=None): + successes = [] + errors = [] + task_mapping = [] + tests = OrderedDict() + + max_attempts = 1 #args and args.count if args.count is not None else 1 + print(f"Testing Random OWR with {max_attempts} Tests") + + pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads) + dead_or_alive = 0 + alive = 0 + + + basecommand = f'py Mystery.py --suppress_rom --create_spoiler --outputpath L:/_Work/Zelda/ROMs/Bug/Automate --weights L:/_Work/Zelda/ROMs/Bug/Automate/_test.yml' + + def gen_seed(): + return subprocess.run(basecommand, capture_output=True, shell=True, text=True) + + for x in range(1, max_attempts + 1): + task = pool.submit(gen_seed) + task.success = False + task.name = "OWR Random Test" + task.mode = "" + task.cmd = basecommand + task_mapping.append(task) + + + from tqdm import tqdm + with tqdm(concurrent.futures.as_completed(task_mapping), + total=len(task_mapping), unit="seed(s)", + desc=f"Success rate: 0.00%") as progressbar: + for task in progressbar: + dead_or_alive += 1 + try: + result = task.result() + if result.returncode: + errors.append([datetime.now(), result.stderr]) + #print(result.stderr) + #print(result.stdout) + else: + alive += 1 + successes.append([datetime.now(), result.stderr]) + task.success = True + except Exception as e: + raise e + + progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}{task.mode}") + + def get_results(testname: str): + result = "" + dead_or_alive = [task.success for task in task_mapping] + alive = [x for x in dead_or_alive if x] + success = f"Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%" + #successes.append(success) + print(success) + result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t" + return result.strip() + + results = [] + results.append(get_results("")) + + for result in results: + print(result) + #successes.append(result) + + return successes, errors + + +if __name__ == "__main__": + successes = [] + + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--count', default=1, type=lambda value: max(int(value), 1)) + parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1)) + parser.add_argument('--help', default=False, action='store_true') + + args = parser.parse_args() + + if args.help: + parser.print_help() + exit(0) + + cpu_threads = args.cpu_threads + + args = argparse.Namespace() + successes, errors = main(args=args) + # if successes: + # successes += [""] * 2 + # successes += s + print() + + if errors: + with open("L:/_Work/Zelda/ROMs/Bug/Automate/_error_" + datetime.now().strftime("%y%m%d") + ".txt", 'a') as stream: + for error in errors: + stream.write("Run at: " + error[0].strftime("%Y-%m-%d %H:%M:%S") + "\n") + stream.write(error[1] + "\n\n") + stream.write("----------------------------------------------------\n") + + if successes: + with open("L:/_Work/Zelda/ROMs/Bug/Automate/_success_" + datetime.now().strftime("%y%m%d") + ".txt", 'a') as stream: + for success in successes: + stream.write("Run at: " + success[0].strftime("%Y-%m-%d %H:%M:%S") + "\n") + stream.write(success[1] + "\n\n") + stream.write("----------------------------------------------------\n") + + # with open("L:\\_Work\\Zelda\\ROMs\\Bug\\Automate\\_success.txt", "w") as stream: + # stream.write(str.join("\n", successes)) + + + conn = sqlite3.connect("L:/_Work/Zelda/ROMs/Bug/Automate/log.db") + # conn.execute('''CREATE TABLE SEEDGEN + # (SEED INT NOT NULL, + # MYSTERY CHAR(10), + # SETTINGSCODE CHAR(20) NOT NULL, + # VERSION CHAR(10) NOT NULL, + # TIMESTAMP INT NOT NULL, + # SUCCESS INT, + # LOG TEXT);''') + + #input("Press enter to continue") + + conn.close() diff --git a/TestSuiteStat.py b/TestSuiteStat.py new file mode 100644 index 00000000..92d066c6 --- /dev/null +++ b/TestSuiteStat.py @@ -0,0 +1,330 @@ +import subprocess +import sys +import multiprocessing +import concurrent.futures +import argparse +from collections import OrderedDict +import csv + +cpu_threads = multiprocessing.cpu_count() +py_version = f"{sys.version_info.major}.{sys.version_info.minor}" + + +ALL_SETTINGS = { + 'mode': ['open', 'standard', 'inverted'], + 'goal': ['ganon', 'pedestal', 'triforcehunt', 'trinity', 'crystals', 'dungeons'], + 'swords': ['random', 'swordless', 'assured'], + 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','crossed','insanity'], + 'shufflelinks': [True, False], + 'shuffleganon': [True, False], + 'door_shuffle': ['vanilla', 'basic', 'crossed'], + 'intensity': ['1','2','3'], + 'ow_shuffle': ['vanilla', 'parallel', 'full'], + 'ow_fluteshuffle': ['vanilla', 'balanced', 'random'], + 'ow_keepsimilar': [True, False], + 'ow_mixed': [True, False], + 'ow_crossed': [True, False], + 'accessibility': [True, False], + 'difficulty': [True, False], + 'shufflepots': [True, False], + 'keydropshuffle': [True, False], + 'keysanity': [True, False], + 'retro': [True, False], + 'bombbag': [True, False], + 'shopsanity': [True, False] +} + +SETTINGS = { + 'mode': ['standard', 'open', 'inverted'], + 'goal': ['ganon'], + 'swords': ['random'], + 'shuffle': ['vanilla', + 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'crossed', 'insanity' + ], + 'shufflelinks': [True, False], + 'shuffleganon': [True, False], + 'door_shuffle': ['vanilla', 'crossed'], + 'intensity': ['2'], + 'ow_shuffle': ['vanilla', 'parallel', 'full'], + 'ow_fluteshuffle': ['balanced'], + 'ow_keepsimilar': [True, False], + 'ow_mixed': [True, False], + 'ow_crossed': ['none', 'polar', 'grouped', 'limited'], + 'accessibility': [True], + 'difficulty': [False], + 'shufflepots': [False], + 'keydropshuffle': [False], + 'keysanity': [False], + 'retro': [True, False], + 'bombbag': [False], + 'shopsanity': [True, False] +} + +optionsList = [] +for sett,options in SETTINGS.items(): + for option in options: + if isinstance(option, str): + optionsList.append(f'{option}') + else: + optionsList.append('{}-{}'.format(sett,str(option))) + +headerList = list(SETTINGS.keys()) + +def main(args=None): + successes = [] + errors = [] + task_mapping = [] + tests = OrderedDict() + + successes.append(f"Testing {args.ow} with {args.count} Tests" + (f" (intensity={args.tense})" if args.ow in ['basic', 'crossed'] else "")) + print(successes[0]) + + max_attempts = args.count + pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads) + dead_or_alive = 0 + alive = 0 + + def test(testname: list, command: str): + tests[' '.join(testname)] = [command] + basecommand = f"py DungeonRandomizer.py --shuffle {args.ow} --suppress_rom --suppress_spoiler" + + def gen_seed(): + taskcommand = basecommand + ' ' + command + return subprocess.run(taskcommand, capture_output=True, shell=True, text=True) + + for _ in range(1, max_attempts + 1): + task = pool.submit(gen_seed) + task.success = False + task.name = ' '.join(testname) + task.settings = testname + task.cmd = basecommand + ' ' + command + task_mapping.append(task) + + for mode in SETTINGS['mode']: + for goal in SETTINGS['goal']: + for swords in SETTINGS['swords']: + #for shuffle in SETTINGS['shuffle']: + for ow_shuffle in SETTINGS['ow_shuffle']: + for shufflelinks in SETTINGS['shufflelinks']: + for shuffleganon in SETTINGS['shuffleganon']: + for door_shuffle in SETTINGS['door_shuffle']: + for intensity in SETTINGS['intensity']: + for ow_fluteshuffle in SETTINGS['ow_fluteshuffle']: + for ow_keepsimilar in SETTINGS['ow_keepsimilar']: + for ow_mixed in SETTINGS['ow_mixed']: + for ow_crossed in SETTINGS['ow_crossed']: + for difficulty in SETTINGS['difficulty']: + for shufflepots in SETTINGS['shufflepots']: + for accessibility in SETTINGS['accessibility']: + for keydropshuffle in SETTINGS['keydropshuffle']: + for keysanity in SETTINGS['keysanity']: + for retro in SETTINGS['retro']: + for bombbag in SETTINGS['bombbag']: + for shopsanity in SETTINGS['shopsanity']: + commands = '' + name = [] + commands = commands + f' --mode {mode}' + name.append(mode) + commands = commands + f' --goal {goal}' + name.append(goal) + commands = commands + f' --swords {swords}' + name.append(swords) + # if shuffle != 'vanilla': #since this is the output is grouped on this, we only want one of these in the loop + # continue + # commands = commands + f' --shuffle {shuffle}' + # name.append(f'ER{shuffle}') + commands = commands + f' --ow_shuffle {ow_shuffle}' + name.append(ow_shuffle) + if shufflelinks: + commands = commands + f' --shufflelinks' + name.append('shufflelinks-True') + else: + name.append('shufflelinks-False') + if shuffleganon: + commands = commands + f' --shuffleganon' + name.append('shuffleganon-True') + else: + name.append('shuffleganon-False') + commands = commands + f' --door_shuffle {door_shuffle}' + name.append(door_shuffle) + if intensity == '3' and door_shuffle == 'vanilla': + continue + commands = commands + f' --intensity {intensity}' + name.append(intensity) + commands = commands + f' --ow_fluteshuffle {ow_fluteshuffle}' + name.append(ow_fluteshuffle) + if difficulty: + commands = commands + f' --difficulty expert' + commands = commands + f' --item_functionality expert' + name.append('difficulty-True') + else: + name.append('difficulty-False') + if ow_keepsimilar: + commands = commands + f' --ow_keepsimilar' + name.append('ow_keepsimilar-True') + else: + if ow_crossed in ['none', 'polar', 'grouped']: + continue + name.append('ow_keepsimilar-False') + if ow_mixed: + commands = commands + f' --ow_mixed' + name.append('ow_mixed-True') + else: + if ow_crossed == 'polar': + continue + name.append('ow_mixed-False') + commands = commands + f' --ow_crossed {ow_crossed}' + name.append(ow_crossed) + if shufflepots: + commands = commands + f' --shufflepots' + name.append('shufflepots-True') + else: + name.append('shufflepots-False') + if not accessibility: + commands = commands + f' --accessibility none' + name.append('accessibility-False') + else: + name.append('accessibility-True') + if keydropshuffle: + commands = commands + f' --keydropshuffle' + name.append('keydropshuffle-True') + else: + name.append('keydropshuffle-False') + if keysanity: + commands = commands + f' --keysanity' + name.append('keysanity-True') + else: + name.append('keysanity-False') + if retro: + commands = commands + f' --retro' + name.append('retro-True') + else: + name.append('retro-False') + if bombbag: + commands = commands + f' --bombbag' + name.append('bombbag-True') + else: + name.append('bombbag-False') + if shopsanity: + commands = commands + f' --shopsanity' + name.append('shopsanity-True') + else: + name.append('shopsanity-False') + test(name, commands) + +# test("Vanilla ", "--futuro --shuffle vanilla") +# test("Basic ", "--futuro --retro --shuffle vanilla") +# test("Keysanity ", "--futuro --shuffle vanilla --keydropshuffle --keysanity") +# test("Simple ", "--futuro --shuffle simple") +# test("Crossed ", "--futuro --shuffle crossed") +# test("Insanity ", "--futuro --shuffle insanity") +# test("CrossKeys ", "--futuro --shuffle crossed --keydropshuffle --keysanity") + + from tqdm import tqdm + with tqdm(concurrent.futures.as_completed(task_mapping), + total=len(task_mapping), unit="seed(s)", + desc=f"Success rate: 0.00%") as progressbar: + for task in progressbar: + dead_or_alive += 1 + try: + result = task.result() + if result.returncode: + errors.append([task.name, task.cmd, result.stderr]) + else: + alive += 1 + task.success = True + except Exception as e: + raise e + + progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}") + + def get_results(option: str): + result = "" + dead_or_alive = [task.success for task in task_mapping if option in task.settings] + if len(dead_or_alive): + alive = [x for x in dead_or_alive if x] + success = f"{option} Rate: {(len(alive) / len(dead_or_alive)) * 100:.1f}%" + successes.append(success) + print(success) + result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t" + else: + success = f"{option} Rate: NULL%" + print(success) + result += f"NULL%\t" + return result.strip() + + results = [] + for option in optionsList: + results.append(get_results(option)) + + for result in results: + successes.append(result) + + tabresultsfile = './output/' + args.ow + '.tsv' + with open(tabresultsfile, 'w+', newline='') as f: + writer = csv.writer(f, delimiter='\t') + header = headerList.copy() + header.append('Success') + writer.writerow(header) + for task in task_mapping: + settings = [] + for option in headerList: + if option in task.settings: + settings.append(1) + elif str(option + '-True') in task.settings: + settings.append(1) + else: + settings.append(0) + if task.success: + settings.append(1) + else: + settings.append(0) + writer.writerow(settings) + + return successes, errors + + +if __name__ == "__main__": + successes = [] + + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0)) + parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1)) + parser.add_argument('--help', default=False, action='store_true') + + args = parser.parse_args() + + if args.help: + parser.print_help() + exit(0) + + cpu_threads = args.cpu_threads + +# for ow in [['full', args.count if args.count else 2, 1]]: + # for ow in [['vanilla', args.count if args.count else 2, 1], + # ['parallel', args.count if args.count else 5, 1], + # ['full', args.count if args.count else 10, 1]]: + count = args.count if args.count else 1 + for ow in SETTINGS['shuffle']: + for tense in range(1, 2): #ow[2] + 1): #unnecessary when DR is not the root setting + args = argparse.Namespace() + args.ow = ow #ow[0] + args.tense = tense + args.count = count #ow[1] + s, errors = main(args=args) + if successes: + successes += [""] * 2 + successes += s + print() + + if errors: + with open(f"./output/{ow[0]}{(f'-{tense}' if ow in ['parallel', 'full'] else '')}-errors.txt", 'w+') as stream: + for error in errors: + stream.write(error[0] + "\n") + stream.write(error[1] + "\n") + stream.write(error[2] + "\n\n") + + with open("./output/success.txt", "w+") as stream: + stream.write(str.join("\n", successes)) + + input("Press enter to continue") From 5cd8310094d6067afd45c895850770fe7b5a3d5c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 May 2022 14:05:13 -0500 Subject: [PATCH 07/17] Oops --- TestSuiteRandom.py | 136 ------------------- TestSuiteStat.py | 330 --------------------------------------------- 2 files changed, 466 deletions(-) delete mode 100644 TestSuiteRandom.py delete mode 100644 TestSuiteStat.py diff --git a/TestSuiteRandom.py b/TestSuiteRandom.py deleted file mode 100644 index 50cbb737..00000000 --- a/TestSuiteRandom.py +++ /dev/null @@ -1,136 +0,0 @@ -import subprocess -import sys -import multiprocessing -import concurrent.futures -import argparse -import sqlite3 -from collections import OrderedDict -from datetime import datetime - -cpu_threads = multiprocessing.cpu_count() -py_version = f"{sys.version_info.major}.{sys.version_info.minor}" - - -def main(args=None): - successes = [] - errors = [] - task_mapping = [] - tests = OrderedDict() - - max_attempts = 1 #args and args.count if args.count is not None else 1 - print(f"Testing Random OWR with {max_attempts} Tests") - - pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads) - dead_or_alive = 0 - alive = 0 - - - basecommand = f'py Mystery.py --suppress_rom --create_spoiler --outputpath L:/_Work/Zelda/ROMs/Bug/Automate --weights L:/_Work/Zelda/ROMs/Bug/Automate/_test.yml' - - def gen_seed(): - return subprocess.run(basecommand, capture_output=True, shell=True, text=True) - - for x in range(1, max_attempts + 1): - task = pool.submit(gen_seed) - task.success = False - task.name = "OWR Random Test" - task.mode = "" - task.cmd = basecommand - task_mapping.append(task) - - - from tqdm import tqdm - with tqdm(concurrent.futures.as_completed(task_mapping), - total=len(task_mapping), unit="seed(s)", - desc=f"Success rate: 0.00%") as progressbar: - for task in progressbar: - dead_or_alive += 1 - try: - result = task.result() - if result.returncode: - errors.append([datetime.now(), result.stderr]) - #print(result.stderr) - #print(result.stdout) - else: - alive += 1 - successes.append([datetime.now(), result.stderr]) - task.success = True - except Exception as e: - raise e - - progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}{task.mode}") - - def get_results(testname: str): - result = "" - dead_or_alive = [task.success for task in task_mapping] - alive = [x for x in dead_or_alive if x] - success = f"Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%" - #successes.append(success) - print(success) - result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t" - return result.strip() - - results = [] - results.append(get_results("")) - - for result in results: - print(result) - #successes.append(result) - - return successes, errors - - -if __name__ == "__main__": - successes = [] - - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('--count', default=1, type=lambda value: max(int(value), 1)) - parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1)) - parser.add_argument('--help', default=False, action='store_true') - - args = parser.parse_args() - - if args.help: - parser.print_help() - exit(0) - - cpu_threads = args.cpu_threads - - args = argparse.Namespace() - successes, errors = main(args=args) - # if successes: - # successes += [""] * 2 - # successes += s - print() - - if errors: - with open("L:/_Work/Zelda/ROMs/Bug/Automate/_error_" + datetime.now().strftime("%y%m%d") + ".txt", 'a') as stream: - for error in errors: - stream.write("Run at: " + error[0].strftime("%Y-%m-%d %H:%M:%S") + "\n") - stream.write(error[1] + "\n\n") - stream.write("----------------------------------------------------\n") - - if successes: - with open("L:/_Work/Zelda/ROMs/Bug/Automate/_success_" + datetime.now().strftime("%y%m%d") + ".txt", 'a') as stream: - for success in successes: - stream.write("Run at: " + success[0].strftime("%Y-%m-%d %H:%M:%S") + "\n") - stream.write(success[1] + "\n\n") - stream.write("----------------------------------------------------\n") - - # with open("L:\\_Work\\Zelda\\ROMs\\Bug\\Automate\\_success.txt", "w") as stream: - # stream.write(str.join("\n", successes)) - - - conn = sqlite3.connect("L:/_Work/Zelda/ROMs/Bug/Automate/log.db") - # conn.execute('''CREATE TABLE SEEDGEN - # (SEED INT NOT NULL, - # MYSTERY CHAR(10), - # SETTINGSCODE CHAR(20) NOT NULL, - # VERSION CHAR(10) NOT NULL, - # TIMESTAMP INT NOT NULL, - # SUCCESS INT, - # LOG TEXT);''') - - #input("Press enter to continue") - - conn.close() diff --git a/TestSuiteStat.py b/TestSuiteStat.py deleted file mode 100644 index 92d066c6..00000000 --- a/TestSuiteStat.py +++ /dev/null @@ -1,330 +0,0 @@ -import subprocess -import sys -import multiprocessing -import concurrent.futures -import argparse -from collections import OrderedDict -import csv - -cpu_threads = multiprocessing.cpu_count() -py_version = f"{sys.version_info.major}.{sys.version_info.minor}" - - -ALL_SETTINGS = { - 'mode': ['open', 'standard', 'inverted'], - 'goal': ['ganon', 'pedestal', 'triforcehunt', 'trinity', 'crystals', 'dungeons'], - 'swords': ['random', 'swordless', 'assured'], - 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','crossed','insanity'], - 'shufflelinks': [True, False], - 'shuffleganon': [True, False], - 'door_shuffle': ['vanilla', 'basic', 'crossed'], - 'intensity': ['1','2','3'], - 'ow_shuffle': ['vanilla', 'parallel', 'full'], - 'ow_fluteshuffle': ['vanilla', 'balanced', 'random'], - 'ow_keepsimilar': [True, False], - 'ow_mixed': [True, False], - 'ow_crossed': [True, False], - 'accessibility': [True, False], - 'difficulty': [True, False], - 'shufflepots': [True, False], - 'keydropshuffle': [True, False], - 'keysanity': [True, False], - 'retro': [True, False], - 'bombbag': [True, False], - 'shopsanity': [True, False] -} - -SETTINGS = { - 'mode': ['standard', 'open', 'inverted'], - 'goal': ['ganon'], - 'swords': ['random'], - 'shuffle': ['vanilla', - 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'crossed', 'insanity' - ], - 'shufflelinks': [True, False], - 'shuffleganon': [True, False], - 'door_shuffle': ['vanilla', 'crossed'], - 'intensity': ['2'], - 'ow_shuffle': ['vanilla', 'parallel', 'full'], - 'ow_fluteshuffle': ['balanced'], - 'ow_keepsimilar': [True, False], - 'ow_mixed': [True, False], - 'ow_crossed': ['none', 'polar', 'grouped', 'limited'], - 'accessibility': [True], - 'difficulty': [False], - 'shufflepots': [False], - 'keydropshuffle': [False], - 'keysanity': [False], - 'retro': [True, False], - 'bombbag': [False], - 'shopsanity': [True, False] -} - -optionsList = [] -for sett,options in SETTINGS.items(): - for option in options: - if isinstance(option, str): - optionsList.append(f'{option}') - else: - optionsList.append('{}-{}'.format(sett,str(option))) - -headerList = list(SETTINGS.keys()) - -def main(args=None): - successes = [] - errors = [] - task_mapping = [] - tests = OrderedDict() - - successes.append(f"Testing {args.ow} with {args.count} Tests" + (f" (intensity={args.tense})" if args.ow in ['basic', 'crossed'] else "")) - print(successes[0]) - - max_attempts = args.count - pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads) - dead_or_alive = 0 - alive = 0 - - def test(testname: list, command: str): - tests[' '.join(testname)] = [command] - basecommand = f"py DungeonRandomizer.py --shuffle {args.ow} --suppress_rom --suppress_spoiler" - - def gen_seed(): - taskcommand = basecommand + ' ' + command - return subprocess.run(taskcommand, capture_output=True, shell=True, text=True) - - for _ in range(1, max_attempts + 1): - task = pool.submit(gen_seed) - task.success = False - task.name = ' '.join(testname) - task.settings = testname - task.cmd = basecommand + ' ' + command - task_mapping.append(task) - - for mode in SETTINGS['mode']: - for goal in SETTINGS['goal']: - for swords in SETTINGS['swords']: - #for shuffle in SETTINGS['shuffle']: - for ow_shuffle in SETTINGS['ow_shuffle']: - for shufflelinks in SETTINGS['shufflelinks']: - for shuffleganon in SETTINGS['shuffleganon']: - for door_shuffle in SETTINGS['door_shuffle']: - for intensity in SETTINGS['intensity']: - for ow_fluteshuffle in SETTINGS['ow_fluteshuffle']: - for ow_keepsimilar in SETTINGS['ow_keepsimilar']: - for ow_mixed in SETTINGS['ow_mixed']: - for ow_crossed in SETTINGS['ow_crossed']: - for difficulty in SETTINGS['difficulty']: - for shufflepots in SETTINGS['shufflepots']: - for accessibility in SETTINGS['accessibility']: - for keydropshuffle in SETTINGS['keydropshuffle']: - for keysanity in SETTINGS['keysanity']: - for retro in SETTINGS['retro']: - for bombbag in SETTINGS['bombbag']: - for shopsanity in SETTINGS['shopsanity']: - commands = '' - name = [] - commands = commands + f' --mode {mode}' - name.append(mode) - commands = commands + f' --goal {goal}' - name.append(goal) - commands = commands + f' --swords {swords}' - name.append(swords) - # if shuffle != 'vanilla': #since this is the output is grouped on this, we only want one of these in the loop - # continue - # commands = commands + f' --shuffle {shuffle}' - # name.append(f'ER{shuffle}') - commands = commands + f' --ow_shuffle {ow_shuffle}' - name.append(ow_shuffle) - if shufflelinks: - commands = commands + f' --shufflelinks' - name.append('shufflelinks-True') - else: - name.append('shufflelinks-False') - if shuffleganon: - commands = commands + f' --shuffleganon' - name.append('shuffleganon-True') - else: - name.append('shuffleganon-False') - commands = commands + f' --door_shuffle {door_shuffle}' - name.append(door_shuffle) - if intensity == '3' and door_shuffle == 'vanilla': - continue - commands = commands + f' --intensity {intensity}' - name.append(intensity) - commands = commands + f' --ow_fluteshuffle {ow_fluteshuffle}' - name.append(ow_fluteshuffle) - if difficulty: - commands = commands + f' --difficulty expert' - commands = commands + f' --item_functionality expert' - name.append('difficulty-True') - else: - name.append('difficulty-False') - if ow_keepsimilar: - commands = commands + f' --ow_keepsimilar' - name.append('ow_keepsimilar-True') - else: - if ow_crossed in ['none', 'polar', 'grouped']: - continue - name.append('ow_keepsimilar-False') - if ow_mixed: - commands = commands + f' --ow_mixed' - name.append('ow_mixed-True') - else: - if ow_crossed == 'polar': - continue - name.append('ow_mixed-False') - commands = commands + f' --ow_crossed {ow_crossed}' - name.append(ow_crossed) - if shufflepots: - commands = commands + f' --shufflepots' - name.append('shufflepots-True') - else: - name.append('shufflepots-False') - if not accessibility: - commands = commands + f' --accessibility none' - name.append('accessibility-False') - else: - name.append('accessibility-True') - if keydropshuffle: - commands = commands + f' --keydropshuffle' - name.append('keydropshuffle-True') - else: - name.append('keydropshuffle-False') - if keysanity: - commands = commands + f' --keysanity' - name.append('keysanity-True') - else: - name.append('keysanity-False') - if retro: - commands = commands + f' --retro' - name.append('retro-True') - else: - name.append('retro-False') - if bombbag: - commands = commands + f' --bombbag' - name.append('bombbag-True') - else: - name.append('bombbag-False') - if shopsanity: - commands = commands + f' --shopsanity' - name.append('shopsanity-True') - else: - name.append('shopsanity-False') - test(name, commands) - -# test("Vanilla ", "--futuro --shuffle vanilla") -# test("Basic ", "--futuro --retro --shuffle vanilla") -# test("Keysanity ", "--futuro --shuffle vanilla --keydropshuffle --keysanity") -# test("Simple ", "--futuro --shuffle simple") -# test("Crossed ", "--futuro --shuffle crossed") -# test("Insanity ", "--futuro --shuffle insanity") -# test("CrossKeys ", "--futuro --shuffle crossed --keydropshuffle --keysanity") - - from tqdm import tqdm - with tqdm(concurrent.futures.as_completed(task_mapping), - total=len(task_mapping), unit="seed(s)", - desc=f"Success rate: 0.00%") as progressbar: - for task in progressbar: - dead_or_alive += 1 - try: - result = task.result() - if result.returncode: - errors.append([task.name, task.cmd, result.stderr]) - else: - alive += 1 - task.success = True - except Exception as e: - raise e - - progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}") - - def get_results(option: str): - result = "" - dead_or_alive = [task.success for task in task_mapping if option in task.settings] - if len(dead_or_alive): - alive = [x for x in dead_or_alive if x] - success = f"{option} Rate: {(len(alive) / len(dead_or_alive)) * 100:.1f}%" - successes.append(success) - print(success) - result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t" - else: - success = f"{option} Rate: NULL%" - print(success) - result += f"NULL%\t" - return result.strip() - - results = [] - for option in optionsList: - results.append(get_results(option)) - - for result in results: - successes.append(result) - - tabresultsfile = './output/' + args.ow + '.tsv' - with open(tabresultsfile, 'w+', newline='') as f: - writer = csv.writer(f, delimiter='\t') - header = headerList.copy() - header.append('Success') - writer.writerow(header) - for task in task_mapping: - settings = [] - for option in headerList: - if option in task.settings: - settings.append(1) - elif str(option + '-True') in task.settings: - settings.append(1) - else: - settings.append(0) - if task.success: - settings.append(1) - else: - settings.append(0) - writer.writerow(settings) - - return successes, errors - - -if __name__ == "__main__": - successes = [] - - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0)) - parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1)) - parser.add_argument('--help', default=False, action='store_true') - - args = parser.parse_args() - - if args.help: - parser.print_help() - exit(0) - - cpu_threads = args.cpu_threads - -# for ow in [['full', args.count if args.count else 2, 1]]: - # for ow in [['vanilla', args.count if args.count else 2, 1], - # ['parallel', args.count if args.count else 5, 1], - # ['full', args.count if args.count else 10, 1]]: - count = args.count if args.count else 1 - for ow in SETTINGS['shuffle']: - for tense in range(1, 2): #ow[2] + 1): #unnecessary when DR is not the root setting - args = argparse.Namespace() - args.ow = ow #ow[0] - args.tense = tense - args.count = count #ow[1] - s, errors = main(args=args) - if successes: - successes += [""] * 2 - successes += s - print() - - if errors: - with open(f"./output/{ow[0]}{(f'-{tense}' if ow in ['parallel', 'full'] else '')}-errors.txt", 'w+') as stream: - for error in errors: - stream.write(error[0] + "\n") - stream.write(error[1] + "\n") - stream.write(error[2] + "\n\n") - - with open("./output/success.txt", "w+") as stream: - stream.write(str.join("\n", successes)) - - input("Press enter to continue") From 2c41d369e79994e25da9c6f7ddd41e034775cde6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 May 2022 14:18:41 -0500 Subject: [PATCH 08/17] Oops --- Mystery.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Mystery.py b/Mystery.py index c0ee497f..0e2a556d 100644 --- a/Mystery.py +++ b/Mystery.py @@ -214,8 +214,6 @@ def roll_settings(weights): goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal) pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool) pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool) - logging.getLogger('').warning(goal_min) - logging.getLogger('').warning(goal_max) ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal)) ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) From bcf30ae5c236ae3a846211a509b92f9d75b187a8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 17 May 2022 17:34:49 -0500 Subject: [PATCH 09/17] Some additional tile swap corrections --- OverworldShuffle.py | 126 +++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 83758f16..c5485643 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -548,55 +548,54 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): def shuffle_tiles(world, groups, result_list, do_grouped, player): swapped_edges = list() - if not do_grouped: - group_parity = {} - for group_data in groups: - group = group_data[0] - parity = [0, 0, 0, 0, 0] - # vertical land - if 0x00 in group: - parity[0] += 1 - if 0x0f in group: - parity[0] += 1 - if 0x80 in group: - parity[0] -= 1 - if 0x81 in group: - parity[0] -= 1 - # horizontal land - if 0x1a in group: - parity[1] -= 1 - if 0x1b in group: - parity[1] += 1 - if 0x28 in group: - parity[1] += 1 - if 0x29 in group: - parity[1] -= 1 - if 0x30 in group: - parity[1] -= 2 - if 0x3a in group: - parity[1] += 2 - # horizontal water - if 0x2d in group: - parity[2] += 1 - if 0x80 in group: - parity[2] -= 1 - # whirlpool - if 0x0f in group: - parity[3] += 1 - if 0x12 in group: - parity[3] += 1 - if 0x33 in group: - parity[3] += 1 - if 0x35 in group: - parity[3] += 1 - # dropdown exit - if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group: - parity[4] += 1 - if 0x1b in group and world.mode[player] != 'standard': - parity[4] += 1 - if 0x1b in group and world.shuffle_ganon: - parity[4] -= 1 - group_parity[group[0]] = parity + group_parity = {} + for group_data in groups: + group = group_data[0] + parity = [0, 0, 0, 0, 0] + # vertical land + if 0x00 in group: + parity[0] += 1 + if 0x0f in group: + parity[0] += 1 + if 0x80 in group: + parity[0] -= 1 + if 0x81 in group: + parity[0] -= 1 + # horizontal land + if 0x1a in group: + parity[1] -= 1 + if 0x1b in group: + parity[1] += 1 + if 0x28 in group: + parity[1] += 1 + if 0x29 in group: + parity[1] -= 1 + if 0x30 in group: + parity[1] -= 2 + if 0x3a in group: + parity[1] += 2 + # horizontal water + if 0x2d in group: + parity[2] += 1 + if 0x80 in group: + parity[2] -= 1 + # whirlpool + if 0x0f in group: + parity[3] += 1 + if 0x12 in group: + parity[3] += 1 + if 0x33 in group: + parity[3] += 1 + if 0x35 in group: + parity[3] += 1 + # dropdown exit + if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group: + parity[4] += 1 + if 0x1b in group and world.mode[player] != 'standard': + parity[4] += 1 + if 0x1b in group and world.shuffle_ganon: + parity[4] -= 1 + group_parity[group[0]] = parity attempts = 1000 while True: @@ -620,19 +619,16 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): exist_lw_regions.extend(lw_regions) exist_dw_regions.extend(dw_regions) - if not do_grouped: - parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(5)] - parity[3] %= 2 # actual parity - if world.owCrossed[player] == 'none' and parity[:4] != [0, 0, 0, 0]: - attempts -= 1 - continue - # ensure sanc can be placed in LW in certain modes - if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] \ - and world.mode[player] != 'inverted' \ - and (world.mode[player] == 'standard' or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3): - free_dw_drops = parity[4] + (1 if world.shuffle_ganon else 0) - free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0) - if free_dw_drops == free_drops: + parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(5)] + parity[3] %= 2 # actual parity + if (world.owCrossed[player] == 'none' or do_grouped) and parity[:4] != [0, 0, 0, 0]: + attempts -= 1 + continue + # ensure sanc can be placed in LW in certain modes + if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): + free_dw_drops = parity[4] + (1 if world.shuffle_ganon else 0) + free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0) + if free_dw_drops == free_drops: attempts -= 1 continue break @@ -643,7 +639,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): exist_dw_regions.extend(new_results[2]) # replace LW edges with DW - if world.owCrossed[player] not in ['polar', 'grouped', 'chaos']: + if world.owCrossed[player] not in ['polar', 'grouped', 'chaos'] or do_grouped: # in polar, the actual edge connections remain vanilla def getSwappedEdges(world, lst, player): for regionname in lst: @@ -707,13 +703,13 @@ def define_tile_groups(world, player, do_grouped): if world.shuffle[player] == 'vanilla' or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']): merge_groups([[0x13, 0x14, 0x1b]]) - if (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'none') or do_grouped: + if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]]) if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and world.owCrossed[player] == 'none': merge_groups([[0x28, 0x29]]) - if (not world.owWhirlpoolShuffle[player] and world.owCrossed[player] == 'none') or do_grouped: + if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) tile_groups = [] From e3b934897755182038512b317428b7b57b68cc97 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 21 May 2022 21:01:42 -0500 Subject: [PATCH 10/17] Updated ROM build -Fixed mirror portal bugs from Crossed OWR -Updated OWR credits (now including Catobat) --- Rom.py | 2 +- data/base2current.bps | Bin 91774 -> 91854 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 80d6ee95..14430ae0 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '538bf256ff03bb7576991114395eeccc' +RANDOMIZERBASEHASH = 'af75da389ee773e103cbf0a13e689ac3' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index a080ac84b84262bc669872057fafe51ef781590c..61f24b6ceee3e74dae25d71e43c05979c53b356d 100644 GIT binary patch delta 3901 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 ZOu@W|>OAY?!=Mc~r%g>6_XP_6|Gzj|cFX_( delta 3768 zcmW-k4OA1y+Q)Y`AA}G{P{hbL38*L{0t(89h++W|sNYqPh@cS=Q4vKk>=L2`2um1X z3=1q@F#<25rV_wwqPD#t+N(B4y|t%T+gokb-Yb3WsrKrXcYSBh{PusIXV08x=b1Cl z?BBlTeYnlLkO;VO-0Sapk^#WKt2VF0n*dKEi(y>hQ_gE}5q`_0^!9h`-hm*GdMSOL;%yOnvCcD|&;{8eM z%UN3*wJ)pTM(0I90ZC^i&#kIw;TibeIRY=D&EYP3;GiM+=(C*-a9r$%d%lJ*#NTq9 zMCagNC960fQDYXm9_EJdhvnPUI+@4AUV&OK721%-8VVa~Se|S6E6bl6a6PT| z)5qzl|;{@4a-ORmn`4wof~PjPh&_U4Yz8C zVrr=U8eubCNyYR^Dtm1;5S;a#+K6kGx^70R473Ss@;gK43i@r(?Uo&n59IrVI++cd` zZ}{Olm{8XrysT!lR#T%sU9V%*>0}1T&DsN+IMoo=zz`P%e>b_Cq*KxbfWz&R&@-2A9sye9sdv=+V;@& z1RWvKnp4dFD(ED#4|^)k!%`>M-rTynAofcQi0j}f>tRGmoyL>PEC?T_PEz_e0&H5< zGjG?Vei{QGUUmEV*4vbOC(%i@B}SwFfK`_NXJF;dJ1F-AJl+z4=Xt;jEot~R??$wl z=Uem#@`2ltk9=mBT|bBaTIAB2Ce@;_V7o3$_PuQA;-Qu(X_@3zDMC;9t=I>=_=b}C#TGh$+Ce;6u#7M3xkHb%j?No%#-WMjl3d?vDfdtNY93ah9K|IBX(+4JGl^6!m{Yceb^hp<&@#% z;p9>+D%u4v8Fqqj=-Mj9YhIOy%UUhcWQ@Ugmyxe*K-yZ|#(y}>+LT^#hcG6=Y zJ@1)53Y#zax7j_i%~F^KchPIzE0)?R&^`!0OtTaGF8E!*eTz~K!>hEHOLF!tL#g0) z=={6C(D|wGXL=!j_1v45wOlA|o4*`nPV`M53TQFJKOaP5~&X6gjV&^M?qbX5~B zx&w&uE;7NKL+IYK`{JPiISX#1s83;88x=MuX`)5SsX<9D6R6y3`d$YM8w*ih`6N>q zH>qh*Ah~qH_y%jw(Uy{`=6tkW3RkqB56Sbeo8skmeY`EZGo#g^k^#$pMQfCd@na-0 zUwrH)#lc%lCM705T&VpGTg(c%%^rk%px-a2GUCzj5rMiFVJ5!O6VauF_sPD~T< zfSrun+#uiHv%ck8-LY-Qioe(xVbo4flUF1elPw}arQz5yc409e+7UiteC9=koh9!K z6g6E^257Go%&9i0u8lj{hLs|yl9-%crB77UknE)%5`Pl(C%WJiKY zi-spU{oQ>%$0;W_N!=dU)FkWjrjs6HRtou=8=>j4t5j6 zrZ}m&Fq6=yqy+h5v~%!1O`ij-b0i-bFW=?$#m5qyzKmFnd9-yzV;QAKG$SU9W^5FV z1c|XJ$A05j%H-Ym|8GcPXYj%LZ6%lFfn%)Y%m^KL%($MwrbNPioAQ#A{Hw7GxMs{a zCToK0dSpNdi+YR|M+<5UIiDLRr2DXz?fC{R4||E^iCA(x<>ct-*{?yE6icQsh| zw8L2aa^hU=x`EN=;emp-1waJgx;Gs!;=o^fyYO|3 z%#=|Fz+UKW`V4F{A2n?f<3R%W^{^-I!ZW`Z?#5k{#g#BgT&dY2p%<~zO>Ax-O~Yqi z#dPleeWAM9{(Vby+|_*;RcHS16$8%03TmK}nYP|Y5Odwyy4=8Q;0b(yKRwG*2tYC5 zFDSZ?D!kB-I56{zBcHqyY(z8%^e4KOQsl-5{>zA#4F@af*&z?e6_-|(k(yY)M#o~} zn~HsXUK5gGW zk{IqXn=C*szvy33`aO=oDv!T_h$TEejkASL)o98%R->QpK8%;CyU zN9|6a)*+9s%_4XNS}X!`d^cv22DZ^1(1KJJVA0Ur#*{ecYlVi=GEIfkJ`S9DJTTKI}J<4k-x z+V2XQ9X7=hB>u%hi{lJ%Tfp1LqfJW7BMrv_$*b& z`60!_5*G@dISWMLU)_+HAkPvU2d;BOOLHvxC9Z56`ZUS%=PED<@RtUUFdKnIx*Gfw z%-WrP@iO~EzyW~6;W_Xe1Y(peut*XC;PN&{Sq5?~?#bXb&%rfm)Ja)YWO<$jPKW>( zu>fT~haW+}VuY0^|lu8{Wzgb9>)?chFuPm=R zR$bhn=#0MxJ*Ydu(N^Rtu`x>*-W-oAL3eh59~TLWTKjylQ1M=AdT45RGAtvF?=k+{ z*P=i9bDj;YpG)gL-j-UDH>=TnFZ7R0u+mLjVWl>=G26s8Cib9}5skui?r2FChz0_* zKMTkfN%F0Xpqc^utPIAEykaYomROD9qIuFlX)Jr>GEIiiYkt?BU>JQA{Ur-52Z891 zEHEDkk?&3rT9J9uYFf&*SxUGzjTAfU)sGG)c54rb(t1i|-OLi1ZD`TVNsS6ag(Z`w zS(QV6d#zOaQ0#7Na|QNwm^rm$X>vW|w#=%_mEI1u>@Tp=iRI6XDw~Q{y-~f2QPJBx ze>O={tj!-QYli~;%D8T{+T0FVAA>q(vohUEWvCgEdUs<+@UWIGUW{YRFgoHit|?cW zT|69!o-X=Gj?Gq*W7AEmNCoX_wI^6@KakU=tRx%llVsB*Na>BvwiY3mGNq>@?`&|L ze>lyC!cx&s*&vW7me_iBA{PbtgD=jq4aG+z;VzIbota_NV8f>LrKn-7rQ_!aGV`sYfS!ok0=c3c7uQ7 z;&OB-7tF&WJQ1A>RMM@BY$OH?E)EtpDmuz5V{Tw7HV;WzmL++hnN3`v?)3a~Uw`?3 D`~@q8 From 592d24cbf8536e0ecd2d413c17a6725889c6d386 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 21 May 2022 21:03:21 -0500 Subject: [PATCH 11/17] Updated ROM build --- asm/owrando.asm | 62 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/asm/owrando.asm b/asm/owrando.asm index 04a6d576..01e29031 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -39,8 +39,17 @@ org $04E8B4 Overworld_LoadSpecialOverworld: -org $05af75 +; mirror hooks +org $02FBAB +JSL OWMirrorSpriteRestore : NOP +org $05AF75 +Sprite_6C_MirrorPortal: jsl OWPreserveMirrorSprite : nop #2 ; LDA $7EF3CA : BNE $05AFDF +org $05AFDF +Sprite_6C_MirrorPortal_missing_mirror: +JML OWMirrorSpriteDelete : NOP ; STZ $0DD0,X : BRA $05AFF1 +org $0ABFBF +JSL OWMirrorSpriteOnMap : BRA + : NOP #6 : + ; whirlpool shuffle cross world change org $02b3bd @@ -196,38 +205,61 @@ OWWhirlpoolUpdate: rtl } +OWMirrorSpriteOnMap: +{ + lda.w $1ac0,x : bit.b #$f0 : beq .continue + lda.b #$00 : rtl + .continue + ora.w $1ab0,x + ora.w $1ad0,x + ora.w $1ae0,x + rtl +} OWPreserveMirrorSprite: { - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla - rtl ; if OW Crossed, skip world check and continue + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla ; if OW Crossed, skip world check and continue + lda $10 : cmp.b #$0f : beq .vanilla + rtl .vanilla - lda InvertedMode : beq + - lda $7ef3ca : beq .deleteMirror + lda.l InvertedMode : beq + + lda.l $7ef3ca : beq .deleteMirror rtl - + lda $7ef3ca : bne .deleteMirror + + lda.l $7ef3ca : bne .deleteMirror rtl .deleteMirror - pla : lda #$de : pha ; in vanilla, if in dark world, jump to $05afdf - rtl + lda.b $10 : cmp.b #$0f : bne + + jsr.w OWMirrorSpriteMove ; if performing mirror superbunny + + pla : pla : pla : jml Sprite_6C_MirrorPortal_missing_mirror } OWMirrorSpriteMove: { lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq + - lda $1acf : eor #$80 : sta $1acf - + lda #$2c : jml.l $07A985 ; what we wrote over + lda.w $1acf : ora.b #$40 : sta.w $1acf + + rts +} +OWMirrorSpriteBonk: +{ + jsr.w OWMirrorSpriteMove + lda.b #$2c : jml.l SetGameModeLikeMirror ; what we wrote over +} +OWMirrorSpriteDelete: +{ + stz.w $0dd0,x ; what we wrote over + jsr.w OWMirrorSpriteMove + jml Sprite_6C_MirrorPortal_dont_do_warp } OWMirrorSpriteRestore: { lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return - lda InvertedMode : beq + - lda $7ef3ca : beq .return + lda.l InvertedMode : beq + + lda.l $7ef3ca : beq .return bra .restorePortal - + lda $7ef3ca : bne .return + + lda.l $7ef3ca : bne .return .restorePortal - lda $1acf : and #$0f : sta $1acf + lda.w $1acf : and.b #$0f : sta.w $1acf .return rep #$30 : lda.w $04AC ; what we wrote over @@ -603,7 +635,7 @@ OWWorldUpdate: ; x = owid of destination screen cmp #0 : beq + : lda #1 + cmp.l InvertedMode : bne + lda $1acf : and #$0f : sta $1acf : bra .playSfx ; bring portal back into position - + lda $1acf : eor #$80 : sta $1acf ; move portal off screen + + lda $1acf : ora #$40 : sta $1acf ; move portal off screen .playSfx lda #$38 : sta $012f ; play sfx - #$3b is an alternative From 5eb04bc41f8a3e1c1af98a9b0b64c1e305560f46 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 22 May 2022 15:53:13 -0500 Subject: [PATCH 12/17] Added bush crabs to rupee farm logic --- BaseClasses.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/BaseClasses.py b/BaseClasses.py index ad8675a3..983367fb 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1089,6 +1089,10 @@ class CollectionState(object): 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)) @@ -1109,6 +1113,22 @@ class CollectionState(object): 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('Beat Agahnim 1', 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): From d5f75ff7c5d827d3ef3e2e67bceef95d8eb513a2 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 22 May 2022 15:53:26 -0500 Subject: [PATCH 13/17] Updated tree pull logic to also include ability to kill most things --- BaseClasses.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 983367fb..dad76954 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1101,7 +1101,8 @@ class CollectionState(object): if can_reach_non_bunny(region): return True - if any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']): + # 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 @@ -1193,7 +1194,7 @@ class CollectionState(object): return True # tree pulls - if any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']): + 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 From 0c32cac4f861583ae0129e924f958a61cbc41fcc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 23 May 2022 13:34:06 -0500 Subject: [PATCH 14/17] Fixed bush crab logic to only allow in enemizer --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index dad76954..d3be01e2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1116,7 +1116,7 @@ class CollectionState(object): return True # bush crabs (final item isn't considered) - if self.world.enemy_shuffle[player] != 'none': + 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): @@ -1208,7 +1208,7 @@ class CollectionState(object): return True # bush crabs (final item isn't considered) - if self.world.enemy_shuffle[player] != 'none': + 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): From 5cc809a93cc7e99b9b413d77e6e9a17372e68d5a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 28 May 2022 22:57:41 -0500 Subject: [PATCH 15/17] Added new DPad invert options for just L/R and just U/D --- Rom.py | 2 +- data/base2current.bps | Bin 91854 -> 91883 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 14430ae0..4c5e1114 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'af75da389ee773e103cbf0a13e689ac3' +RANDOMIZERBASEHASH = 'fc6f1d6ba782d08ac92600c31cc7ee43' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 61f24b6ceee3e74dae25d71e43c05979c53b356d..bcd2bb5621fe62c01137ff75e310c7042083743f 100644 GIT binary patch delta 5907 zcmX9>30xD$_uolE2zLU?t%OxMJTQVMA|fb)CyFOVrPc$Rs%WeAgxyHM0GlNYu)+!n zun_~|)~1T$fkv$aYK=#gs#vRSRkUqdwN=`m{0IM^{k(bKnR)Z}&6{~MZ@#DQ6M22P>7I+~#_kM@R^AL}r0rX&`EQvI;Z=n6sZ!meTsg zkwJ|YIH_7=IxEsqc>Y~9QjdR4aXpe&GD}~n12bU+7$j=Au`CLvg3*E~8{7>N!#ix8 zgeb@LC0tKW#5_U|B)-7ezf`XzYPp^+3V(c`izw8#z$YL~QVO|=&n5U1C=+CZn@}x? z1*hOSL9k%JEqGrre;8)tZbP4T?J?e{WGMVc-ndqy?bCBZNj>gbZtO{;DLfs{7tRJ1 zkQGLQHSjy(e6O)K?xhCL>We2tYAyZ~94XQSF4rS-f{#&ncS$2=EXN&3P(72>;)SqN zlrI?h0H!;A479MtX*PIe5sE(tF2(H}wx7aBH9*4oJ1_AAZh8S3z@`aEIj#K-u9Vcc zY=E4Spz!hcpvWaO>gWC$_w=|oQ~0?u<1;9xG^8weTSN_#y?8#P_XrtI;Y9bDpc@kIYu3)V#VLy@ zJguXD8%QLqFp|n9$q`>x;*) zm6e~Zrts(sFiW}^*x`9;0DirU>p5GVD1G*C|D*jNDkY9&bZ>`j@QA$Sp&g-no?P*?EA%PZ~Kpg&(o3l%)&65zBF(bwHt2M(KTs z30{;iPmj_ioDb4Ch}v2#k~mQFZ*V1gv$E+;`tJOdUF zOkF>`n;TjL=dUQ8h!#x#&?rJZW4gJX=|~(^(~upAJ+jRCtQ3Whqnmri8{5zP-t|Yf zGLKSLb#pt_hRfv|PbL14izuR$^N_w`8mNa-FiDLO^mGG-PwIw~eUE~@@R_f20E&;y zBei$}$EoBGZK{Ox<#?rKnO|R+FeOM0GvX%*(=xkZS^RQ&P3p%i{U`Z?Rg?cB`hPyq z?*aL;Ddy`RXU&B^Q+Emmc3Y^aCZUhwJC3&NH`|@@Eli<)3*yJ>lam1Gu*{skQ55`B zCuauKa{&)Yg~JK|{Tip@+$XB!{$=4}zE(&{$fo zX{Bv|BfyOHWBiv|BVBnR2j5?1tR_5hX_GNam2xI_+6(y2+!&!a={Edt?qq>St!2`@ zK9R?mYaHQEV5cx&}XO-fbYEFLiq*C}2vq}-L%IiE;xZ1$szgrr(b`iL4$u_?P{*j%W zDaa9SNE1;{3adAh3eB8eWePlK+1|7djAOU^{P$eq5d4ZAkUoB_*xlP?!mh6-6q$T9 zvK6Q*{5B>;VZ0$MDQ zwo-{&ERViP<{51DsYFw8%L%5~h2X1@O`-g@wA@HxV{Y5;G}m~}Kas+)a{-AIz{~^% z65)7K-!z;(a8k)3aT|ynl2}&z$)`6^qd?T!vtc17!DztW2|!i zv$Wh;X~1JStv()j9sQ?7fHoES6Tfe2fd|q5Pn)PjcnBn}^;V80FtqWueCe}3u|OaB zuZq{faj;TqTHz3xmNx`Lc)+AeF(unY_^Eb|v6vkae(|duyC6%IRg*G-g{${b?7ST6 zwEKr_{H$qmyu2$uMEDP(D(S@LHBDp z<&7E+k5+T|Uoaf+KEvS{(S}c?IJ_J#I6e`~hK0um$EYZ+z#(RK#nUh+p4I|KII|96 z*9&A&L-r=3YMYNvvt_?wp@1rN&n(+SDo1p{`^P7NGC1%n`J`dNG~*daz^FV@Vn_=h z3`;&KF2`LC8u4y3MV(MKrOP%ax@^wuvdBKnF0Nb8A{ZOXh~m36(PeZ#x!Is5cOc3^l#fV9 zt2K2MbRl$>Jow4(DSri1zn%o#;r_1|=GL&hPM95;fH^|=1O)mphg;pRmBt5Bd2|^O zQTMmuHaV!FfqZB3P<+IE9VqrEj2~el)Zgrkj#z!9n$MD5W0!C@w>cj?Lf*b^! zNrL`I^S+WDPV|pEF}tkJx61gllus09%l9(xNGaV#{aytZo*10F8Fk1QH9Dr2UMPiA%!mE)_7Yi0aQMpqeMWz3TCDF`K3 z89$ZrsR+%hGCq*;vk+Q>OqB7n5n5Gcd?w@PBDAB*_)^BtLnsF+%=PBe5X?skz4-+Q z>5xKiej!2?NTD~M4rSzN<6lx$3uE;feuZHJK}E5~w+-_Ma=hUzYLD-4R~e>|R5BU`7ptK-kDOG1TwPFJ(sUCqFkR2>tmgW3Uh7 zq9#wWiXv#L74+|&Z>o&)29%_tI_R%|2a{Vu2YOP5C{Id1+$WG4$rM7_tYSlED%{%= zEiRq$8EI&RCtD~Wg40j^4P4>m(+fv=so`EPwPuNw{RqJn1eZzqN==58&pWM@nu|Os zxipZ%rF@d``W1Ngbgb~>&-cT3rw@(X;!0Ek%&y`K2!k}(mFQkAd91jXfwNX_cW*?0 z$jmcu9z^MUnCUm+#nxD`4*u8r6ik6XpP3Fe!pO5D!8o|&Y+TfHxU*VmJV>_~M3K ztGUUlsMs?JUQ}5d`@c(w{F|(e6-SvJWetZk2bvj*q}hDBTHDfmt*L^tHXh@78`)vG z+59VSZG37ueA3$3e9}-{{u=`OEVK)u`Lu{jr>V0U;ZA9vBX^& zE(SB;hR*3=y5(r+d7*O*MS2WXKR*JD)L*C~Rf($u$k?B6Q9o zv0om``3@h)SSyModVzA4qKW;S2ktUVOXJx2^sqE^yG_N`bBQrMeZ{zr?qk?!w+duuEu;; zb3!FAy|lJ{t*t*O?QU=rS&m7wJEZR#gJ`qhvgz+gsY58AcZJzC@%Rn-3b^O{p;L!F ztrj_aOg8_GE|=<2iqy<{?Re4Px#LA?_)h&Ma_(VU+k7i09bt3WMZe038MZ&|PVyC3 zh_SZ65SRxaem_BkU6G%F0e6xIq+aTfzOG6BymdQaPs{D-kiJC{pWV5altf=N52pl4 z=Bt!2$;?rrB=edfM2$YvDmS$=qZeK~G50d(zcDZK1uc+aCL2RFZ|ZNT|L6tnEyF6l zNN$CtcMHH!7}zy5YR{jgla)ma2;pI@?Nl9iSXeEVBG~!?O8Vj1574t8o`;`y4FU>F zVHYnzM@8&?x$guWIf|L=Rbondonv?5N4?I8W!8AX?E8bjAgH?^=P{w=%Iw!Ufix=c zneguYQQ<1xPX(BLKtzUdDE{tSBaaagcA@;PZ8rfT8iuP-D&Rz@h17#FxhfqQ%c%8PD*U5U>|m8nAChv94`(-K=!#m4 z&J-QHSm+i0I)_Hoo0X-aH@(VX@x{7}-qujQ@paBd305LB_w~_@jNhaZxIVt5d4M(4 zgrOsN3+it89foY9HT35CjIDJ2De|a6UDRrrod4VlGkcUaI&TM9F9w2twP z)-mH2Kd<)SC-={IGLta&Msic93%`jLUvS2_|fL4SlfJo$J+VRJ$mrP~C^{z&)>>__qx z^4Hgo6l@2v5$T%WkV;p5Dg0ny^}55 zE~(fWQC%;^h>G<08msiGG8!5mFAnjb%@~?h1M->(b5x9(T32f_s(1krZVzMTdB6cr z>_82Fd9n#Chv`4>6M)Ioxf&4vB3@$q?J z-?qH@kLvz2fyoG7e%>eMtZU7xF7N6T;&ywYv#GoD;nXme(c;2MIWH#TqrXf$-t%mE)uR- ztg{^bHy5O$_n5pFGAql?63n6EhoDQz=ion+XK2x*CM-!?i2-{!AOET;j`?x>3N7Vj zb{c9Zqj~CZkUff-`%Ssn4l?;o><~Bj`Tr=tJ;X)sYR)-@LvCm2!&h^fGT-+xk&`U` z?^}Q@mGEmzaD<~=Ej%LACcy0QQQv65luLmh7*ZYqhRT)@8nk)V4=+M*!RV@35r1MO z_zpO`A0`D%cv43c@VpWW{FB9Mb4myb)OqhSmNf*#Aa+{>nrx1f8y_ncdEm}{I zA&8c}FAu{oAO|&DryXu6XZ#}T>O(e|nYF=BC=iKZ^caG@&DG0A zHUM{Kp9~BKSR_jq=HxM+yW(fnV zNyIF$0fXXdQ^gxatpuya+A38%s;x()HC5Xx{{HeG{Osq=`@Wesvv1zKnR)Z#s>`*j zF6DUK_cVun@#GnK?itA?5|m;#QM*U481^&gXdW&m$l=+$bP5wu>&V%qCO~Q-rTA5= zTND+%{v0P&sm-TFS_+?e8}-!T-&34Je3FdR>9xQOMuI`2`0JNq;Vcj%_^Tam2gwm7 zHcm`b;pyd^!x1@;&;juP;1*D!QzX@Lj!p`%yURtEXug4uLAZDUF^lHIbtixXd|Y@klKZEE~k>hPquRh zDLfu7bRQ*nO~YdM4S<4g+-C~@=zxhHW85`pnAAcYz6oygNCQV6qQGk!w>6m4;_cUwHfoj+?9dG8Fz~nsVIv8b2!n=betEeJKxInE4g2= zs6;(1(XiEXU(ybhW&`B%kTsJvO^&49HF(P&7RRkKy^jd`V&|RD;PgR|+ls<-M=KXl zNGivW8mXzzS$E61ZVfKqTQ6Iz8AI^i^UC`XHTY4}M}O~8<6X=Imj<=Nfm|SDImdD4 z^Hg1)io##D!Zp%Gpd6l-2I6!j=Qv%JEPeLO;YcLNr2XWiuXmH_vBeZ#-pU=P@Uiee z>6D@SS#FFuw6)!g6*B&#^9(EfjzpEAO`&M5`5lF|O+i zh3eU}4b-F9y55YqLOtGiolCuZL5{5W-*t|q@YmNl1#(L%oZdf49CVu#qa}8MW&LNZ zaddHErEvbTiV0}J@|)L)%1?-9+KDn9rIZT*lvy>ifpBbff_I@> znF6OWHp~KOEUm!&sjZ(g(1P`0xCCnS=knR?LuMn{0^@9 zqUd@k5$iM#A60zB=H?8>S9EYnOPso$NxZ7@&#MWrCXdaTGEB9Ah0ZBl$j4JSW%Od;&X+N21y$?GIaJlVwIhpY`;y9m(M zTuV0yeBQyCgPjqkG!gZsJSMf^mjL!=C;ob_$+gyQPmF#cuC8 zV8*U5Ct|bt(a2bsTVcNRI$!xw7-Mu#e@l+24$CaJ4sHHaP%x3`QSt_)7Rql*D~J*{ zwH~6mrZWM_6o#D%Or`*4At;cnxL=6zSJDdXl7?V!{c565j(6^icYYf09N8vs4oKD{ ziJih(Nn#BF5GQ~JOGX?^)?h__yhx2blK1s8u8*ckf>u$4hDf3_cT-6k%!}Sld1){q z{SzsCZ8Nd^s+u)kPQffMjS!VWW+4HU{k4E_{b*@io1FhDtsq_+_(ycUkF&`ZtBoFenm#(@=Zw^^BDo@p21TiZFtYH^DB zMX&Sif*fVezSQwXxcpnnIBzF)!s}zTYh;fF!<3OdjQe5-sWc8lr8`u?AsJ=bph<*j zTn2o1ILgKAwP}Dij5soQkg=9i==O2=XcdRQf)RMlDGtYoR(t}*;fvtPBNM=EXgo4F zZat+DI9-`-lW3SfiPivT1hX1pk8@;jW8MbBu*pxW&Mh=85KwSM!h;Q@;)M-%ADIX; zVEA|PiK7Q!XCy%cj4mR@rnEr9l)0{~3U}Yb^1F<42T*6eXSqjGCrEY4bW=*4z?Q4mR^Cf#7-x#ph9 zhaVM3@nuX>L7B$0?g!HVf*b^!QN$f4&!WGhUPnDE>u%}U-=%!AFi)Pyyd$M_Cw0ic zb{80TGiQzS2~rII(-_!i#z>OkjApa2&x~jT`@4+sB$;H=_<$7Ar*+}zYW8JH7_M{! zp?{)h6*7JX)YjDfrDwBc{7hymq5oIUu9ESeFk1ZwJ)0xrQgd>TSC4eTmkJ{_TX1~$i+Uw}|1 z8qk+t2-{AuucW*JP3n_Mc9V?%l=+&VUdi~+jFiTgUk;1a{8BhgU3!i>W?io(je-52lwZUoi&SKW$*YK}Hn88q z(FDBBC{#>Bi7~09rtUMOB;zxgkX`zXsM(ia%=nPx*QQuPmv3MP`Z4Y*a*Hvx6q;)V zed}0kVC9V!kV7))leGpmtWQ?++hIn^k7YPJxvzqs8#c zRS!(^)eqp0Eff&KtmFRzcbI-+L5xHNzm=%enNs5l1XB=PBIWh!EGb`fR3WvLN+`KB z2qjqjw2Dvmww{6Z6Y;_obBdw&$-N^sdJuYm*_He*!XzE&LEKv|eiVBr3t!g8ZSRTf z+f(x_8wXKZKW6$3_{+(7FceBpJw;di+f&m)6r6N=1Q-L?pH2i;*mycbGL^2u)a^=h zyM4fgjBnu2r#A(bvo8)Pn|(c{0*j~33mudgP$h1 z71f!RAXZ#w%ChBeXNC@`FtuWeHrYEYW9E141eP zP-d&Qmb962QOnL|^Eq@7TmOf+{IGZHheim`JrV?NgWsHw02nl#Ut6HE9B7NJ-Z2rL zSE`!&zCuKuh%yf{TPhp(s#BU7ikxpOrfW3cG*btvDOJ-(p0|-D)}NXicvVxQbgNb^}}+0kiXXG>#rr(m|RIXH4y&$Qyf?h zhuw?@bKugOa^E>;TYWxwNA

XI{>4l`+1PVCl^_;7dsST;Ln5g>%Yk$xzE`N)>7; zFfLAnvR}d%C)4@^IdvFTqO1;7pLlW~Bs@cL#xi&~!J^dtCXYIdAfVXjI^C@Y&sw z5$ml#(CUQ5O^DqRjrx{n(Nuh;5AQtt2(#&t`zr6)^Il^!}@ z;uG;EpGMS^lcS_Jyv{e`8QSx{ZJ~VAoBVZRtXyd6?WLO-{|V)A&7|_?er=&<3?00= zsJZbk81f~prZ?7S<9A{!h_vxDNO%pE#o^%%Z$x< zUgOOt^vyVd$(sx5dr{tQHfBbAd3DU3=3i6Y;H7)xef@Oi@HhDZx#LmN?E{P9+L2Il ze-{eu>ie-E1zx^imA281_(gHm=~6o9fW@sQ%}0La8vgXnV7cdYQ)1korl^E-x)*A( z<=WYe!<{f}`RN8(>;>{|{fNMtOC3p=fM1JVUJ`yX5o>cnnWpDBc;EpM&~|-)QLOxX zRE|>KDG697$C_A7e(^e-_Hc~n_tyhfKT=8@DGI?kPFTtMhtY=Ycm-T(t!%Bx86>rD|e+e z>d!eI?$dT_eDFcW6+v+4fA@o$hYFpZr<%)LL(c8NphscVMa+KwK)_8YfLq><7W}G% zO>f7$Wb8Jkr^6?2n}hm!5Qd=WJ>=5r&vA7vW>4dLoDt61%#Ijq^SdY@*2cZ87EP-9 z8$Re+9j5lmu2_5@PCTX8Us8Kyp9zIICrclaZgf%U@WmoQT$%~K7* z_Go7A4^^&qkj-x}4)K7x|D(Wm81kV%m|z|Ip#{jy{;f&Q2)oD<5n1tN4B<_{|Lctb zi>pQge-KtR1BA&I5o#13YlfAgC*Wvnef1to9>`pE+;%mi@!QO_6lOL38>l%&iwg u9Tyt6hqRQ&g2;Y8!<*D{tU7a Date: Fri, 10 Jun 2022 17:46:05 -0500 Subject: [PATCH 16/17] Minor capitalization --- Items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Items.py b/Items.py index bc685236..dfb19e19 100644 --- a/Items.py +++ b/Items.py @@ -42,7 +42,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'), 'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'), 'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'), - 'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'), + 'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'), 'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'), 'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'), 'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'), From d9d7a2afab34edebf51854ee52a39ddfa622a294 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 10 Jun 2022 17:53:29 -0500 Subject: [PATCH 17/17] Version bump 0.2.7.3 --- CHANGELOG.md | 10 +++++++++- OverworldShuffle.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae14e2cf..dbb65f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ # Changelog +### 0.2.7.3 +- Restructured OWR algorithm to include some additional scenarios not previously allowed +- Added new Inverted D-pad controls for Social Distorion (ie. Mirror Mode) support +- Crossed OWR/Special OW Areas are now included in the spoiler log +- Fixed default TF pieces with Trinity in Mystery +- Added bush crabs to rupee farm logic (only in non-enemizer) +- Updated tree pull logic to also require ability to kill most things + ### 0.2.7.2 -- Special OW Area are now shuffled in Layout Shuffle (Zora/Hobo/Pedestal) +- Special OW Areas are now shuffled in Layout Shuffle (Zora/Hobo/Pedestal) - Fixed some broken water region graph modelling, fixed some reachability logic - Some minor code simplifications diff --git a/OverworldShuffle.py b/OverworldShuffle.py index c5485643..287961be 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -6,7 +6,7 @@ from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel from Utils import bidict -version_number = '0.2.7.2' +version_number = '0.2.7.3' version_branch = '-u' __version__ = '%s%s' % (version_number, version_branch)