From cf2e001447df40b479aa17b9f8ea00ef6401016e Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 17 Aug 2023 13:42:45 -0600 Subject: [PATCH 001/123] Fix for TFH --- source/item/FillUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 6a055584..595e86c9 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -357,7 +357,7 @@ def determine_major_items(world, player): major_item_set.add('Single Arrow') if world.keyshuffle[player] == 'universal': major_item_set.add('Small Key (Universal)') - if world.goal == 'triforcehunt': + if world.goal[player] == 'triforcehunt': major_item_set.add('Triforce Piece') if world.bombbag[player]: major_item_set.add('Bomb Upgrade (+10)') From 26727fbfca0c1a8760513c3f16eb83618cc033d3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 25 Aug 2023 11:18:21 -0500 Subject: [PATCH 002/123] Added Customizer support for OWR Layout and Whirlpool Shuffle --- BaseClasses.py | 14 ++- OverworldShuffle.py | 154 +++++++++++++++++++++++++------ source/classes/CustomSettings.py | 33 ++++++- 3 files changed, 169 insertions(+), 32 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 25aebbb1..3615d0e9 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2784,6 +2784,7 @@ class Spoiler(object): self.world = world self.hashes = {} self.overworlds = {} + self.whirlpools = {} self.maps = {} self.entrances = {} self.doors = {} @@ -2808,6 +2809,12 @@ class Spoiler(object): else: self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) + def set_whirlpool(self, entrance, exit, direction, player): + if self.world.players == 1: + self.whirlpools[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)]) + else: + self.whirlpools[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) + def set_map(self, type, text, data, player): if self.world.players == 1: self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)]) @@ -3010,6 +3017,7 @@ class Spoiler(object): self.parse_data() out = OrderedDict() out['Overworld'] = list(self.overworlds.values()) + out['Whirlpools'] = list(self.whirlpools.values()) out['Maps'] = list(self.maps.values()) out['Entrances'] = list(self.entrances.values()) out['Doors'] = list(self.doors.values()) @@ -3181,7 +3189,7 @@ class Spoiler(object): for fairy, bottle in self.bottles.items(): outfile.write(f'{fairy}: {bottle}\n') - if self.overworlds or self.maps: + if self.overworlds or self.whirlpools or self.maps: outfile.write('\n\nOverworld:\n\n') # flute shuffle @@ -3217,6 +3225,10 @@ class Spoiler(object): outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(self.maps[('groups', player)]['text'] + '\n\n') + if self.whirlpools: + # whirlpools + 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","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()])) + 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 f1752dbd..8d242d2a 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -3,6 +3,7 @@ from collections import OrderedDict, defaultdict from DungeonGenerator import GenerationException from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_light_dark_world_regions +from source.overworld.EntranceShuffle2 import connect_simple from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel from OverworldGlitchRules import create_owg_connections from Utils import bidict @@ -278,16 +279,24 @@ def link_overworld(world, player): connect_simple(world, from_whirlpool, to_region, player) connect_simple(world, to_whirlpool, from_region, player) else: + def connect_whirlpool(from_whirlpool, to_whirlpool): + (from_owid, from_name, from_region) = from_whirlpool + (to_owid, to_name, to_region) = to_whirlpool + connect_simple(world, from_name, to_region, player) + connect_simple(world, to_name, 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 + connected_whirlpools.append(tuple((from_name, to_name))) + world.spoiler.set_whirlpool(from_name, to_name, 'both', player) + + whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ] whirlpool_candidates = [[],[]] + connected_whirlpools = [] world.owwhirlpools[player] = [None] * 8 for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections: if world.owCrossed[player] == 'polar' and world.owMixed[player] and from_owid == 0x55: # connect the 2 DW whirlpools in Polar Mixed - connect_simple(world, from_whirlpool, to_region, player) - connect_simple(world, to_whirlpool, from_region, player) - world.owwhirlpools[player][7] = from_owid - world.owwhirlpools[player][6] = to_owid - world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player) + connect_whirlpool((from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region)) 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'] \ @@ -304,19 +313,27 @@ def link_overworld(world, player): whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region))) # shuffle happens here - whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ] + if world.customizer: + custom_whirlpools = world.customizer.get_whirlpools() + if custom_whirlpools and player in custom_whirlpools: + custom_whirlpools = custom_whirlpools[player] + if 'two-way' in custom_whirlpools: + for whirlpools in whirlpool_candidates: + for whirlname1, whirlname2 in custom_whirlpools['two-way'].items(): + whirl1 = next((w for w in whirlpools if w[1] == whirlname1), None) + whirl2 = next((w for w in whirlpools if w[1] == whirlname2), None) + if whirl1 and whirl2: + whirlpools.remove(whirl1) + whirlpools.remove(whirl2) + connect_whirlpool(whirl1, whirl2) + elif whirl1 != whirl2 or not any(w for w in connected_whirlpools if (whirlname1 in w) and (whirlname2 in w)): + raise GenerationException('Attempting to connect whirlpools not in same pool: \'%s\' <-> \'%s\'', whirl1, whirl2) for whirlpools in whirlpool_candidates: random.shuffle(whirlpools) while len(whirlpools): if len(whirlpools) % 2 == 1: x=0 - 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) + connect_whirlpool(whirlpools.pop(), whirlpools.pop()) # layout shuffle logging.getLogger('').debug('Shuffling overworld layout') @@ -337,7 +354,7 @@ def link_overworld(world, player): world.owsectors[player] = build_sectors(world, player) else: - if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: + if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel': for exitname, destname in parallelsimilar_connections: connect_two_way(world, exitname, destname, player, connected_edges) @@ -345,10 +362,10 @@ def link_overworld(world, player): for exitname, destname in test_connections: connect_two_way(world, exitname, destname, player, connected_edges) - connect_custom(world, connected_edges, player) - # layout shuffle groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) + + connect_custom(world, connected_edges, groups, player) tries = 100 valid_layout = False @@ -564,21 +581,100 @@ def link_overworld(world, player): s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('flute', text_output, new_spots, player) -def connect_custom(world, connected_edges, player): - if hasattr(world, 'custom_overworld') and world.custom_overworld[player]: - for edgename1, edgename2 in world.custom_overworld[player]: - if edgename1 in connected_edges or edgename2 in connected_edges: - owedge1 = world.check_for_owedge(edgename1, player) - owedge2 = world.check_for_owedge(edgename2, player) - if owedge1.dest is not None and owedge1.dest.name == owedge2.name: - continue # if attempting to connect a pair that was already connected earlier, allow it to continue - raise RuntimeError('Invalid plando connection: rule violation based on current settings') - connect_two_way(world, edgename1, edgename2, player, connected_edges) - if world.owKeepSimilar[player]: #TODO: If connecting an edge that belongs to a similar pair, the remaining edges need to get connected automatically +def connect_custom(world, connected_edges, groups, player): + def remove_pair_from_pool(edgename1, edgename2): + def add_to_unresolved(forward_set, back_set): + if len(forward_set) > 1: + if edgename1 in forward_set: + forward_set.remove(edgename1) + back_set.remove(edgename2) + else: + back_set.remove(edgename1) + forward_set.remove(edgename2) + unresolved_similars.append(tuple((forward_set, back_set))) + for forward_pool, back_pool in groups: continue + if len(forward_pool[0]) == 1: + if [edgename1] in forward_pool: + if [edgename2] in back_pool: + forward_pool.remove([edgename1]) + back_pool.remove([edgename2]) + return + else: + break + elif [edgename1] in back_pool: + if [edgename2] in forward_pool: + back_pool.remove([edgename1]) + forward_pool.remove([edgename2]) + return + else: + break + else: + forward_similar = next((x for x in forward_pool if edgename1 in x), None) + if forward_similar: + back_similar = next((x for x in back_pool if edgename2 in x), None) + if back_similar: + forward_pool.remove(forward_similar) + back_pool.remove(back_similar) + add_to_unresolved(forward_similar, back_similar) + return + else: + break + else: + back_similar = next((x for x in back_pool if edgename1 in x), None) + if back_similar: + forward_similar = next((x for x in forward_pool if edgename2 in x), None) + if forward_similar: + back_pool.remove(forward_similar) + forward_pool.remove(back_similar) + add_to_unresolved(forward_similar, back_similar) + return + else: + break + for pair in unresolved_similars: + forward_set, back_set = pair + if edgename1 in forward_set: + if edgename2 in back_set: + unresolved_similars.remove(pair) + add_to_unresolved(forward_set, back_set) + return + else: + break + else: + if edgename1 in back_set: + if edgename2 in forward_set: + unresolved_similars.remove(pair) + add_to_unresolved(forward_set, back_set) + return + else: + break + raise GenerationException('Could not find both OW edges in same pool: \'%s\' <-> \'%s\'', edgename1, edgename2) -def connect_simple(world, exitname, regionname, player): - world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) + if world.customizer: + custom_edges = world.customizer.get_owedges() + if custom_edges and player in custom_edges: + custom_edges = custom_edges[player] + if 'two-way' in custom_edges: + unresolved_similars = [] + for edgename1, edgename2 in custom_edges['two-way'].items(): + edge1 = world.check_for_owedge(edgename1, player) + edge2 = world.check_for_owedge(edgename2, player) + if edgename1 not in connected_edges and edgename2 not in connected_edges: + # attempt connection + remove_pair_from_pool(edgename1, edgename2) + connect_two_way(world, edgename1, edgename2, player, connected_edges) + # resolve parallel + if (world.owShuffle[player] == 'parallel' and + (edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys())): + parallel_forward_edge = parallel_links[edgename1] if edgename1 in parallel_links.keys() else parallel_links.inverse[edgename1][0] + parallel_back_edge = parallel_links[edgename2] if edgename2 in parallel_links.keys() else parallel_links.inverse[edgename2][0] + remove_pair_from_pool(parallel_forward_edge, parallel_back_edge) + elif not edge1.dest or not edge2.dest or edge1.dest.name != edgename2 or edge2.dest.name != edgename1: + raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2) + # connect leftover similars + for forward_pool, back_pool in unresolved_similars: + for (forward_edge, back_edge) in zip(forward_pool, back_pool): + connect_two_way(world, forward_edge, back_edge, player, connected_edges) def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): edge1 = world.get_entrance(edgename1, player) diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 7eb8a31c..58c9494b 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -196,6 +196,16 @@ class CustomSettings(object): return self.file_source['advanced_placements'] return None + def get_owedges(self): + if 'ow-edges' in self.file_source: + return self.file_source['ow-edges'] + return None + + def get_whirlpools(self): + if 'ow-whirlpools' in self.file_source: + return self.file_source['ow-whirlpools'] + return None + def get_owtileflips(self): if 'ow-tileflips' in self.file_source: return self.file_source['ow-tileflips'] @@ -355,21 +365,40 @@ class CustomSettings(object): placements[location.player][location.name] = location.item.name def record_overworld(self, world): + self.world_rep['ow-edges'] = edges = {} + self.world_rep['ow-whirlpools'] = whirlpools = {} self.world_rep['ow-tileflips'] = flips = {} + self.world_rep['ow-flutespots'] = flute = {} for p in self.player_range: + connections = edges[p] = {} + connections['two-way'] = {} + connections['one-way'] = {} + whirlconnects = whirlpools[p] = {} + whirlconnects['two-way'] = {} + whirlconnects['one-way'] = {} + # tile flips if p in world.owswaps and len(world.owswaps[p][0]) > 0: flips[p] = {} flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) flips[p]['force_flip'].sort() flips[p]['undefined_chance'] = 0 - self.world_rep['ow-flutespots'] = flute = {} - for p in self.player_range: + # flute spots flute[p] = {} if p in world.owflutespots: flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p])) else: flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections)) flute[p]['forbid'] = [] + for key, data in world.spoiler.overworlds.items(): + player = data['player'] if 'player' in data else 1 + connections = edges[player] + sub = 'two-way' if data['direction'] == 'both' else 'one-way' + connections[sub][data['entrance']] = data['exit'] + for key, data in world.spoiler.whirlpools.items(): + player = data['player'] if 'player' in data else 1 + whirlconnects = whirlconnects[player] + sub = 'two-way' if data['direction'] == 'both' else 'one-way' + whirlconnects[sub][data['entrance']] = data['exit'] def record_entrances(self, world): self.world_rep['entrances'] = entrances = {} From e8193463b46138be402b35aee6c18ddf9f34afdf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 25 Aug 2023 11:19:43 -0500 Subject: [PATCH 003/123] Fixed error with Mixed OWR + Flute Shuffle --- OverworldShuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 8d242d2a..0ff042b9 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -361,7 +361,7 @@ def link_overworld(world, player): #TODO: Remove, just for testing for exitname, destname in test_connections: connect_two_way(world, exitname, destname, player, connected_edges) - + # layout shuffle groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) @@ -1370,7 +1370,7 @@ def validate_layout(world, player): # check if can be accessed flute if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): owid = OWTileRegions[region_name] - if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid][0]: + if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid % 40][0]: if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections: unreachable_regions.pop(region_name) explore_region(region_name) From 3cc166bd28cb7b87d6abc45617aec16fefd6092b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 25 Aug 2023 11:19:43 -0500 Subject: [PATCH 004/123] Fixed error with Mixed OWR + Flute Shuffle --- OverworldShuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 8d242d2a..b25051ee 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -361,7 +361,7 @@ def link_overworld(world, player): #TODO: Remove, just for testing for exitname, destname in test_connections: connect_two_way(world, exitname, destname, player, connected_edges) - + # layout shuffle groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) @@ -1370,7 +1370,7 @@ def validate_layout(world, player): # check if can be accessed flute if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): owid = OWTileRegions[region_name] - if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid][0]: + if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid % 0x40][0]: if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections: unreachable_regions.pop(region_name) explore_region(region_name) From 2f2764adb4e2c185e544a9b7c3d4765abff8ecc6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 25 Aug 2023 13:20:24 -0500 Subject: [PATCH 005/123] Fixed error with Mixed OWR + Flute Shuffle --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index b25051ee..a9c26f57 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1370,7 +1370,7 @@ def validate_layout(world, player): # check if can be accessed flute if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): owid = OWTileRegions[region_name] - if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid % 0x40][0]: + if owid < 0x80 and owid % 0x40 in flute_data and region_name in flute_data[owid % 0x40][0]: if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections: unreachable_regions.pop(region_name) explore_region(region_name) From b68438bc351fc9985c5a70a4b416e4ed71108337 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 25 Aug 2023 14:14:03 -0500 Subject: [PATCH 006/123] Added Customizer support for OWR Layout and Whirlpool Shuffle --- OverworldShuffle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a9c26f57..d251e157 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -593,6 +593,7 @@ def connect_custom(world, connected_edges, groups, player): forward_set.remove(edgename2) unresolved_similars.append(tuple((forward_set, back_set))) for forward_pool, back_pool in groups: + if not len(forward_pool): continue if len(forward_pool[0]) == 1: if [edgename1] in forward_pool: From 59d446aac2b915617d43e9df858dc242e216ec7d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 25 Aug 2023 14:15:33 -0500 Subject: [PATCH 007/123] Adding consideration when flute is removed entirely from item pool --- OverworldShuffle.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d251e157..8274f2d1 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1030,6 +1030,9 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): return list(groups.values()) def create_flute_exits(world, player): + flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) + if not flute_in_pool: + return 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']): if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): exitname = 'Flute From ' + region.name @@ -1121,7 +1124,7 @@ def can_reach_smith(world, player): for exit in region.exits: if not found and exit.connected_region is not None: if exit.spot_type == 'Flute': - if any(map(lambda i: i.name == 'Ocarina (Activated)', world.precollected_items)): + if any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, world.precollected_items)): for flutespot in exit.connected_region.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: explore_region(flutespot.connected_region.name, flutespot.connected_region) @@ -1239,7 +1242,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F region = base_world.get_region(region_name, player) for exit in region.exits: if exit.connected_region is not None: - if any(map(lambda i: i.name == 'Ocarina (Activated)', base_world.precollected_items)) and exit.spot_type == 'Flute': + if any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, base_world.precollected_items)) and exit.spot_type == 'Flute': fluteregion = exit.connected_region for flutespot in fluteregion.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: @@ -1305,7 +1308,8 @@ def validate_layout(world, player): item_entrances = list(zip(*default_item_connections))[0] shop_entrances = list(zip(*default_shop_connections))[0] drop_entrances = list(zip(*default_drop_connections + default_dropexit_connections))[0] - + flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) + def explore_region(region_name, region=None): if stack_size3a() > 500: raise GenerationException(f'Infinite loop detected for "{region_name}" located at \'validate_layout\'') @@ -1343,11 +1347,12 @@ def validate_layout(world, player): start_region = 'Dark Chapel Area' explore_region(start_region) - if not world.is_tile_swapped(0x30, player): - start_region = 'Desert Teleporter Ledge' - else: - start_region = 'Mire Teleporter Ledge' - explore_region(start_region) + if flute_in_pool: + if not world.is_tile_swapped(0x30, player): + start_region = 'Desert Teleporter Ledge' + else: + start_region = 'Mire Teleporter Ledge' + explore_region(start_region) if not world.is_tile_swapped(0x1b, player): start_region = 'Pyramid Area' @@ -1369,7 +1374,7 @@ def validate_layout(world, player): unreachable_count = len(unreachable_regions) for region_name in reversed(unreachable_regions): # check if can be accessed flute - if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): + if flute_in_pool and unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): owid = OWTileRegions[region_name] if owid < 0x80 and owid % 0x40 in flute_data and region_name in flute_data[owid % 0x40][0]: if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections: @@ -1394,7 +1399,11 @@ def validate_layout(world, player): break if unreachable_count != len(unreachable_regions): break - + + if not flute_in_pool: + unreachable_regions.pop('Desert Teleporter Ledge') + unreachable_regions.pop('Mire Teleporter Ledge') + if len(unreachable_regions): return False From 179e605253244e5d05fcec7cf6e06644b920cf6d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 27 Aug 2023 08:19:02 -0500 Subject: [PATCH 008/123] Added Customizer support for OWR Layout and Whirlpool Shuffle --- OverworldShuffle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 8274f2d1..d70ae9f5 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -326,8 +326,10 @@ def link_overworld(world, player): whirlpools.remove(whirl1) whirlpools.remove(whirl2) connect_whirlpool(whirl1, whirl2) - elif whirl1 != whirl2 or not any(w for w in connected_whirlpools if (whirlname1 in w) and (whirlname2 in w)): - raise GenerationException('Attempting to connect whirlpools not in same pool: \'%s\' <-> \'%s\'', whirl1, whirl2) + elif whirl1 != whirl2: + raise GenerationException('Attempting to connect whirlpools not in same pool: \'%s\' <-> \'%s\'', whirlname1, whirlname2) + elif any(w for w in connected_whirlpools if (whirlname1 in w) != (whirlname2 in w)): + raise GenerationException('Attempting to connect whirlpools already connected: \'%s\' <-> \'%s\'', whirlname1, whirlname2) for whirlpools in whirlpool_candidates: random.shuffle(whirlpools) while len(whirlpools): From 712188251d4c16dba541d9caa514a4c94a2236cd Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 4 Sep 2023 19:32:34 -0500 Subject: [PATCH 009/123] Restructuring parallel_links in OWR --- OverworldShuffle.py | 48 +++++++-------- docs/ideas/shufflehorizontal.yaml | 72 ++++++++++++++++++++++ docs/ideas/shufflelargescreens.yaml | 96 +++++++++++++++++++++++++++++ docs/ideas/shufflelight.yaml | 90 +++++++++++++++++++++++++++ docs/ideas/shufflesmallscreens.yaml | 58 +++++++++++++++++ docs/ideas/shufflevertical.yaml | 81 ++++++++++++++++++++++++ 6 files changed, 421 insertions(+), 24 deletions(-) create mode 100644 docs/ideas/shufflehorizontal.yaml create mode 100644 docs/ideas/shufflelargescreens.yaml create mode 100644 docs/ideas/shufflelight.yaml create mode 100644 docs/ideas/shufflesmallscreens.yaml create mode 100644 docs/ideas/shufflevertical.yaml diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d70ae9f5..59a610ee 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -14,17 +14,19 @@ version_branch = '-u' __version__ = '%s%s' % (version_number, version_branch) +parallel_links_new = None # needs to be globally available, reset every new generation/player + def link_overworld(world, player): + global parallel_links_new + # setup mandatory connections for exitname, regionname in mandatory_connections: connect_simple(world, exitname, regionname, player) def performSwap(groups, swaps): def getParallel(edgename): - if edgename in parallel_links: - return parallel_links[edgename] - elif edgename in parallel_links.inverse: - return parallel_links.inverse[edgename][0] + if edgename in parallel_links_new: + return parallel_links_new[edgename] else: raise Exception('No parallel edge found for edge %s', edgename) @@ -112,16 +114,19 @@ def link_overworld(world, player): swapped_edges = list() # restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship + 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'] for group in trimmed_groups.keys(): (std, region, axis, terrain, parallel, _) = group if parallel == IsParallel.Yes: (forward_edges, back_edges) = trimmed_groups[group] if ['Maze Race ES'] in forward_edges: - forward_edges = list(filter((['Maze Race ES']).__ne__, forward_edges)) + forward_edges.remove(['Maze Race ES']) trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][0].append(['Maze Race ES']) if ['Kakariko Suburb WS'] in back_edges: - back_edges = list(filter((['Kakariko Suburb WS']).__ne__, back_edges)) + back_edges.remove(['Kakariko Suburb WS']) trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Kakariko Suburb WS']) trimmed_groups[group] = (forward_edges, back_edges) else: @@ -129,14 +134,15 @@ def link_overworld(world, player): (std, region, axis, terrain, _, _) = group (forward_edges, back_edges) = trimmed_groups[group] if ['Dig Game EC', 'Dig Game ES'] in forward_edges: - forward_edges = list(filter((['Dig Game EC', 'Dig Game ES']).__ne__, forward_edges)) + forward_edges.remove(['Dig Game EC', 'Dig Game ES']) trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1)][0].append(['Dig Game ES']) trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][0].append(['Dig Game EC']) if ['Frog WC', 'Frog WS'] in back_edges: - back_edges = list(filter((['Frog WC', 'Frog WS']).__ne__, back_edges)) + back_edges.remove(['Frog WC', 'Frog WS']) trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1)][1].append(['Frog WS']) trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC']) trimmed_groups[group] = (forward_edges, back_edges) + parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})} connected_edges = [] if world.owShuffle[player] != 'vanilla': @@ -201,19 +207,13 @@ def link_overworld(world, player): logging.getLogger('').debug('Crossing overworld edges') 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 edge not in parallel_links_new: if world.owCrossed[player] == 'grouped': # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask @@ -244,13 +244,13 @@ def link_overworld(world, player): (mode, wrld, dir, terrain, parallel, count) = group if wrld == WorldType.Light and mode != OpenStd.Standard: for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): - 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): for edge in forward_set: crossed_edges.append(edge) elif world.owCrossed[player] == 'limited': crossed_candidates.append(forward_set) + if forward_set[0] in parallel_links_new: else: for edge in forward_set: if world.owCrossed[player] == 'chaos' and random.randint(0, 1): @@ -667,12 +667,11 @@ def connect_custom(world, connected_edges, groups, player): remove_pair_from_pool(edgename1, edgename2) connect_two_way(world, edgename1, edgename2, player, connected_edges) # resolve parallel - if (world.owShuffle[player] == 'parallel' and - (edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys())): - parallel_forward_edge = parallel_links[edgename1] if edgename1 in parallel_links.keys() else parallel_links.inverse[edgename1][0] - parallel_back_edge = parallel_links[edgename2] if edgename2 in parallel_links.keys() else parallel_links.inverse[edgename2][0] remove_pair_from_pool(parallel_forward_edge, parallel_back_edge) elif not edge1.dest or not edge2.dest or edge1.dest.name != edgename2 or edge2.dest.name != edgename1: + if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new: + parallel_forward_edge = parallel_links_new[edge1.name] + parallel_back_edge = parallel_links_new[edge2.name] raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2) # connect leftover similars for forward_pool, back_pool in unresolved_similars: @@ -716,10 +715,10 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): # connecting parallel connections if world.owShuffle[player] in ['vanilla', 'parallel']: - if (edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys()): + if edgename1 in parallel_links_new: try: - parallel_forward_edge = parallel_links[edgename1] if edgename1 in parallel_links.keys() else parallel_links.inverse[edgename1][0] - parallel_back_edge = parallel_links[edgename2] if edgename2 in parallel_links.keys() else parallel_links.inverse[edgename2][0] + parallel_forward_edge = parallel_links_new[edgename1] + parallel_back_edge = parallel_links_new[edgename2] 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: @@ -2075,7 +2074,8 @@ mirror_connections = { } parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'), - ('Dig Game EC', 'Frog WC') + ('Dig Game EC', 'Frog WC'), + ('Dig Game ES', 'Frog WS') ] # non shuffled overworld diff --git a/docs/ideas/shufflehorizontal.yaml b/docs/ideas/shufflehorizontal.yaml new file mode 100644 index 00000000..63246b32 --- /dev/null +++ b/docs/ideas/shufflehorizontal.yaml @@ -0,0 +1,72 @@ +settings: + 1: + ow_mixed: false +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods SW: Lost Woods Pass NW + Lost Woods SC: Lost Woods Pass NE + Lost Woods SE: Kakariko Fortune NE + Skull Woods SW: Skull Woods Pass NW + Skull Woods SC: Skull Woods Pass NE + Skull Woods SE: Dark Fortune NE + Lumberjack SW: Mountain Pass NW + Dark Lumberjack SW: Bumper Cave NW + Mountain Pass SE: Kakariko Pond NE + Bumper Cave SE: Outcast Pond NE + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE: Zora Approach NE + Catfish SE: Catfish Approach NE + Lost Woods Pass SW: Kakariko NW + Lost Woods Pass SE: Kakariko NC + Skull Woods Pass SW: Village of Outcasts NW + Skull Woods Pass SE: Village of Outcasts NC + Kakariko Fortune SC: Kakariko NE + Dark Fortune SC: Village of Outcasts NE + Kakariko Pond SW: Forgotten Forest NW + Kakariko Pond SE: Forgotten Forest NE + Outcast Pond SW: Shield Shop NW + Outcast Pond SE: Shield Shop NE + River Bend SW: Wooden Bridge NW + River Bend SC: Wooden Bridge NC + River Bend SE: Wooden Bridge NE + Qirn Jump SW: Broken Bridge NW + Qirn Jump SC: Broken Bridge NC + Qirn Jump SE: Broken Bridge NE + Kakariko SE: Kakariko Suburb NE + Village of Outcasts SE: Frog NE + Hyrule Castle SW: Central Bonk Rocks NW + Hyrule Castle SE: Links House NE + Pyramid SW: Dark Bonk Rocks NW + Pyramid SE: Big Bomb Shop NE + Wooden Bridge SW: Sand Dunes NW + Broken Bridge SW: Dark Dunes NW + Eastern Palace SW: Tree Line NW + Eastern Palace SE: Eastern Nook NE + Palace of Darkness SW: Dark Tree Line NW + Palace of Darkness SE: Palace of Darkness Nook NE + Sand Dunes SC: Stone Bridge NC + Dark Dunes SC: Hammer Bridge NC + Flute Boy SW: Flute Boy Approach NW + Flute Boy SC: Flute Boy Approach NC + Stumpy SW: Stumpy Approach NW + Stumpy SC: Stumpy Approach NC + Central Bonk Rocks SW: C Whirlpool NW + Dark Bonk Rocks SW: Dark C Whirlpool NW + Links House SC: Statues NC + Big Bomb Shop SC: Hype Cave NC + Stone Bridge SC: Lake Hylia NW + Hammer Bridge SC: Ice Lake NW + Tree Line SC: Lake Hylia NC + Tree Line SE: Lake Hylia NE + Dark Tree Line SC: Ice Lake NC + Dark Tree Line SE: Ice Lake NE + C Whirlpool SC: Dam NC + Dark C Whirlpool SC: Swamp NC + Statues SC: South Pass NC + Hype Cave SC: Dark South Pass NC + Ice Cave SW: Octoballoon NW + Ice Cave SE: Octoballoon NE + Shopping Mall SW: Bomber Corner NW + Shopping Mall SE: Bomber Corner NE diff --git a/docs/ideas/shufflelargescreens.yaml b/docs/ideas/shufflelargescreens.yaml new file mode 100644 index 00000000..93f7472d --- /dev/null +++ b/docs/ideas/shufflelargescreens.yaml @@ -0,0 +1,96 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES: Kakariko Suburb WS + Dig Game EC: Frog WC + Dig Game ES: Frog WS + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS8 + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* \ No newline at end of file diff --git a/docs/ideas/shufflelight.yaml b/docs/ideas/shufflelight.yaml new file mode 100644 index 00000000..0a2f9f58 --- /dev/null +++ b/docs/ideas/shufflelight.yaml @@ -0,0 +1,90 @@ +meta: + players: 1 +settings: + 1: + ow_shuffle: full + ow_whirlpool: true + ow_crossed: none + ow_keepsimilar: false + ow_swapped: false + ow_terrain: true + ow_decoupled: none + ow_mixed: false + ow_fluteshuffle: vanilla +ow-edges: + 1: + two-way: + Skull Woods EN: Dark Lumberjack WN + Skull Woods SW: Skull Woods Pass NW + Skull Woods SC: Skull Woods Pass NE + Skull Woods SE: Dark Fortune NE + Dark Lumberjack SW: Bumper Cave NW + West Dark Death Mountain EN: East Dark Death Mountain WN + West Dark Death Mountain ES: East Dark Death Mountain WS + East Dark Death Mountain EN: Turtle Rock WN + Bumper Cave SE: Outcast Pond NE + Catfish SE: Catfish Approach NE + Skull Woods Pass SW: Village of Outcasts NW + Skull Woods Pass SE: Village of Outcasts NC + Dark Fortune EN: Outcast Pond WN + Dark Fortune ES: Outcast Pond WS + Dark Fortune SC: Village of Outcasts NE + Outcast Pond EN: Dark Chapel WN + Outcast Pond ES: Dark Chapel WS + Outcast Pond SW: Shield Shop NW + Outcast Pond SE: Shield Shop NE + Dark Chapel EC: Dark Graveyard WC + Dark Graveyard EC: Qirn Jump WC + Qirn Jump EN: Dark Witch WN + Qirn Jump EC: Dark Witch WC + Qirn Jump ES: Dark Witch WS + Qirn Jump SW: Broken Bridge NW + Qirn Jump SC: Broken Bridge NC + Qirn Jump SE: Broken Bridge NE + Dark Witch EN: Catfish Approach WN + Dark Witch EC: Catfish Approach WC + Village of Outcasts ES: Hammer Pegs WS + Village of Outcasts SE: Frog NE + Pyramid ES: Dark Dunes WN + Pyramid SW: Dark Bonk Rocks NW + Pyramid SE: Big Bomb Shop NE + Broken Bridge SW: Dark Dunes NW + Palace of Darkness SW: Dark Tree Line NW + Palace of Darkness SE: Palace of Darkness Nook NE + Dark Dunes SC: Hammer Bridge NC + Dig Game EC: Frog WC + Dig Game ES: Frog WS + Frog ES: Stumpy WS + Stumpy SW: Stumpy Approach NW + Stumpy SC: Stumpy Approach NC + Dark Bonk Rocks EN: Big Bomb Shop WN + Dark Bonk Rocks EC: Big Bomb Shop WC + Dark Bonk Rocks ES: Big Bomb Shop WS + Dark Bonk Rocks SW: Dark C Whirlpool NW + Big Bomb Shop SC: Hype Cave NC + Big Bomb Shop ES: Hammer Bridge WS + Hammer Bridge EN: Dark Tree Line WN + Hammer Bridge EC: Dark Tree Line WC + Hammer Bridge SC: Ice Lake NW + Dark Tree Line SC: Ice Lake NC + Dark Tree Line SE: Ice Lake NE + Stumpy Approach EC: Dark C Whirlpool WC + Dark C Whirlpool EN: Hype Cave WN + Dark C Whirlpool EC: Hype Cave WC + Dark C Whirlpool ES: Hype Cave WS + Dark C Whirlpool SC: Swamp NC + Hype Cave SC: Dark South Pass NC + Ice Lake WS: Dark South Pass ES + Ice Lake EC: Bomber Corner WC + Ice Lake ES: Bomber Corner WS + Shopping Mall SW: Bomber Corner NW + Shopping Mall SE: Bomber Corner NE + Swamp Nook EC: Swamp WC + Swamp Nook ES: Swamp WS + Swamp EC: Dark South Pass WC + one-way: [] +ow-whirlpools: + 1: + two-way: + Qirn Jump Whirlpool: Bomber Corner Whirlpool + one-way: [] diff --git a/docs/ideas/shufflesmallscreens.yaml b/docs/ideas/shufflesmallscreens.yaml new file mode 100644 index 00000000..0dbea02e --- /dev/null +++ b/docs/ideas/shufflesmallscreens.yaml @@ -0,0 +1,58 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Zora Waterfall NE: Zoras Domain SW + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool diff --git a/docs/ideas/shufflevertical.yaml b/docs/ideas/shufflevertical.yaml new file mode 100644 index 00000000..60c1fe9a --- /dev/null +++ b/docs/ideas/shufflevertical.yaml @@ -0,0 +1,81 @@ +settings: + 1: + ow_mixed: false +ow-edges: + 1: + two-way: + Lost Woods EN: Lumberjack WN + Skull Woods EN: Dark Lumberjack WN + West Death Mountain EN: East Death Mountain WN + West Death Mountain ES: East Death Mountain WS + West Dark Death Mountain EN: East Dark Death Mountain WN + West Dark Death Mountain ES: East Dark Death Mountain WS + East Death Mountain EN: Death Mountain TR Pegs WN + East Dark Death Mountain EN: Turtle Rock WN + Kakariko Fortune EN: Kakariko Pond WN + Kakariko Fortune ES: Kakariko Pond WS + Dark Fortune EN: Outcast Pond WN + Dark Fortune ES: Outcast Pond WS + Kakariko Pond EN: Sanctuary WN + Kakariko Pond ES: Sanctuary WS + Outcast Pond EN: Dark Chapel WN + Outcast Pond ES: Dark Chapel WS + Sanctuary EC: Graveyard WC + Dark Chapel EC: Dark Graveyard WC + Graveyard EC: River Bend WC + Dark Graveyard EC: Qirn Jump WC + River Bend EN: Potion Shop WN + River Bend EC: Potion Shop WC + River Bend ES: Potion Shop WS + Qirn Jump EN: Dark Witch WN + Qirn Jump EC: Dark Witch WC + Qirn Jump ES: Dark Witch WS + Potion Shop EN: Zora Approach WN + Potion Shop EC: Zora Approach WC + Dark Witch EN: Catfish Approach WN + Dark Witch EC: Catfish Approach WC + Kakariko ES: Blacksmith WS + Village of Outcasts ES: Hammer Pegs WS + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES: Sand Dunes WN + Pyramid ES: Dark Dunes WN + Maze Race ES: Kakariko Suburb WS + Dig Game EC: Frog WC + Dig Game ES: Frog WS + Kakariko Suburb ES: Flute Boy WS + Frog ES: Stumpy WS + Central Bonk Rocks EN: Links House WN + Central Bonk Rocks EC: Links House WC + Central Bonk Rocks ES: Links House WS + Dark Bonk Rocks EN: Big Bomb Shop WN + Dark Bonk Rocks EC: Big Bomb Shop WC + Dark Bonk Rocks ES: Big Bomb Shop WS + Links House ES: Stone Bridge WS + Big Bomb Shop ES: Hammer Bridge WS + Stone Bridge WC: Hobo EC + Stone Bridge EN: Tree Line WN + Stone Bridge EC: Tree Line WC + Hammer Bridge EN: Dark Tree Line WN + Hammer Bridge EC: Dark Tree Line WC + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC: C Whirlpool WC + Stumpy Approach EC: Dark C Whirlpool WC + C Whirlpool EN: Statues WN + C Whirlpool EC: Statues WC + C Whirlpool ES: Statues WS + Dark C Whirlpool EN: Hype Cave WN + Dark C Whirlpool EC: Hype Cave WC + Dark C Whirlpool ES: Hype Cave WS + Lake Hylia WS: South Pass ES + Lake Hylia EC: Octoballoon WC + Lake Hylia ES: Octoballoon WS + Ice Lake WS: Dark South Pass ES + Ice Lake EC: Bomber Corner WC + Ice Lake ES: Bomber Corner WS + Desert Pass EC: Dam WC + Desert Pass ES: Dam WS + Swamp Nook EC: Swamp WC + Swamp Nook ES: Swamp WS + Dam EC: South Pass WC + Swamp EC: Dark South Pass WC From 7f0e2d8b65352a6600f062f84e6d9cbe820d3276 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 4 Sep 2023 23:05:22 -0500 Subject: [PATCH 010/123] Removing yamls not meant to be commited --- docs/ideas/shufflehorizontal.yaml | 72 ---------------------- docs/ideas/shufflelargescreens.yaml | 96 ----------------------------- docs/ideas/shufflelight.yaml | 90 --------------------------- docs/ideas/shufflesmallscreens.yaml | 58 ----------------- docs/ideas/shufflevertical.yaml | 81 ------------------------ docs/presets/Swapkeys.yaml | 22 ------- docs/presets/swapkeys.yml | 17 ----- docs/vanilla_multi_lobbies.yaml | 28 --------- 8 files changed, 464 deletions(-) delete mode 100644 docs/ideas/shufflehorizontal.yaml delete mode 100644 docs/ideas/shufflelargescreens.yaml delete mode 100644 docs/ideas/shufflelight.yaml delete mode 100644 docs/ideas/shufflesmallscreens.yaml delete mode 100644 docs/ideas/shufflevertical.yaml delete mode 100644 docs/presets/Swapkeys.yaml delete mode 100644 docs/presets/swapkeys.yml delete mode 100644 docs/vanilla_multi_lobbies.yaml diff --git a/docs/ideas/shufflehorizontal.yaml b/docs/ideas/shufflehorizontal.yaml deleted file mode 100644 index 63246b32..00000000 --- a/docs/ideas/shufflehorizontal.yaml +++ /dev/null @@ -1,72 +0,0 @@ -settings: - 1: - ow_mixed: false -ow-edges: - 1: - two-way: - Lost Woods NW: Master Sword Meadow SC - Lost Woods SW: Lost Woods Pass NW - Lost Woods SC: Lost Woods Pass NE - Lost Woods SE: Kakariko Fortune NE - Skull Woods SW: Skull Woods Pass NW - Skull Woods SC: Skull Woods Pass NE - Skull Woods SE: Dark Fortune NE - Lumberjack SW: Mountain Pass NW - Dark Lumberjack SW: Bumper Cave NW - Mountain Pass SE: Kakariko Pond NE - Bumper Cave SE: Outcast Pond NE - Zora Waterfall NE: Zoras Domain SW - Zora Waterfall SE: Zora Approach NE - Catfish SE: Catfish Approach NE - Lost Woods Pass SW: Kakariko NW - Lost Woods Pass SE: Kakariko NC - Skull Woods Pass SW: Village of Outcasts NW - Skull Woods Pass SE: Village of Outcasts NC - Kakariko Fortune SC: Kakariko NE - Dark Fortune SC: Village of Outcasts NE - Kakariko Pond SW: Forgotten Forest NW - Kakariko Pond SE: Forgotten Forest NE - Outcast Pond SW: Shield Shop NW - Outcast Pond SE: Shield Shop NE - River Bend SW: Wooden Bridge NW - River Bend SC: Wooden Bridge NC - River Bend SE: Wooden Bridge NE - Qirn Jump SW: Broken Bridge NW - Qirn Jump SC: Broken Bridge NC - Qirn Jump SE: Broken Bridge NE - Kakariko SE: Kakariko Suburb NE - Village of Outcasts SE: Frog NE - Hyrule Castle SW: Central Bonk Rocks NW - Hyrule Castle SE: Links House NE - Pyramid SW: Dark Bonk Rocks NW - Pyramid SE: Big Bomb Shop NE - Wooden Bridge SW: Sand Dunes NW - Broken Bridge SW: Dark Dunes NW - Eastern Palace SW: Tree Line NW - Eastern Palace SE: Eastern Nook NE - Palace of Darkness SW: Dark Tree Line NW - Palace of Darkness SE: Palace of Darkness Nook NE - Sand Dunes SC: Stone Bridge NC - Dark Dunes SC: Hammer Bridge NC - Flute Boy SW: Flute Boy Approach NW - Flute Boy SC: Flute Boy Approach NC - Stumpy SW: Stumpy Approach NW - Stumpy SC: Stumpy Approach NC - Central Bonk Rocks SW: C Whirlpool NW - Dark Bonk Rocks SW: Dark C Whirlpool NW - Links House SC: Statues NC - Big Bomb Shop SC: Hype Cave NC - Stone Bridge SC: Lake Hylia NW - Hammer Bridge SC: Ice Lake NW - Tree Line SC: Lake Hylia NC - Tree Line SE: Lake Hylia NE - Dark Tree Line SC: Ice Lake NC - Dark Tree Line SE: Ice Lake NE - C Whirlpool SC: Dam NC - Dark C Whirlpool SC: Swamp NC - Statues SC: South Pass NC - Hype Cave SC: Dark South Pass NC - Ice Cave SW: Octoballoon NW - Ice Cave SE: Octoballoon NE - Shopping Mall SW: Bomber Corner NW - Shopping Mall SE: Bomber Corner NE diff --git a/docs/ideas/shufflelargescreens.yaml b/docs/ideas/shufflelargescreens.yaml deleted file mode 100644 index 93f7472d..00000000 --- a/docs/ideas/shufflelargescreens.yaml +++ /dev/null @@ -1,96 +0,0 @@ -settings: - 1: - ow_whirlpool: false -ow-edges: - 1: - two-way: - Lumberjack SW*: Mountain Pass NW* - Dark Lumberjack SW*: Bumper Cave NW* - Mountain Pass SE*: Kakariko Pond NE* - Bumper Cave SE*: Outcast Pond NE* - Zora Waterfall SE*: Zora Approach NE* - Catfish SE*: Catfish Approach NE* - Kakariko Fortune EN*: Kakariko Pond WN* - Kakariko Fortune ES*: Kakariko Pond WS* - Dark Fortune EN*: Outcast Pond WN* - Dark Fortune ES*: Outcast Pond WS* - Kakariko Pond EN*: Sanctuary WN* - Kakariko Pond ES*: Sanctuary WS* - Kakariko Pond SW*: Forgotten Forest NW* - Kakariko Pond SE*: Forgotten Forest NE* - Outcast Pond EN*: Dark Chapel WN* - Outcast Pond ES*: Dark Chapel WS* - Outcast Pond SW*: Shield Shop NW* - Outcast Pond SE*: Shield Shop NE* - Sanctuary EC*: Graveyard WC* - Dark Chapel EC*: Dark Graveyard WC* - Graveyard EC*: River Bend WC* - Dark Graveyard EC*: Qirn Jump WC* - River Bend EN*: Potion Shop WN* - River Bend EC*: Potion Shop WC* - River Bend ES*: Potion Shop WS* - River Bend SW*: Wooden Bridge NW* - River Bend SC*: Wooden Bridge NC* - River Bend SE*: Wooden Bridge NE* - Qirn Jump EN*: Dark Witch WN* - Qirn Jump EC*: Dark Witch WC* - Qirn Jump ES*: Dark Witch WS* - Qirn Jump SW*: Broken Bridge NW* - Qirn Jump SC*: Broken Bridge NC* - Qirn Jump SE*: Broken Bridge NE* - Potion Shop EN*: Zora Approach WN* - Potion Shop EC*: Zora Approach WC* - Dark Witch EN*: Catfish Approach WN* - Dark Witch EC*: Catfish Approach WC* - Wooden Bridge SW*: Sand Dunes NW* - Broken Bridge SW*: Dark Dunes NW* - Sand Dunes SC*: Stone Bridge NC* - Dark Dunes SC*: Hammer Bridge NC* - Maze Race ES: Kakariko Suburb WS - Dig Game EC: Frog WC - Dig Game ES: Frog WS - Kakariko Suburb ES*: Flute Boy WS* - Frog ES*: Stumpy WS* - Flute Boy SW*: Flute Boy Approach NW* - Flute Boy SC*: Flute Boy Approach NC* - Stumpy SW*: Stumpy Approach NW* - Stumpy SC*: Stumpy Approach NC* - Central Bonk Rocks EN*: Links House WN* - Central Bonk Rocks EC*: Links House WC* - Central Bonk Rocks ES*: Links House WS* - Central Bonk Rocks SW*: C Whirlpool NW* - Dark Bonk Rocks EN*: Big Bomb Shop WN* - Dark Bonk Rocks EC*: Big Bomb Shop WC* - Dark Bonk Rocks ES*: Big Bomb Shop WS* - Dark Bonk Rocks SW*: Dark C Whirlpool NW* - Links House SC*: Statues NC* - Links House ES*: Stone Bridge WS8 - Big Bomb Shop SC*: Hype Cave NC* - Big Bomb Shop ES*: Hammer Bridge WS* - Stone Bridge WC: Hobo EC - Stone Bridge EN*: Tree Line WN* - Stone Bridge EC*: Tree Line WC* - Hammer Bridge EN*: Dark Tree Line WN* - Hammer Bridge EC*: Dark Tree Line WC* - Flute Boy Approach EC*: C Whirlpool WC* - Stumpy Approach EC*: Dark C Whirlpool WC* - C Whirlpool EN*: Statues WN* - C Whirlpool EC*: Statues WC* - C Whirlpool ES*: Statues WS* - C Whirlpool SC*: Dam NC* - Dark C Whirlpool EN*: Hype Cave WN* - Dark C Whirlpool EC*: Hype Cave WC* - Dark C Whirlpool ES*: Hype Cave WS* - Dark C Whirlpool SC*: Swamp NC* - Statues SC*: South Pass NC* - Hype Cave SC*: Dark South Pass NC* - Ice Cave SW*: Octoballoon NW* - Ice Cave SE*: Octoballoon NE* - Shopping Mall SW*: Bomber Corner NW* - Shopping Mall SE*: Bomber Corner NE* - Desert Pass EC*: Dam WC* - Desert Pass ES*: Dam WS* - Swamp Nook EC*: Swamp WC* - Swamp Nook ES*: Swamp WS* - Dam EC*: South Pass WC* - Swamp EC*: Dark South Pass WC* \ No newline at end of file diff --git a/docs/ideas/shufflelight.yaml b/docs/ideas/shufflelight.yaml deleted file mode 100644 index 0a2f9f58..00000000 --- a/docs/ideas/shufflelight.yaml +++ /dev/null @@ -1,90 +0,0 @@ -meta: - players: 1 -settings: - 1: - ow_shuffle: full - ow_whirlpool: true - ow_crossed: none - ow_keepsimilar: false - ow_swapped: false - ow_terrain: true - ow_decoupled: none - ow_mixed: false - ow_fluteshuffle: vanilla -ow-edges: - 1: - two-way: - Skull Woods EN: Dark Lumberjack WN - Skull Woods SW: Skull Woods Pass NW - Skull Woods SC: Skull Woods Pass NE - Skull Woods SE: Dark Fortune NE - Dark Lumberjack SW: Bumper Cave NW - West Dark Death Mountain EN: East Dark Death Mountain WN - West Dark Death Mountain ES: East Dark Death Mountain WS - East Dark Death Mountain EN: Turtle Rock WN - Bumper Cave SE: Outcast Pond NE - Catfish SE: Catfish Approach NE - Skull Woods Pass SW: Village of Outcasts NW - Skull Woods Pass SE: Village of Outcasts NC - Dark Fortune EN: Outcast Pond WN - Dark Fortune ES: Outcast Pond WS - Dark Fortune SC: Village of Outcasts NE - Outcast Pond EN: Dark Chapel WN - Outcast Pond ES: Dark Chapel WS - Outcast Pond SW: Shield Shop NW - Outcast Pond SE: Shield Shop NE - Dark Chapel EC: Dark Graveyard WC - Dark Graveyard EC: Qirn Jump WC - Qirn Jump EN: Dark Witch WN - Qirn Jump EC: Dark Witch WC - Qirn Jump ES: Dark Witch WS - Qirn Jump SW: Broken Bridge NW - Qirn Jump SC: Broken Bridge NC - Qirn Jump SE: Broken Bridge NE - Dark Witch EN: Catfish Approach WN - Dark Witch EC: Catfish Approach WC - Village of Outcasts ES: Hammer Pegs WS - Village of Outcasts SE: Frog NE - Pyramid ES: Dark Dunes WN - Pyramid SW: Dark Bonk Rocks NW - Pyramid SE: Big Bomb Shop NE - Broken Bridge SW: Dark Dunes NW - Palace of Darkness SW: Dark Tree Line NW - Palace of Darkness SE: Palace of Darkness Nook NE - Dark Dunes SC: Hammer Bridge NC - Dig Game EC: Frog WC - Dig Game ES: Frog WS - Frog ES: Stumpy WS - Stumpy SW: Stumpy Approach NW - Stumpy SC: Stumpy Approach NC - Dark Bonk Rocks EN: Big Bomb Shop WN - Dark Bonk Rocks EC: Big Bomb Shop WC - Dark Bonk Rocks ES: Big Bomb Shop WS - Dark Bonk Rocks SW: Dark C Whirlpool NW - Big Bomb Shop SC: Hype Cave NC - Big Bomb Shop ES: Hammer Bridge WS - Hammer Bridge EN: Dark Tree Line WN - Hammer Bridge EC: Dark Tree Line WC - Hammer Bridge SC: Ice Lake NW - Dark Tree Line SC: Ice Lake NC - Dark Tree Line SE: Ice Lake NE - Stumpy Approach EC: Dark C Whirlpool WC - Dark C Whirlpool EN: Hype Cave WN - Dark C Whirlpool EC: Hype Cave WC - Dark C Whirlpool ES: Hype Cave WS - Dark C Whirlpool SC: Swamp NC - Hype Cave SC: Dark South Pass NC - Ice Lake WS: Dark South Pass ES - Ice Lake EC: Bomber Corner WC - Ice Lake ES: Bomber Corner WS - Shopping Mall SW: Bomber Corner NW - Shopping Mall SE: Bomber Corner NE - Swamp Nook EC: Swamp WC - Swamp Nook ES: Swamp WS - Swamp EC: Dark South Pass WC - one-way: [] -ow-whirlpools: - 1: - two-way: - Qirn Jump Whirlpool: Bomber Corner Whirlpool - one-way: [] diff --git a/docs/ideas/shufflesmallscreens.yaml b/docs/ideas/shufflesmallscreens.yaml deleted file mode 100644 index 0dbea02e..00000000 --- a/docs/ideas/shufflesmallscreens.yaml +++ /dev/null @@ -1,58 +0,0 @@ -ow-edges: - 1: - two-way: - Lost Woods NW: Master Sword Meadow SC - Lost Woods EN*: Lumberjack WN* - Lost Woods SW*: Lost Woods Pass NW* - Lost Woods SC*: Lost Woods Pass NE* - Lost Woods SE*: Kakariko Fortune NE* - Skull Woods EN*: Dark Lumberjack WN* - Skull Woods SW*: Skull Woods Pass NW* - Skull Woods SC*: Skull Woods Pass NE* - Skull Woods SE*: Dark Fortune NE* - West Death Mountain EN*: East Death Mountain WN* - West Death Mountain ES*: East Death Mountain WS* - West Dark Death Mountain EN*: East Dark Death Mountain WN* - West Dark Death Mountain ES*: East Dark Death Mountain WS* - East Death Mountain EN*: Death Mountain TR Pegs WN* - East Dark Death Mountain EN*: Turtle Rock WN* - Zora Waterfall NE: Zoras Domain SW - Lost Woods Pass SW*: Kakariko NW* - Lost Woods Pass SE*: Kakariko NC* - Skull Woods Pass SW*: Village of Outcasts NW* - Skull Woods Pass SE*: Village of Outcasts NC* - Kakariko Fortune SC*: Kakariko NE* - Dark Fortune SC*: Village of Outcasts NE* - Kakariko ES*: Blacksmith WS* - Kakariko SE*: Kakariko Suburb NE* - Village of Outcasts ES*: Hammer Pegs WS* - Village of Outcasts SE*: Frog NE* - Forgotten Forest ES: Hyrule Castle WN - Hyrule Castle ES*: Sand Dunes WN* - Hyrule Castle SW*: Central Bonk Rocks NW* - Hyrule Castle SE*: Links House NE* - Pyramid ES*: Dark Dunes WN* - Pyramid SW*: Dark Bonk Rocks NW* - Pyramid SE*: Big Bomb Shop NE* - Eastern Palace SW*: Tree Line NW* - Eastern Palace SE*: Eastern Nook NE* - Palace of Darkness SW*: Dark Tree Line NW* - Palace of Darkness SE*: Palace of Darkness Nook NE* - Stone Bridge SC*: Lake Hylia NW* - Hammer Bridge SC*: Ice Lake NW* - Tree Line SC*: Lake Hylia NC* - Tree Line SE*: Lake Hylia NE* - Dark Tree Line SC*: Ice Lake NC* - Dark Tree Line SE*: Ice Lake NE* - Desert EC: Desert Pass WC - Desert ES: Desert Pass WS - Lake Hylia WS*: South Pass ES* - Lake Hylia EC*: Octoballoon WC* - Lake Hylia ES*: Octoballoon WS* - Ice Lake WS*: Dark South Pass ES* - Ice Lake EC*: Bomber Corner WC* - Ice Lake ES*: Bomber Corner WS* -ow-whirlpools: - 1: - two-way: - Zora Whirlpool: Lake Hylia Whirlpool diff --git a/docs/ideas/shufflevertical.yaml b/docs/ideas/shufflevertical.yaml deleted file mode 100644 index 60c1fe9a..00000000 --- a/docs/ideas/shufflevertical.yaml +++ /dev/null @@ -1,81 +0,0 @@ -settings: - 1: - ow_mixed: false -ow-edges: - 1: - two-way: - Lost Woods EN: Lumberjack WN - Skull Woods EN: Dark Lumberjack WN - West Death Mountain EN: East Death Mountain WN - West Death Mountain ES: East Death Mountain WS - West Dark Death Mountain EN: East Dark Death Mountain WN - West Dark Death Mountain ES: East Dark Death Mountain WS - East Death Mountain EN: Death Mountain TR Pegs WN - East Dark Death Mountain EN: Turtle Rock WN - Kakariko Fortune EN: Kakariko Pond WN - Kakariko Fortune ES: Kakariko Pond WS - Dark Fortune EN: Outcast Pond WN - Dark Fortune ES: Outcast Pond WS - Kakariko Pond EN: Sanctuary WN - Kakariko Pond ES: Sanctuary WS - Outcast Pond EN: Dark Chapel WN - Outcast Pond ES: Dark Chapel WS - Sanctuary EC: Graveyard WC - Dark Chapel EC: Dark Graveyard WC - Graveyard EC: River Bend WC - Dark Graveyard EC: Qirn Jump WC - River Bend EN: Potion Shop WN - River Bend EC: Potion Shop WC - River Bend ES: Potion Shop WS - Qirn Jump EN: Dark Witch WN - Qirn Jump EC: Dark Witch WC - Qirn Jump ES: Dark Witch WS - Potion Shop EN: Zora Approach WN - Potion Shop EC: Zora Approach WC - Dark Witch EN: Catfish Approach WN - Dark Witch EC: Catfish Approach WC - Kakariko ES: Blacksmith WS - Village of Outcasts ES: Hammer Pegs WS - Forgotten Forest ES: Hyrule Castle WN - Hyrule Castle ES: Sand Dunes WN - Pyramid ES: Dark Dunes WN - Maze Race ES: Kakariko Suburb WS - Dig Game EC: Frog WC - Dig Game ES: Frog WS - Kakariko Suburb ES: Flute Boy WS - Frog ES: Stumpy WS - Central Bonk Rocks EN: Links House WN - Central Bonk Rocks EC: Links House WC - Central Bonk Rocks ES: Links House WS - Dark Bonk Rocks EN: Big Bomb Shop WN - Dark Bonk Rocks EC: Big Bomb Shop WC - Dark Bonk Rocks ES: Big Bomb Shop WS - Links House ES: Stone Bridge WS - Big Bomb Shop ES: Hammer Bridge WS - Stone Bridge WC: Hobo EC - Stone Bridge EN: Tree Line WN - Stone Bridge EC: Tree Line WC - Hammer Bridge EN: Dark Tree Line WN - Hammer Bridge EC: Dark Tree Line WC - Desert EC: Desert Pass WC - Desert ES: Desert Pass WS - Flute Boy Approach EC: C Whirlpool WC - Stumpy Approach EC: Dark C Whirlpool WC - C Whirlpool EN: Statues WN - C Whirlpool EC: Statues WC - C Whirlpool ES: Statues WS - Dark C Whirlpool EN: Hype Cave WN - Dark C Whirlpool EC: Hype Cave WC - Dark C Whirlpool ES: Hype Cave WS - Lake Hylia WS: South Pass ES - Lake Hylia EC: Octoballoon WC - Lake Hylia ES: Octoballoon WS - Ice Lake WS: Dark South Pass ES - Ice Lake EC: Bomber Corner WC - Ice Lake ES: Bomber Corner WS - Desert Pass EC: Dam WC - Desert Pass ES: Dam WS - Swamp Nook EC: Swamp WC - Swamp Nook ES: Swamp WS - Dam EC: South Pass WC - Swamp EC: Dark South Pass WC diff --git a/docs/presets/Swapkeys.yaml b/docs/presets/Swapkeys.yaml deleted file mode 100644 index 20b7e88b..00000000 --- a/docs/presets/Swapkeys.yaml +++ /dev/null @@ -1,22 +0,0 @@ -meta: - branch: OWR - seed_name: Swapkeys - seed_notes: Crosskeys but Swapped ER -settings: - 1: - mode: open - logic: noglitches - goal: crystals - crystals_gt: "7" - crystals_ganon: "7" - accessibility: locations - mapshuffle: 1 - compassshuffle: 1 - keyshuffle: wild - bigkeyshuffle: 1 - shuffle: swapped - shuffleganon: 1 - shufflelinks: 0 - shuffletavern: 1 - experimental: 0 - hints: 0 diff --git a/docs/presets/swapkeys.yml b/docs/presets/swapkeys.yml deleted file mode 100644 index e71e4521..00000000 --- a/docs/presets/swapkeys.yml +++ /dev/null @@ -1,17 +0,0 @@ -settings: - 1: - description: Swapkeys - glitches_required: none - mode: open - goal: crystals - crystals_gt: "7" - crystals_ganon: "7" - weapons: randomized - accessibility: locations - entrance_shuffle: swapped - shufflelinks: off - shuffletavern: on - mapshuffle: on - compassshuffle: on - keyshuffle: wild - bigkeyshuffle: on diff --git a/docs/vanilla_multi_lobbies.yaml b/docs/vanilla_multi_lobbies.yaml deleted file mode 100644 index 06eaf835..00000000 --- a/docs/vanilla_multi_lobbies.yaml +++ /dev/null @@ -1,28 +0,0 @@ -doors: - 1: - lobbies: - #Agahnims Tower: Tower Lobby S - Desert Back: Desert Back Lobby S - Desert East: Desert East Lobby S - Desert South: Desert Main Lobby S - Desert West: Desert West S - #Eastern: Eastern Lobby S - #Ganons Tower: GT Lobby S - #Hera: Hera Lobby S - Hyrule Castle East: Hyrule Castle East Lobby S - Hyrule Castle South: Hyrule Castle Lobby S - Hyrule Castle West: Hyrule Castle West Lobby S - #Ice: Ice Lobby SE - #Mire: Mire Lobby S - #Palace of Darkness: PoD Lobby S - #Sanctuary: Sanctuary S - #Skull 1: Skull 1 Lobby S - #Skull 2 East: Skull 2 East Lobby SW - #Skull 2 West: Skull 2 West Lobby S - #Skull 3: Skull 3 Lobby SW - #Swamp: Swamp Lobby S - #Thieves Town: Thieves Lobby S - Turtle Rock Chest: TR Big Chest Entrance SE - Turtle Rock Eye Bridge: TR Eye Bridge SW - Turtle Rock Lazy Eyes: TR Lazy Eyes SE - Turtle Rock Main: TR Main Lobby SE From c1f98dfb517c25824d224cd69d11df7012cc8139 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 5 Sep 2023 00:14:05 -0500 Subject: [PATCH 011/123] Customizer: Force Tile Flip for connected whirlpools --- OverworldShuffle.py | 69 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 59a610ee..788c9d2e 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -317,7 +317,7 @@ def link_overworld(world, player): custom_whirlpools = world.customizer.get_whirlpools() if custom_whirlpools and player in custom_whirlpools: custom_whirlpools = custom_whirlpools[player] - if 'two-way' in custom_whirlpools: + if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0: for whirlpools in whirlpool_candidates: for whirlname1, whirlname2 in custom_whirlpools['two-way'].items(): whirl1 = next((w for w in whirlpools if w[1] == whirlname1), None) @@ -784,13 +784,12 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): # customizer adjustments undefined_chance = 50 flipped_groups = list() - always_removed = list() + nonflipped_groups = list() if world.customizer: if not do_grouped: custom_flips = world.customizer.get_owtileflips() if custom_flips and player in custom_flips: custom_flips = custom_flips[player] - nonflipped_groups = list() forced_flips = list() forced_nonflips = list() if 'undefined_chance' in custom_flips: @@ -805,30 +804,72 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): nonflipped_groups.append(group) if any(owid in group[0] for owid in forced_flips): flipped_groups.append(group) - - # Check if there are any groups that appear in both sets - if any(group in flipped_groups for group in nonflipped_groups): - raise GenerationException('Conflict found when flipping tiles') - for g in nonflipped_groups: - always_removed.append(g) if undefined_chance == 0: - for g in [g for g in groups if g not in flipped_groups + always_removed]: - always_removed.append(g) + nonflipped_groups.extend([g for g in groups if g not in flipped_groups + nonflipped_groups]) + if world.owCrossed[player] != 'none': + # ensure any customized whirlpool connections don't end up crossworld + custom_whirlpools = world.customizer.get_whirlpools() + if custom_whirlpools and player in custom_whirlpools: + custom_whirlpools = custom_whirlpools[player] + if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0: + custom_whirlpools = custom_whirlpools['two-way'] + whirlpool_map = {name:owid for wc in default_whirlpool_connections for (owid, name, _) in wc} + for whirl1, whirl2 in custom_whirlpools.items(): + w1_owid = whirlpool_map[whirl1] + w2_owid = whirlpool_map[whirl2] + flip_together = (w1_owid & 0x40) == (w2_owid & 0x40) + w1_nonflipped = any(g for g in nonflipped_groups if w1_owid in g[0]) + w1_flipped = any(g for g in flipped_groups if w1_owid in g[0]) + if w1_nonflipped or w1_flipped: + group = next(g for g in groups if w2_owid in g[0]) + if w1_nonflipped == flip_together: + nonflipped_groups.append(group) + else: + flipped_groups.append(group) + else: + w2_nonflipped = any(g for g in nonflipped_groups if w2_owid in g[0]) + w2_flipped = any(g for g in flipped_groups if w2_owid in g[0]) + if w2_nonflipped or w2_flipped: + group = next(g for g in groups if w1_owid in g[0]) + if w2_nonflipped == flip_together: + nonflipped_groups.append(group) + else: + flipped_groups.append(group) + else: + w1_group = next(g for g in groups if w1_owid in g[0]) + w2_group = next(g for g in groups if w2_owid in g[0]) + if not flip_together: + if random.randint(0, 1) > 0: + nonflipped_groups.append(w1_group) + flipped_groups.append(w2_group) + else: + flipped_groups.append(w1_group) + nonflipped_groups.append(w2_group) + else: + if random.randint(1, 100) > undefined_chance: + nonflipped_groups.append(w1_group) + nonflipped_groups.append(w2_group) + else: + flipped_groups.append(w1_group) + flipped_groups.append(w2_group) + # Check if there are any groups that appear in both sets + if any(group in flipped_groups for group in nonflipped_groups): + raise GenerationException('Conflict found when flipping tiles') attempts = 1 if 0 < undefined_chance < 100: # do roughly 1000 attempts at a full list - attempts = len(groups) - len(always_removed) + attempts = len(groups) - len(nonflipped_groups) attempts = (attempts ** 1.9) + (attempts * 10) + 1 while True: if attempts == 0: # expected to only occur with custom flips raise GenerationException('Could not find valid tile flips') # tile shuffle happens here - removed = copy.deepcopy(always_removed) + removed = copy.deepcopy(nonflipped_groups) if 0 < undefined_chance < 100: - for group in [g for g in groups if g not in always_removed]: + for group in [g for g in groups if g not in nonflipped_groups]: if group not in flipped_groups and random.randint(1, 100) > undefined_chance: removed.append(group) From 927588a3d82ef2963a24aec3b1f89e107797b8b3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 5 Sep 2023 00:30:53 -0500 Subject: [PATCH 012/123] Adding Customizer support for Crossed OWR Also made Layout OWR compatible with Mixed OWR with asterisk notation --- BaseClasses.py | 20 +- OverworldShuffle.py | 328 ++++++++++++++++++++++++------- docs/customizer_example.yaml | 18 ++ source/classes/CustomSettings.py | 5 + 4 files changed, 296 insertions(+), 75 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 3615d0e9..6456c1c5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -87,6 +87,7 @@ class World(object): self.owedges = [] self._owedge_cache = {} self.owswaps = {} + self.owcrossededges = {} self.owwhirlpools = {} self.owflutespots = {} self.owsectors = {} @@ -114,6 +115,7 @@ class World(object): set_player_attr('_region_cache', {}) set_player_attr('player_names', []) set_player_attr('owswaps', [[],[],[]]) + set_player_attr('owcrossededges', []) set_player_attr('owwhirlpools', []) set_player_attr('owsectors', None) set_player_attr('remote_items', False) @@ -321,6 +323,16 @@ class World(object): if isinstance(edgename, OWEdge): return edgename try: + if edgename[-1] == '*': + edgename = edgename[:-1] + edge = self.check_for_owedge(edgename, player) + if self.is_tile_swapped(edge.owIndex, player): + from OverworldShuffle import parallel_links + if edgename in parallel_links.keys() or edgename in parallel_links.inverse.keys(): + edgename = parallel_links[edgename] if edgename in parallel_links.keys() else parallel_links.inverse[edgename][0] + return self.check_for_owedge(edgename, player) + else: + raise Exception("Edge notated with * doesn't have a parallel edge: %s" & edgename) return self._owedge_cache[(edgename, player)] except KeyError: for edge in self.owedges: @@ -2248,7 +2260,7 @@ class OWEdge(object): self.unknownX = 0x0 self.unknownY = 0x0 - if self.owIndex < 0x40 or self.owIndex >= 0x80: + if self.owIndex & 0x40 == 0: self.worldType = WorldType.Light else: self.worldType = WorldType.Dark @@ -2289,6 +2301,12 @@ class OWEdge(object): self.specialID = special_id return self + def is_tile_swapped(self, world): + return world.is_tile_swapped(self.owIndex, self.player) + + def is_lw(self, world): + return (self.worldType == WorldType.Light) != self.is_tile_swapped(world) + def __eq__(self, other): return isinstance(other, self.__class__) and self.name == other.name diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 788c9d2e..d18356b5 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -205,20 +205,40 @@ def link_overworld(world, player): # crossed shuffle logging.getLogger('').debug('Crossing overworld edges') - crossed_edges = list() - #TODO: Revisit with changes to Limited/Allowed - if world.owCrossed[player] not in ['none', 'grouped', 'polar', 'chaos']: + # customizer setup + force_crossed = set() + force_noncrossed = set() + count_crossed = 0 + limited_crossed = 9 if world.owCrossed[player] == 'limited' else -1 + if world.customizer: + custom_crossed = world.customizer.get_owcrossed() + if custom_crossed and player in custom_crossed: + custom_crossed = custom_crossed[player] + if 'force_crossed' in custom_crossed and len(custom_crossed['force_crossed']) > 0: + for edgename in custom_crossed['force_crossed']: + edge = world.check_for_owedge(edgename, player) + force_crossed.add(edge.name) + if 'force_noncrossed' in custom_crossed and len(custom_crossed['force_noncrossed']) > 0: + for edgename in custom_crossed['force_noncrossed']: + edge = world.check_for_owedge(edgename, player) + force_noncrossed.add(edge.name) + if 'limit_crossed' in custom_crossed: + limited_crossed = custom_crossed['limit_crossed'] + + if limited_crossed > -1: + # connect forced crossed non-parallel edges based on previously determined tile flips for edge in swapped_edges: - crossed_edges.append(edge) - - if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'): if edge not in parallel_links_new: + world.owcrossededges[player].append(edge) + count_crossed = count_crossed + 1 + + if world.owCrossed[player] == 'grouped' or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos') or limited_crossed > -1: if world.owCrossed[player] == 'grouped': # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask ow_crossed_tiles_mask = [[],[],[]] - crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player) + world.owcrossededges[player] = 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 @@ -244,32 +264,77 @@ def link_overworld(world, player): (mode, wrld, dir, terrain, parallel, count) = group 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) if forward_set[0] in parallel_links_new: + forward_parallel = [parallel_links_new[e] for e in forward_set] + back_parallel = [parallel_links_new[e] for e in back_set] + forward_combine = forward_set+forward_parallel + back_combine = back_set+back_parallel + combine_set = forward_combine+back_combine + + skip_forward = False + if world.owShuffle[player] == 'vanilla': + if any(edge in force_crossed for edge in combine_set): + if not any(edge in force_noncrossed for edge in combine_set): + if any(edge in force_crossed for edge in forward_combine): + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + continue + else: + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + continue + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in combine_set): + continue 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': + skip_back = False + if any(edge in force_crossed for edge in forward_combine): + if not any(edge in force_noncrossed for edge in forward_combine): + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + skip_forward = True + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in force_crossed for edge in back_combine): + if not any(edge in force_noncrossed for edge in back_combine): + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + skip_back = True + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in forward_combine): + skip_forward = True + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in back_combine): + skip_back = True + if not skip_back: + if limited_crossed > -1: + crossed_candidates.append(back_set) + elif random.randint(0, 1): + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + if not skip_forward: + if limited_crossed > -1: + crossed_candidates.append(forward_set) + elif random.randint(0, 1): + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges" + + if limited_crossed > -1: + limit = limited_crossed - count_crossed 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): + for edge_set in crossed_candidates[:limit]: + world.owcrossededges[player].extend(edge_set) + assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges" + + for edge in copy.deepcopy(world.owcrossededges[player]): 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]) + if parallel_links_new[edge] not in world.owcrossededges[player]: + world.owcrossededges[player].append(parallel_links_new[edge]) # after tile flip and crossed, determine edges that need to flip - edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)] + edges_to_swap = [e for e in swapped_edges+world.owcrossededges[player] if (e not in swapped_edges) or (e not in world.owcrossededges[player])] # whirlpool shuffle logging.getLogger('').debug('Shuffling whirlpools') @@ -367,53 +432,142 @@ def link_overworld(world, player): # layout shuffle groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) - connect_custom(world, connected_edges, groups, player) + connect_custom(world, connected_edges, groups, (force_crossed, force_noncrossed), player) tries = 100 valid_layout = False connected_edge_cache = connected_edges.copy() + groups_cache = copy.deepcopy(groups) while not valid_layout and tries > 0: + def connect_set(forward_set, back_set, connected_edges): + if forward_set is not None and back_set is not None: + assert len(forward_set) == len(back_set) + for (forward_edge, back_edge) in zip(forward_set, back_set): + connect_two_way(world, forward_edge, back_edge, player, connected_edges) + elif forward_set is not None: + logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0]) + elif back_set is not None: + logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0]) + connected_edges = connected_edge_cache.copy() + groups = copy.deepcopy(groups_cache) + groupKeys = list(groups.keys()) if world.mode[player] == 'standard': - random.shuffle(groups[2:]) # keep first 2 groups (Standard) first + random.shuffle(groupKeys[2:]) # keep first 2 groups (Standard) first else: - random.shuffle(groups) + random.shuffle(groupKeys) - for (forward_edge_sets, back_edge_sets) in groups: - assert len(forward_edge_sets) == len(back_edge_sets) + for key in groupKeys: + (mode, wrld, dir, terrain, parallel, count) = key + (forward_edge_sets, back_edge_sets) = groups[key] + def remove_connected(): + s = 0 + while s < len(forward_edge_sets): + forward_set = forward_edge_sets[s] + if forward_set[0] in connected_edges: + del forward_edge_sets[s] + continue + s += 1 + s = 0 + while s < len(back_edge_sets): + back_set = back_edge_sets[s] + if back_set[0] in connected_edges: + del back_edge_sets[s] + continue + s += 1 + assert len(forward_edge_sets) == len(back_edge_sets) + + remove_connected() random.shuffle(forward_edge_sets) random.shuffle(back_edge_sets) - if len(forward_edge_sets) > 0: - f = 0 - b = 0 - while f < len(forward_edge_sets) and b < len(back_edge_sets): - forward_set = forward_edge_sets[f] - back_set = back_edge_sets[b] - while forward_set[0] in connected_edges: - f += 1 - if f < len(forward_edge_sets): - forward_set = forward_edge_sets[f] + if wrld is None and len(force_crossed) + len(force_noncrossed) > 0: + # divide forward/back sets into LW/DW + forward_lw_sets, forward_dw_sets = [], [] + back_lw_sets, back_dw_sets = [], [] + forward_parallel_lw_sets, forward_parallel_dw_sets = [], [] + back_parallel_lw_sets, back_parallel_dw_sets = [], [] + + for edge_set in forward_edge_sets: + if world.check_for_owedge(edge_set[0], player).is_lw(world): + forward_lw_sets.append(edge_set) + if parallel == IsParallel.Yes: + forward_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set]) + else: + forward_dw_sets.append(edge_set) + if parallel == IsParallel.Yes: + forward_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set]) + for edge_set in back_edge_sets: + if world.check_for_owedge(edge_set[0], player).is_lw(world): + back_lw_sets.append(edge_set) + if parallel == IsParallel.Yes: + back_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set]) + else: + back_dw_sets.append(edge_set) + if parallel == IsParallel.Yes: + back_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set]) + + crossed_sets = [] + noncrossed_sets = [] + def add_to_crossed_sets(sets, parallel_sets): + for i in range(0, len(sets)): + affected_edges = set(sets[i]+(parallel_sets[i] if parallel == IsParallel.Yes else [])) + if sets[i] not in crossed_sets and len(set.intersection(set(force_crossed), affected_edges)) > 0: + crossed_sets.append(sets[i]) + if sets not in noncrossed_sets and len(set.intersection(set(force_noncrossed), affected_edges)) > 0: + noncrossed_sets.append(sets[i]) + if sets[i] in crossed_sets and sets[i] in noncrossed_sets: + raise GenerationException('Conflict in force crossed/non-crossed definition') + add_to_crossed_sets(forward_lw_sets, forward_parallel_lw_sets) + add_to_crossed_sets(forward_dw_sets, forward_parallel_dw_sets) + add_to_crossed_sets(back_lw_sets, back_parallel_lw_sets) + add_to_crossed_sets(back_dw_sets, back_parallel_dw_sets) + + # random connect forced crossed/noncrossed + c = 0 + while c < len(noncrossed_sets): + if noncrossed_sets[c] in forward_edge_sets: + forward_set = noncrossed_sets[c] + if forward_set in forward_lw_sets: + back_set = next(s for s in back_lw_sets if s in back_edge_sets and s not in crossed_sets) else: - forward_set = None - break - f += 1 - while back_set[0] in connected_edges: - b += 1 - if b < len(back_edge_sets): - back_set = back_edge_sets[b] + back_set = next(s for s in back_dw_sets if s in back_edge_sets and s not in crossed_sets) + elif noncrossed_sets[c] in back_edge_sets: + back_set = noncrossed_sets[c] + if back_set in back_lw_sets: + forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets and s not in crossed_sets) else: - back_set = None - break - b += 1 - if forward_set is not None and back_set is not None: - assert len(forward_set) == len(back_set) - for (forward_edge, back_edge) in zip(forward_set, back_set): - connect_two_way(world, forward_edge, back_edge, player, connected_edges) - elif forward_set is not None: - logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0]) - elif back_set is not None: - logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0]) + forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets and s not in crossed_sets) + else: + c = c + 1 + continue + connect_set(forward_set, back_set, connected_edges) + remove_connected() + c = c + 1 + c = 0 + while c < len(crossed_sets): + if crossed_sets[c] in forward_edge_sets: + forward_set = crossed_sets[c] + if forward_set in forward_lw_sets: + back_set = next(s for s in back_dw_sets if s in back_edge_sets) + else: + back_set = next(s for s in back_lw_sets if s in back_edge_sets) + elif crossed_sets[c] in back_edge_sets: + back_set = crossed_sets[c] + if back_set in back_lw_sets: + forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets) + else: + forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets) + else: + c = c + 1 + continue + connect_set(forward_set, back_set, connected_edges) + remove_connected() + c = c + 1 + + while len(forward_edge_sets) > 0 and len(back_edge_sets) > 0: + connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges) + remove_connected() assert len(connected_edges) == len(default_connections) * 2, connected_edges world.owsectors[player] = build_sectors(world, player) @@ -583,8 +737,9 @@ def link_overworld(world, player): s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('flute', text_output, new_spots, player) -def connect_custom(world, connected_edges, groups, player): - def remove_pair_from_pool(edgename1, edgename2): +def connect_custom(world, connected_edges, groups, forced, player): + forced_crossed, forced_noncrossed = forced + def remove_pair_from_pool(edgename1, edgename2, is_crossed): def add_to_unresolved(forward_set, back_set): if len(forward_set) > 1: if edgename1 in forward_set: @@ -593,8 +748,8 @@ def connect_custom(world, connected_edges, groups, player): else: back_set.remove(edgename1) forward_set.remove(edgename2) - unresolved_similars.append(tuple((forward_set, back_set))) - for forward_pool, back_pool in groups: + unresolved_similars.append(tuple((forward_set, back_set, is_crossed))) + for forward_pool, back_pool in groups.values(): if not len(forward_pool): continue if len(forward_pool[0]) == 1: @@ -635,7 +790,7 @@ def connect_custom(world, connected_edges, groups, player): else: break for pair in unresolved_similars: - forward_set, back_set = pair + forward_set, back_set, _ = pair if edgename1 in forward_set: if edgename2 in back_set: unresolved_similars.remove(pair) @@ -659,24 +814,45 @@ def connect_custom(world, connected_edges, groups, player): custom_edges = custom_edges[player] if 'two-way' in custom_edges: unresolved_similars = [] + def validate_crossed_allowed(edge1, edge2, is_crossed): + return not ((not is_crossed and (edge1 in forced_crossed or edge2 in forced_crossed)) + or (is_crossed and (edge1 in forced_noncrossed or edge2 in forced_noncrossed))) for edgename1, edgename2 in custom_edges['two-way'].items(): edge1 = world.check_for_owedge(edgename1, player) edge2 = world.check_for_owedge(edgename2, player) - if edgename1 not in connected_edges and edgename2 not in connected_edges: + is_crossed = edge1.is_lw(world) != edge2.is_lw(world) + if not validate_crossed_allowed(edge1.name, edge2.name, is_crossed): + if edgename2[-1] == '*': + edge2 = world.check_for_owedge(edge2.name + '*', player) + is_crossed = not is_crossed + else: + raise GenerationException('Violation of force crossed rules: \'%s\' <-> \'%s\'', edgename1, edgename2) + if edge1.name not in connected_edges and edge2.name not in connected_edges: # attempt connection - remove_pair_from_pool(edgename1, edgename2) - connect_two_way(world, edgename1, edgename2, player, connected_edges) + remove_pair_from_pool(edge1.name, edge2.name, is_crossed) + connect_two_way(world, edge1.name, edge2.name, player, connected_edges) # resolve parallel - remove_pair_from_pool(parallel_forward_edge, parallel_back_edge) - elif not edge1.dest or not edge2.dest or edge1.dest.name != edgename2 or edge2.dest.name != edgename1: if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new: parallel_forward_edge = parallel_links_new[edge1.name] parallel_back_edge = parallel_links_new[edge2.name] + if validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed): + remove_pair_from_pool(parallel_forward_edge, parallel_back_edge, is_crossed) + else: + raise GenerationException('Violation of force crossed rules on parallel connection: \'%s\' <-> \'%s\'', edgename1, edgename2) + elif not edge1.dest or not edge2.dest or edge1.dest.name != edge2.name or edge2.dest.name != edge1.name: raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2) # connect leftover similars - for forward_pool, back_pool in unresolved_similars: + for forward_pool, back_pool, is_crossed in unresolved_similars: for (forward_edge, back_edge) in zip(forward_pool, back_pool): - connect_two_way(world, forward_edge, back_edge, player, connected_edges) + if validate_crossed_allowed(forward_edge, back_edge, is_crossed): + connect_two_way(world, forward_edge, back_edge, player, connected_edges) + else: + raise GenerationException('Violation of force crossed rules on unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge) + if world.owShuffle[player] == 'parallel' and forward_edge in parallel_links_new: + parallel_forward_edge = parallel_links_new[forward_edge] + parallel_back_edge = parallel_links_new[back_edge] + if not validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed): + raise GenerationException('Violation of force crossed rules on parallel unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge) def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): edge1 = world.get_entrance(edgename1, player) @@ -1054,12 +1230,16 @@ def reorganize_groups(world, groups, player): def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): groups = defaultdict(lambda: ([],[])) + limited_crossed = False + if world.customizer: + custom_crossed = world.customizer.get_owcrossed() + limited_crossed = custom_crossed and (player in custom_crossed) and ('limit_crossed' in custom_crossed[player]) 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': + if world.owCrossed[player] == 'chaos' and not limited_crossed: groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0]) groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1]) else: @@ -1069,7 +1249,7 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): 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()) + return groups def create_flute_exits(world, player): flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index ecf209b1..36bfbdde 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -69,6 +69,24 @@ placements: Palace of Darkness - Big Chest: Hammer Capacity Upgrade - Left: Moon Pearl Turtle Rock - Pokey 2 Key Drop: Ice Rod +ow-edges: + 1: + two-way: + Kakariko Fortune ES*: Sanctuary WN* + Central Bonk Rocks EC: Potion Shop WN + Central Bonk Rocks ES: Potion Shop WC +ow-crossed: + 1: + force_crossed: + - Links House ES* + - Kakariko Fortune ES* + force_noncrossed: + - Links House NE + limit_crossed: 9 # emulates Limited Crossed +ow-whirlpools: + 1: + two-way: + River Bend Whirlpool: Lake Hylia Whirlpool ow-tileflips: 1: force_flip: diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 58c9494b..96330516 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -201,6 +201,11 @@ class CustomSettings(object): return self.file_source['ow-edges'] return None + def get_owcrossed(self): + if 'ow-crossed' in self.file_source: + return self.file_source['ow-crossed'] + return None + def get_whirlpools(self): if 'ow-whirlpools' in self.file_source: return self.file_source['ow-whirlpools'] From 290fe9f3bd17e5118da0f07dc502ec979ef88ed8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 5 Sep 2023 00:44:02 -0500 Subject: [PATCH 013/123] Minor logical code restructure Mostly just indentation but GitHub textdiff sucks --- OverworldShuffle.py | 177 ++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 89 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d18356b5..498a28ff 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -233,105 +233,104 @@ def link_overworld(world, player): world.owcrossededges[player].append(edge) count_crossed = count_crossed + 1 - if world.owCrossed[player] == 'grouped' or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos') or limited_crossed > -1: - if world.owCrossed[player] == 'grouped': - # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work - # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask - ow_crossed_tiles_mask = [[],[],[]] - world.owcrossededges[player] = 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])] + if world.owCrossed[player] == 'grouped': + # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work + # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask + ow_crossed_tiles_mask = [[],[],[]] + world.owcrossededges[player] = 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 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) - else: - crossed_candidates = list() - for group in trimmed_groups.keys(): - (mode, wrld, dir, terrain, parallel, count) = group - if wrld == WorldType.Light and mode != OpenStd.Standard: - for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): - if forward_set[0] in parallel_links_new: - forward_parallel = [parallel_links_new[e] for e in forward_set] - back_parallel = [parallel_links_new[e] for e in back_set] - forward_combine = forward_set+forward_parallel - back_combine = back_set+back_parallel - combine_set = forward_combine+back_combine - - skip_forward = False - if world.owShuffle[player] == 'vanilla': - if any(edge in force_crossed for edge in combine_set): - if not any(edge in force_noncrossed for edge in combine_set): - if any(edge in force_crossed for edge in forward_combine): - world.owcrossededges[player].extend(forward_set) - count_crossed = count_crossed + 1 - continue - else: - world.owcrossededges[player].extend(back_set) - count_crossed = count_crossed + 1 - continue - else: - raise GenerationException('Conflict detected in force_crossed and force_noncrossed') - if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in combine_set): - continue - else: - skip_back = False - if any(edge in force_crossed for edge in forward_combine): - if not any(edge in force_noncrossed for edge in forward_combine): + # update spoiler + 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], + 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 limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'): + crossed_candidates = list() + for group in trimmed_groups.keys(): + (mode, wrld, dir, terrain, parallel, count) = group + if wrld == WorldType.Light and mode != OpenStd.Standard: + for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): + if forward_set[0] in parallel_links_new: + forward_parallel = [parallel_links_new[e] for e in forward_set] + back_parallel = [parallel_links_new[e] for e in back_set] + forward_combine = forward_set+forward_parallel + back_combine = back_set+back_parallel + combine_set = forward_combine+back_combine + + skip_forward = False + if world.owShuffle[player] == 'vanilla': + if any(edge in force_crossed for edge in combine_set): + if not any(edge in force_noncrossed for edge in combine_set): + if any(edge in force_crossed for edge in forward_combine): world.owcrossededges[player].extend(forward_set) count_crossed = count_crossed + 1 - skip_forward = True + continue else: - raise GenerationException('Conflict detected in force_crossed and force_noncrossed') - if any(edge in force_crossed for edge in back_combine): - if not any(edge in force_noncrossed for edge in back_combine): world.owcrossededges[player].extend(back_set) count_crossed = count_crossed + 1 - skip_back = True - else: - raise GenerationException('Conflict detected in force_crossed and force_noncrossed') - if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in forward_combine): - skip_forward = True - if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in back_combine): - skip_back = True - if not skip_back: - if limited_crossed > -1: - crossed_candidates.append(back_set) - elif random.randint(0, 1): - world.owcrossededges[player].extend(back_set) - count_crossed = count_crossed + 1 - if not skip_forward: - if limited_crossed > -1: - crossed_candidates.append(forward_set) - elif random.randint(0, 1): + continue + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in combine_set): + continue + else: + skip_back = False + if any(edge in force_crossed for edge in forward_combine): + if not any(edge in force_noncrossed for edge in forward_combine): world.owcrossededges[player].extend(forward_set) count_crossed = count_crossed + 1 - assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges" + skip_forward = True + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in force_crossed for edge in back_combine): + if not any(edge in force_noncrossed for edge in back_combine): + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + skip_back = True + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in forward_combine): + skip_forward = True + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in back_combine): + skip_back = True + if not skip_back: + if limited_crossed > -1: + crossed_candidates.append(back_set) + elif random.randint(0, 1): + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + if not skip_forward: + if limited_crossed > -1: + crossed_candidates.append(forward_set) + elif random.randint(0, 1): + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges" - if limited_crossed > -1: - limit = limited_crossed - count_crossed - random.shuffle(crossed_candidates) - for edge_set in crossed_candidates[:limit]: - world.owcrossededges[player].extend(edge_set) - assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges" + if limited_crossed > -1: + limit = limited_crossed - count_crossed + random.shuffle(crossed_candidates) + for edge_set in crossed_candidates[:limit]: + world.owcrossededges[player].extend(edge_set) + assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges" - for edge in copy.deepcopy(world.owcrossededges[player]): - if edge in parallel_links_new: - if parallel_links_new[edge] not in world.owcrossededges[player]: - world.owcrossededges[player].append(parallel_links_new[edge]) + for edge in copy.deepcopy(world.owcrossededges[player]): + if edge in parallel_links_new: + if parallel_links_new[edge] not in world.owcrossededges[player]: + world.owcrossededges[player].append(parallel_links_new[edge]) # after tile flip and crossed, determine edges that need to flip edges_to_swap = [e for e in swapped_edges+world.owcrossededges[player] if (e not in swapped_edges) or (e not in world.owcrossededges[player])] From b0c9f943efd91c95deacf46abe7c1ee10a478e69 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 5 Sep 2023 02:00:19 -0500 Subject: [PATCH 014/123] Adding Customizer documentation for OWR modes --- docs/Customizer.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/Customizer.md b/docs/Customizer.md index 68687c23..0a6c2394 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -87,6 +87,38 @@ You may define an item, and a list of locations. The locations may be weighted i You may define an item and a list of locations that an item should not be placed at. This will apply to all items of that type. The logic is considered for this. If it is otherwise impossible, the item will be considered for the listed locations. This is important for small key layouts mostly, but it will try other locations first. +### ow-edges + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has one primary subsection: `two-way`. + +#### two-way + +`two-way` should be used for defining overworld edge transition connections. An asterisk `*` at the end of an edge name can be used on any parallel edge (an edge that exists in the same place in the opposite world), this will swap the defined edge with its parallel edge if the tile is flipped by Tile Flip. + +`Links House ES*: Stone Bridge WS*` The edge east of Links House will be vanilla, but if Links House screen gets flipped by Tile Flip, then Big Bomb Shop ES will connect to Stone Bridge. + +### ow-crossed + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_crossed`, `force_noncrossed`, and `limit_crossed`. + +#### force_crossed / force_noncrossed + +`force_crossed` and `force_noncrossed` should be used to define specific overworld edge transitions you wish to be cross-world connected without needing to specify an exact destination. These sections are optional but must contain a list of edge names. An asterisk `*` at the end of an edge name can be used on any parallel edge (an edge that exists in the same place in the opposite world), this will swap the defined edge with its parallel edge if the tile is flipped by Tile Flip. + +#### limit_crossed + +`limit_crossed` should be used to limit how many overworld edge transitions end up connecting cross-world. This value can be set to any non-negative integer. A value of 0 means no edges will be cross-world, except for edges that are forced cross-world (either by the previous step or a result of some combination of OWR settings). + +### ow-whirlpools + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_whirlpool: true` in the `settings` section in order for any values here to take effect. This section has one primary subsection: `two-way`. + +#### two-way + +`two-way` should be used for defining whirlpool connections. + +`River Bend Whirlpool: Lake Hylia Whirlpool` The whirlpool west of Potion Shop will be connected to the whirlpool at Lake Hylia. + ### ow-tileflips This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined_chance`. From 508a68e5e5b00c2214967447d24b1d9f53ba294c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 5 Sep 2023 03:01:49 -0500 Subject: [PATCH 015/123] Merging Limited and Chaos Crossed OWR options into Unrestricted --- BaseClasses.py | 2 +- OverworldShuffle.py | 8 +++---- README.md | 22 ++++++------------- Rom.py | 2 +- TestSuiteStat.py | 2 +- docs/Customizer.md | 2 +- mystery_example.yml | 3 +-- resources/app/cli/args.json | 3 +-- resources/app/cli/lang/en.json | 4 +--- resources/app/gui/lang/en.json | 3 +-- .../app/gui/randomize/overworld/widgets.json | 3 +-- source/classes/CustomSettings.py | 4 ++++ source/tools/MysteryUtils.py | 4 ++-- 13 files changed, 26 insertions(+), 36 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6456c1c5..5381d677 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -3481,7 +3481,7 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique # byte 11: OOOT WCCC (OWR layout, free terrain, whirlpools, OWR crossed) or_mode = {"vanilla": 0, "parallel": 1, "full": 2} -orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "limited": 3, "chaos": 4} +orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "unrestricted": 4} # byte 12: KMB? FF?? (keep similar, mixed/tile flip, bonk drops, flute spots) flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2} diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 498a28ff..36dded6b 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -210,7 +210,7 @@ def link_overworld(world, player): force_crossed = set() force_noncrossed = set() count_crossed = 0 - limited_crossed = 9 if world.owCrossed[player] == 'limited' else -1 + limited_crossed = -1 if world.customizer: custom_crossed = world.customizer.get_owcrossed() if custom_crossed and player in custom_crossed: @@ -257,7 +257,7 @@ 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 limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'): + elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'): crossed_candidates = list() for group in trimmed_groups.keys(): (mode, wrld, dir, terrain, parallel, count) = group @@ -1084,7 +1084,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'] or do_grouped: + if world.owCrossed[player] == 'none' or do_grouped: # in polar, the actual edge connections remain vanilla def getSwappedEdges(world, lst, player): for regionname in lst: @@ -1238,7 +1238,7 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): if mode == OpenStd.Standard: groups[key] = group else: - if world.owCrossed[player] == 'chaos' and not limited_crossed: + if world.owCrossed[player] == 'unrestricted' and not limited_crossed: groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0]) groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1]) else: diff --git a/README.md b/README.md index 0bae614a..ad343b01 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,9 @@ With OW Layout Shuffle, this allows land and water edges to be connected. This allows OW connections to be shuffled cross-world. There are 2 main methodologies of Crossed OWR: -- Grouped and Polar both are guaranteed to result in two separated planes of tiles, similar to that of vanilla. This means you cannot simply walk around and be able to visit all the tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a tile that was previously unreachable +- `Grouped` and `Polar` both are guaranteed to result in two separated planes of tiles, similar to that of vanilla. This means you cannot simply walk around and be able to visit all the tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a tile that was previously unreachable -- Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. +- `Unrestricted` is not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. See each option to get more details on the differences. @@ -171,31 +171,23 @@ Transitions will remain same-world. ### Grouped -This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR. +This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike `Unrestricted`). This is considered the simplest way to play Crossed OWR. ### Polar Only effective if Tile Flip (Mixed) is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are flipped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Tile Flip works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Flip / Mixed` section for more details) -### Limited +### Unrestricted -Every transition is independently a candidate to be chosen as a cross-world connection, however only 9 total transitions become crossed (to/from each world). This option abides by the `Keep Similar Edges Together` option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. +Every transition is independently a candidate to be chosen as a cross-world connection. This option abides by the `Keep Similar Edges Together` option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. Each transition has an equal 50/50 chance of being a crossed connection. -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 existing standard portals. - -### Chaos - -Same as Limited, except that there is no limit to the number of cross-world connections that are made. Each transition has an equal 50/50 chance of being a crossed connection. +Note: If Whirlpool Shuffle is enabled, those connections can be cross-world. ## Keep Similar Edges Together (--ow_keepsimilar) This keeps similar edge transitions together. ie. The 2 west land edges of Potion Shop will be paired to another set of two similar edges, unless Free Terrain is also enabled, in which case these 2 edges together with the west water edge form a group of 3 similar edges. See `Terminology` section above for a more detailed explanation of Similar Edges. -Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW. +Note: This affects OW Layout Shuffle mostly, but also affects `Unrestricted` mode in Crossed OW. ## Tile Flip / Mixed Overworld (--ow_mixed) diff --git a/Rom.py b/Rom.py index 559ef2b8..27a1a38f 100644 --- a/Rom.py +++ b/Rom.py @@ -740,7 +740,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif world.owShuffle[player] == 'full': owMode = 2 - if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] in ['limited', 'chaos']): + if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'): owMode |= 0x0100 if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]): owMode |= 0x0200 diff --git a/TestSuiteStat.py b/TestSuiteStat.py index ecde323d..882f9ffb 100644 --- a/TestSuiteStat.py +++ b/TestSuiteStat.py @@ -49,7 +49,7 @@ SETTINGS = { 'ow_fluteshuffle': ['balanced'], 'ow_keepsimilar': [True, False], 'ow_mixed': [True, False], - 'ow_crossed': ['none', 'polar', 'grouped', 'limited'], + 'ow_crossed': ['none', 'polar', 'grouped', 'unrestricted'], 'accessibility': [True], 'difficulty': [False], 'shufflepots': [False], diff --git a/docs/Customizer.md b/docs/Customizer.md index 0a6c2394..67aa37e5 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -107,7 +107,7 @@ This must be defined by player. Each player number should be listed with the app #### limit_crossed -`limit_crossed` should be used to limit how many overworld edge transitions end up connecting cross-world. This value can be set to any non-negative integer. A value of 0 means no edges will be cross-world, except for edges that are forced cross-world (either by the previous step or a result of some combination of OWR settings). +`limit_crossed` should be used to limit how many overworld edge transitions end up connecting cross-world. This value can be set to any non-negative integer. A value of 0 means no edges will be cross-world, except for edges that are forced cross-world (either by the previous step or a result of some combination of OWR settings). This option only takes effect if `ow_crossed: unrestricted` is in the `settings` section. ### ow-whirlpools diff --git a/mystery_example.yml b/mystery_example.yml index 3e65a483..dbb455d7 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -10,8 +10,7 @@ none: 4 polar: 1 grouped: 1 - limited: 1 - chaos: 1 + unrestricted: 1 overworld_keepsimilar: on: 1 off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 7ab56184..52edf0cd 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -174,8 +174,7 @@ "none", "grouped", "polar", - "limited", - "chaos" + "unrestricted" ] }, "ow_keepsimilar": { diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 20dce34c..ca4543d0 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -239,9 +239,7 @@ " walk around and access the other plane version by walking.", "Polar: Only used when Tile Flip is enabled. This retains original", " connections even when overworld tiles are flipped.", - "Limited: Exactly nine transitions are randomly chosen as", - " cross-world connections (to emulate the nine portals).", - "Chaos: Every transition has a 50/50 chance to become a", + "Unrestricted: Every transition has a 50/50 chance to become a", " crossworld connection." ], "ow_keepsimilar": [ diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 187f3c53..95336905 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -147,8 +147,7 @@ "randomizer.overworld.crossed.none": "None", "randomizer.overworld.crossed.grouped": "Grouped", "randomizer.overworld.crossed.polar": "Polar", - "randomizer.overworld.crossed.limited": "Limited", - "randomizer.overworld.crossed.chaos": "Chaos", + "randomizer.overworld.crossed.unrestricted": "Unrestricted", "randomizer.overworld.keepsimilar": "Keep Similar Edges Together", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index c9438deb..c3c49c94 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -17,8 +17,7 @@ "none", "grouped", "polar", - "limited", - "chaos" + "unrestricted" ] }, "mixed": { diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 96330516..c3f20ff9 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -79,6 +79,10 @@ class CustomSettings(object): args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p]) args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p]) args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p]) + if args.ow_crossed[p] == 'chaos': + import logging + logging.getLogger('').info("Crossed OWR option 'chaos' is deprecated. Use 'unrestricted' instead.") + args.ow_crossed[p] = 'unrestricted' args.ow_keepsimilar[p] = get_setting(settings['ow_keepsimilar'], args.ow_keepsimilar[p]) args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p]) args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p]) diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 0473e2c1..12266f22 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -78,9 +78,9 @@ def roll_settings(weights): overworld_shuffle = get_choice('overworld_shuffle') ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' ret.ow_terrain = get_choice('overworld_terrain') == 'on' - valid_options = {'none', 'polar', 'grouped', 'limited', 'chaos'} + valid_options = {'none': 'none', 'polar': 'polar', 'grouped': 'polar', 'chaos': 'unrestricted', 'unrestricted': 'unrestricted'} ret.ow_crossed = get_choice('overworld_crossed') - ret.ow_crossed = ret.ow_crossed if ret.ow_crossed in valid_options else 'none' + ret.ow_crossed = valid_options[ret.ow_crossed] if ret.ow_crossed in valid_options else 'none' ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on' ret.ow_mixed = get_choice('overworld_swap') == 'on' ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on' From bcb224ade8bee3e822751ecbb87687e517ad064a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 5 Sep 2023 13:38:29 -0500 Subject: [PATCH 016/123] Some code simplification --- BaseClasses.py | 81 +++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 5381d677..b7e9d812 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -240,29 +240,16 @@ class World(object): raise RuntimeError('No such region %s for player %d' % (regionname, player)) def get_owedge(self, edgename, player): - if isinstance(edgename, OWEdge): - return edgename - try: - return self._owedge_cache[(edgename, player)] - except KeyError: - for edge in self.owedges: - if edge.name == edgename and edge.player == player: - self._owedge_cache[(edgename, player)] = edge - return edge + edge = self.check_for_owedge(edgename, player) + if edge is None: raise RuntimeError('No such edge %s for player %d' % (edgename, player)) + return edge def get_entrance(self, entrance, player): - if isinstance(entrance, Entrance): - return entrance - try: - return self._entrance_cache[(entrance, player)] - except KeyError: - for region in self.regions: - for exit in region.exits: - if exit.name == entrance and exit.player == player: - self._entrance_cache[(entrance, player)] = exit - return exit + ent = self.check_for_entrance(entrance, player) + if ent is None: raise RuntimeError('No such entrance %s for player %d' % (entrance, player)) + return ent def remove_entrance(self, entrance, player): if (entrance, player) in self._entrance_cache.keys(): @@ -296,16 +283,10 @@ class World(object): raise RuntimeError('No such dungeon %s for player %d' % (dungeonname, player)) def get_door(self, doorname, player): - if isinstance(doorname, Door): - return doorname - try: - return self._door_cache[(doorname, player)] - except KeyError: - for door in self.doors: - if door.name == doorname and door.player == player: - self._door_cache[(doorname, player)] = door - return door + door = self.check_for_door(doorname, player) + if door is None: raise RuntimeError('No such door %s for player %d' % (doorname, player)) + return door def get_portal(self, portal_name, player): if isinstance(portal_name, Portal): @@ -319,28 +300,6 @@ class World(object): return portal raise RuntimeError('No such portal %s for player %d' % (portal_name, player)) - def check_for_owedge(self, edgename, player): - if isinstance(edgename, OWEdge): - return edgename - try: - if edgename[-1] == '*': - edgename = edgename[:-1] - edge = self.check_for_owedge(edgename, player) - if self.is_tile_swapped(edge.owIndex, player): - from OverworldShuffle import parallel_links - if edgename in parallel_links.keys() or edgename in parallel_links.inverse.keys(): - edgename = parallel_links[edgename] if edgename in parallel_links.keys() else parallel_links.inverse[edgename][0] - return self.check_for_owedge(edgename, player) - else: - raise Exception("Edge notated with * doesn't have a parallel edge: %s" & edgename) - return self._owedge_cache[(edgename, player)] - except KeyError: - for edge in self.owedges: - if edge.name == edgename and edge.player == player: - self._owedge_cache[(edgename, player)] = edge - return edge - return None - def is_tile_swapped(self, owid, player): return (self.mode[player] == 'inverted') != (owid in self.owswaps[player][0] and self.owMixed[player]) @@ -366,6 +325,28 @@ class World(object): else: return False + def check_for_owedge(self, edgename, player): + if isinstance(edgename, OWEdge): + return edgename + try: + if edgename[-1] == '*': + edgename = edgename[:-1] + edge = self.check_for_owedge(edgename, player) + if self.is_tile_swapped(edge.owIndex, player): + from OverworldShuffle import parallel_links + if edgename in parallel_links.keys() or edgename in parallel_links.inverse.keys(): + edgename = parallel_links[edgename] if edgename in parallel_links.keys() else parallel_links.inverse[edgename][0] + return self.check_for_owedge(edgename, player) + else: + raise Exception("Edge notated with * doesn't have a parallel edge: %s" & edgename) + return self._owedge_cache[(edgename, player)] + except KeyError: + for edge in self.owedges: + if edge.name == edgename and edge.player == player: + self._owedge_cache[(edgename, player)] = edge + return edge + return None + def check_for_door(self, doorname, player): if isinstance(doorname, Door): return doorname From 99753c00390926b5d97e3cb6ded6f6bb1797e3a8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 6 Sep 2023 12:39:57 -0500 Subject: [PATCH 017/123] Minor fixed to OWR algorithm --- OverworldShuffle.py | 29 +++++++++++++---------------- source/classes/CustomSettings.py | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 36dded6b..10853982 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -217,11 +217,11 @@ def link_overworld(world, player): custom_crossed = custom_crossed[player] if 'force_crossed' in custom_crossed and len(custom_crossed['force_crossed']) > 0: for edgename in custom_crossed['force_crossed']: - edge = world.check_for_owedge(edgename, player) + edge = world.get_owedge(edgename, player) force_crossed.add(edge.name) if 'force_noncrossed' in custom_crossed and len(custom_crossed['force_noncrossed']) > 0: for edgename in custom_crossed['force_noncrossed']: - edge = world.check_for_owedge(edgename, player) + edge = world.get_owedge(edgename, player) force_noncrossed.add(edge.name) if 'limit_crossed' in custom_crossed: limited_crossed = custom_crossed['limit_crossed'] @@ -322,9 +322,10 @@ def link_overworld(world, player): if limited_crossed > -1: limit = limited_crossed - count_crossed - random.shuffle(crossed_candidates) - for edge_set in crossed_candidates[:limit]: - world.owcrossededges[player].extend(edge_set) + if limit > 1: + random.shuffle(crossed_candidates) + for edge_set in crossed_candidates[:limit]: + world.owcrossededges[player].extend(edge_set) assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges" for edge in copy.deepcopy(world.owcrossededges[player]): @@ -488,7 +489,7 @@ def link_overworld(world, player): back_parallel_lw_sets, back_parallel_dw_sets = [], [] for edge_set in forward_edge_sets: - if world.check_for_owedge(edge_set[0], player).is_lw(world): + if world.get_owedge(edge_set[0], player).is_lw(world): forward_lw_sets.append(edge_set) if parallel == IsParallel.Yes: forward_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set]) @@ -497,7 +498,7 @@ def link_overworld(world, player): if parallel == IsParallel.Yes: forward_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set]) for edge_set in back_edge_sets: - if world.check_for_owedge(edge_set[0], player).is_lw(world): + if world.get_owedge(edge_set[0], player).is_lw(world): back_lw_sets.append(edge_set) if parallel == IsParallel.Yes: back_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set]) @@ -817,12 +818,12 @@ def connect_custom(world, connected_edges, groups, forced, player): return not ((not is_crossed and (edge1 in forced_crossed or edge2 in forced_crossed)) or (is_crossed and (edge1 in forced_noncrossed or edge2 in forced_noncrossed))) for edgename1, edgename2 in custom_edges['two-way'].items(): - edge1 = world.check_for_owedge(edgename1, player) - edge2 = world.check_for_owedge(edgename2, player) + edge1 = world.get_owedge(edgename1, player) + edge2 = world.get_owedge(edgename2, player) is_crossed = edge1.is_lw(world) != edge2.is_lw(world) if not validate_crossed_allowed(edge1.name, edge2.name, is_crossed): if edgename2[-1] == '*': - edge2 = world.check_for_owedge(edge2.name + '*', player) + edge2 = world.get_owedge(edge2.name + '*', player) is_crossed = not is_crossed else: raise GenerationException('Violation of force crossed rules: \'%s\' <-> \'%s\'', edgename1, edgename2) @@ -856,13 +857,9 @@ def connect_custom(world, connected_edges, groups, forced, player): def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): edge1 = world.get_entrance(edgename1, player) edge2 = world.get_entrance(edgename2, player) - x = world.check_for_owedge(edgename1, player) - y = world.check_for_owedge(edgename2, player) + x = world.get_owedge(edgename1, player) + y = world.get_owedge(edgename2, player) - if x is None: - raise Exception('%s is not a valid edge.', edgename1) - elif y is None: - raise Exception('%s is not a valid edge.', edgename2) if connected_edges is not None: if edgename1 in connected_edges or edgename2 in connected_edges: if (x.dest and x.dest.name == edgename2) and (y.dest and y.dest.name == edgename1): diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index c3f20ff9..5ec40409 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -388,7 +388,7 @@ class CustomSettings(object): # tile flips if p in world.owswaps and len(world.owswaps[p][0]) > 0: flips[p] = {} - flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) + flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f & 0x40 == 0) flips[p]['force_flip'].sort() flips[p]['undefined_chance'] = 0 # flute spots From 72e41e20dc4cd6460880620a5c5d532a60caecfc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 6 Sep 2023 13:58:19 -0500 Subject: [PATCH 018/123] Implemented Customizer OWR Edge Groups --- OWEdges.py | 88 ++++++++++++++++++------------------ OverworldShuffle.py | 54 ++++++++++++++-------- docs/Customizer.md | 15 +++++- docs/customizer_example.yaml | 11 +++++ 4 files changed, 104 insertions(+), 64 deletions(-) diff --git a/OWEdges.py b/OWEdges.py index f4da280e..751bd307 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -332,8 +332,8 @@ def create_owedge(player, name, owIndex, direction, terrain, edge_id, owSlotInde OWEdgeGroups = { - #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup) - (St, LW, Vt, Ld, PL, 1): ( + #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup, CustomizerGroup) + (St, LW, Vt, Ld, PL, 1, None): ( [ ['Hyrule Castle SW'], ['Hyrule Castle SE'] @@ -343,7 +343,7 @@ OWEdgeGroups = { ['Links House NE'] ] ), - (St, LW, Hz, Ld, PL, 3): ( + (St, LW, Hz, Ld, PL, 3, None): ( [ ['Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES'] ], @@ -351,7 +351,7 @@ OWEdgeGroups = { ['Links House WN', 'Links House WC', 'Links House WS'] ] ), - (Op, LW, Hz, Ld, PL, 1): ( + (Op, LW, Hz, Ld, PL, 1, None): ( [ ['Lost Woods EN'], ['East Death Mountain EN'], @@ -391,7 +391,7 @@ OWEdgeGroups = { ['East Death Mountain WS'] ] ), - (Op, LW, Hz, Ld, NP, 1): ( + (Op, LW, Hz, Ld, NP, 1, None): ( [ ['Forgotten Forest ES'] ], @@ -399,7 +399,7 @@ OWEdgeGroups = { ['Hyrule Castle WN'] ] ), - (Op, LW, Vt, Ld, PL, 1): ( + (Op, LW, Vt, Ld, PL, 1, None): ( [ ['Lumberjack SW'], ['Mountain Pass SE'], @@ -439,7 +439,7 @@ OWEdgeGroups = { ['Octoballoon NE'] ] ), - (Op, LW, Vt, Ld, NP, 1): ( + (Op, LW, Vt, Ld, NP, 1, None): ( [ ['Master Sword Meadow SC'], ['Zoras Domain SW'] @@ -449,7 +449,7 @@ OWEdgeGroups = { ['Zora Waterfall NE'] ] ), - (Op, LW, Hz, Ld, PL, 2): ( + (Op, LW, Hz, Ld, PL, 2, None): ( [ ['Kakariko Fortune EN', 'Kakariko Fortune ES'], ['Kakariko Pond EN', 'Kakariko Pond ES'], @@ -465,7 +465,7 @@ OWEdgeGroups = { ['Statues WN', 'Statues WS'] ] ), - (Op, LW, Hz, Ld, NP, 2): ( + (Op, LW, Hz, Ld, NP, 2, None): ( [ ['Desert EC', 'Desert ES'] ], @@ -473,7 +473,7 @@ OWEdgeGroups = { ['Desert Pass WC', 'Desert Pass WS'] ] ), - (Op, LW, Vt, Ld, PL, 2): ( + (Op, LW, Vt, Ld, PL, 2, None): ( [ ['Lost Woods SW', 'Lost Woods SC'], ['Lost Woods Pass SW', 'Lost Woods Pass SE'], @@ -489,7 +489,7 @@ OWEdgeGroups = { ['Wooden Bridge NW', 'Wooden Bridge NE'] ] ), - (Op, LW, Hz, Wr, PL, 1): ( + (Op, LW, Hz, Wr, PL, 1, None): ( [ ['Potion Shop EN'], ['Lake Hylia EC'], @@ -505,7 +505,7 @@ OWEdgeGroups = { ['Statues WC'] ] ), - (Op, LW, Hz, Wr, NP, 1): ( + (Op, LW, Hz, Wr, NP, 1, None): ( [ ['Hobo EC'] ], @@ -513,7 +513,7 @@ OWEdgeGroups = { ['Stone Bridge WC'] ] ), - (Op, LW, Vt, Wr, PL, 1): ( + (Op, LW, Vt, Wr, PL, 1, None): ( [ ['Tree Line SC'], ['Ice Cave SW'], @@ -525,7 +525,7 @@ OWEdgeGroups = { ['Wooden Bridge NC'] ] ), - (Op, DW, Hz, Ld, PL, 1): ( + (Op, DW, Hz, Ld, PL, 1, None): ( [ ['Skull Woods EN'], ['East Dark Death Mountain EN'], @@ -563,7 +563,7 @@ OWEdgeGroups = { ['East Dark Death Mountain WS'] ] ), - (Op, DW, Vt, Ld, PL, 1): ( + (Op, DW, Vt, Ld, PL, 1, None): ( [ ['Dark Lumberjack SW'], ['Bumper Cave SE'], @@ -607,11 +607,11 @@ OWEdgeGroups = { ['Bomber Corner NE'] ] ), - (Op, DW, Hz, Ld, NP, 1): ( + (Op, DW, Hz, Ld, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, Ld, PL, 2): ( + (Op, DW, Hz, Ld, PL, 2, None): ( [ ['Dark Fortune EN', 'Dark Fortune ES'], ['Outcast Pond EN', 'Outcast Pond ES'], @@ -627,11 +627,11 @@ OWEdgeGroups = { ['Hype Cave WN', 'Hype Cave WS'] ] ), - (Op, DW, Vt, Ld, NP, 1): ( + (Op, DW, Vt, Ld, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, Ld, NP, 2): ( + (Op, DW, Hz, Ld, NP, 2, None): ( [ ['Dig Game EC', 'Dig Game ES'] ], @@ -639,7 +639,7 @@ OWEdgeGroups = { ['Frog WC', 'Frog WS'] ] ), - (Op, DW, Vt, Ld, PL, 2): ( + (Op, DW, Vt, Ld, PL, 2, None): ( [ ['Skull Woods SW', 'Skull Woods SC'], ['Skull Woods Pass SW', 'Skull Woods Pass SE'], @@ -655,7 +655,7 @@ OWEdgeGroups = { ['Broken Bridge NW', 'Broken Bridge NE'] ] ), - (Op, DW, Hz, Ld, PL, 3): ( + (Op, DW, Hz, Ld, PL, 3, None): ( [ ['Dark Bonk Rocks EN', 'Dark Bonk Rocks EC', 'Dark Bonk Rocks ES'] ], @@ -663,7 +663,7 @@ OWEdgeGroups = { ['Big Bomb Shop WN', 'Big Bomb Shop WC', 'Big Bomb Shop WS'] ] ), - (Op, DW, Hz, Wr, PL, 1): ( + (Op, DW, Hz, Wr, PL, 1, None): ( [ ['Dark Witch EN'], ['Ice Lake EC'], @@ -679,11 +679,11 @@ OWEdgeGroups = { ['Hype Cave WC'] ] ), - (Op, DW, Hz, Wr, NP, 1): ( + (Op, DW, Hz, Wr, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Vt, Wr, PL, 1): ( + (Op, DW, Vt, Wr, PL, 1, None): ( [ ['Dark Tree Line SC'], ['Shopping Mall SW'], @@ -699,7 +699,7 @@ OWEdgeGroups = { OWEdgeGroupsTerrain = { #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup) - (St, LW, Vt, None, PL, 1): ( + (St, LW, Vt, None, PL, 1, None): ( [ ['Hyrule Castle SW'], ['Hyrule Castle SE'] @@ -709,7 +709,7 @@ OWEdgeGroupsTerrain = { ['Links House NE'] ] ), - (St, LW, Hz, None, PL, 3): ( + (St, LW, Hz, None, PL, 3, None): ( [ ['Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES'] ], @@ -717,7 +717,7 @@ OWEdgeGroupsTerrain = { ['Links House WN', 'Links House WC', 'Links House WS'] ] ), - (Op, LW, Hz, None, PL, 1): ( + (Op, LW, Hz, None, PL, 1, None): ( [ ['Lost Woods EN'], ['East Death Mountain EN'], @@ -751,7 +751,7 @@ OWEdgeGroupsTerrain = { ['East Death Mountain WS'] ] ), - (Op, LW, Hz, None, NP, 1): ( + (Op, LW, Hz, None, NP, 1, None): ( [ ['Forgotten Forest ES'], ['Hobo EC'] @@ -761,7 +761,7 @@ OWEdgeGroupsTerrain = { ['Stone Bridge WC'] ] ), - (Op, LW, Vt, None, PL, 1): ( + (Op, LW, Vt, None, PL, 1, None): ( [ ['Lumberjack SW'], ['Mountain Pass SE'], @@ -797,7 +797,7 @@ OWEdgeGroupsTerrain = { ['South Pass NC'] ] ), - (Op, LW, Vt, None, NP, 1): ( + (Op, LW, Vt, None, NP, 1, None): ( [ ['Master Sword Meadow SC'], ['Zoras Domain SW'] @@ -807,7 +807,7 @@ OWEdgeGroupsTerrain = { ['Zora Waterfall NE'] ] ), - (Op, LW, Hz, None, PL, 2): ( + (Op, LW, Hz, None, PL, 2, None): ( [ ['Kakariko Fortune EN', 'Kakariko Fortune ES'], ['Kakariko Pond EN', 'Kakariko Pond ES'], @@ -825,7 +825,7 @@ OWEdgeGroupsTerrain = { ['Tree Line WN', 'Tree Line WC'] ] ), - (Op, LW, Hz, None, NP, 2): ( + (Op, LW, Hz, None, NP, 2, None): ( [ ['Desert EC', 'Desert ES'] ], @@ -833,7 +833,7 @@ OWEdgeGroupsTerrain = { ['Desert Pass WC', 'Desert Pass WS'] ] ), - (Op, LW, Vt, None, PL, 2): ( + (Op, LW, Vt, None, PL, 2, None): ( [ ['Lost Woods SW', 'Lost Woods SC'], ['Lost Woods Pass SW', 'Lost Woods Pass SE'], @@ -851,7 +851,7 @@ OWEdgeGroupsTerrain = { ['Octoballoon NW', 'Octoballoon NE'] ] ), - (Op, LW, Hz, None, PL, 3): ( + (Op, LW, Hz, None, PL, 3, None): ( [ ['River Bend EN', 'River Bend EC', 'River Bend ES'], ['C Whirlpool EN', 'C Whirlpool EC', 'C Whirlpool ES'] @@ -861,7 +861,7 @@ OWEdgeGroupsTerrain = { ['Statues WN', 'Statues WC', 'Statues WS'] ] ), - (Op, LW, Vt, None, PL, 3): ( + (Op, LW, Vt, None, PL, 3, None): ( [ ['River Bend SW', 'River Bend SC', 'River Bend SE'] ], @@ -869,7 +869,7 @@ OWEdgeGroupsTerrain = { ['Wooden Bridge NW', 'Wooden Bridge NC', 'Wooden Bridge NE'] ] ), - (Op, DW, Hz, None, PL, 1): ( + (Op, DW, Hz, None, PL, 1, None): ( [ ['Skull Woods EN'], ['East Dark Death Mountain EN'], @@ -901,7 +901,7 @@ OWEdgeGroupsTerrain = { ['East Dark Death Mountain WS'] ] ), - (Op, DW, Vt, None, PL, 1): ( + (Op, DW, Vt, None, PL, 1, None): ( [ ['Dark Lumberjack SW'], ['Bumper Cave SE'], @@ -941,11 +941,11 @@ OWEdgeGroupsTerrain = { ['Dark South Pass NC'] ] ), - (Op, DW, Hz, None, NP, 1): ( + (Op, DW, Hz, None, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, None, PL, 2): ( + (Op, DW, Hz, None, PL, 2, None): ( [ ['Dark Fortune EN', 'Dark Fortune ES'], ['Outcast Pond EN', 'Outcast Pond ES'], @@ -963,11 +963,11 @@ OWEdgeGroupsTerrain = { ['Dark Tree Line WN', 'Dark Tree Line WC'] ] ), - (Op, DW, Vt, None, NP, 1): ( + (Op, DW, Vt, None, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, None, NP, 2): ( + (Op, DW, Hz, None, NP, 2, None): ( [ ['Dig Game EC', 'Dig Game ES'] ], @@ -975,7 +975,7 @@ OWEdgeGroupsTerrain = { ['Frog WC', 'Frog WS'] ] ), - (Op, DW, Vt, None, PL, 2): ( + (Op, DW, Vt, None, PL, 2, None): ( [ ['Skull Woods SW', 'Skull Woods SC'], ['Skull Woods Pass SW', 'Skull Woods Pass SE'], @@ -993,7 +993,7 @@ OWEdgeGroupsTerrain = { ['Bomber Corner NW', 'Bomber Corner NE'] ] ), - (Op, DW, Hz, None, PL, 3): ( + (Op, DW, Hz, None, PL, 3, None): ( [ ['Dark Bonk Rocks EN', 'Dark Bonk Rocks EC', 'Dark Bonk Rocks ES'], ['Qirn Jump EN', 'Qirn Jump EC', 'Qirn Jump ES'], @@ -1005,7 +1005,7 @@ OWEdgeGroupsTerrain = { ['Hype Cave WN', 'Hype Cave WC', 'Hype Cave WS'] ] ), - (Op, DW, Vt, None, PL, 3): ( + (Op, DW, Vt, None, PL, 3, None): ( [ ['Qirn Jump SW', 'Qirn Jump SC', 'Qirn Jump SE'] ], diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 10853982..7e3438e7 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -60,7 +60,7 @@ def link_overworld(world, player): new_groups[group] = ([],[]) for group in groups.keys(): - (mode, wrld, dir, terrain, parallel, count) = group + (mode, wrld, dir, terrain, parallel, count, custom) = group for (forward_set, back_set) in zip(groups[group][0], groups[group][1]): anyF = any(edge in orig_swaps for edge in forward_set) anyB = any(edge in orig_swaps for edge in back_set) @@ -75,11 +75,11 @@ def link_overworld(world, player): if parallel == IsParallel.Yes and not (all(edge in orig_swaps for edge in map(getParallel, forward_set)) and all(edge in orig_swaps for edge in map(getParallel, back_set))): raise Exception('Cannot move a parallel edge without the other') new_mode = OpenStd.Open - if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)) not in new_groups.keys(): + if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)) not in new_groups.keys(): # when Links House tile is flipped, the DW edges need to get put into existing Standard group new_mode = OpenStd.Standard - new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][0].append(forward_set) - new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][1].append(back_set) + new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)][0].append(forward_set) + new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)][1].append(back_set) for edge in forward_set: swaps.remove(edge) for edge in back_set: @@ -119,28 +119,28 @@ def link_overworld(world, player): del parallel_links_new['Maze Race ES'] del parallel_links_new['Kakariko Suburb WS'] for group in trimmed_groups.keys(): - (std, region, axis, terrain, parallel, _) = group + (std, region, axis, terrain, parallel, _, custom) = group if parallel == IsParallel.Yes: (forward_edges, back_edges) = trimmed_groups[group] if ['Maze Race ES'] in forward_edges: forward_edges.remove(['Maze Race ES']) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][0].append(['Maze Race ES']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES']) if ['Kakariko Suburb WS'] in back_edges: back_edges.remove(['Kakariko Suburb WS']) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Kakariko Suburb WS']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS']) trimmed_groups[group] = (forward_edges, back_edges) else: for group in trimmed_groups.keys(): - (std, region, axis, terrain, _, _) = group + (std, region, axis, terrain, _, _, custom) = group (forward_edges, back_edges) = trimmed_groups[group] if ['Dig Game EC', 'Dig Game ES'] in forward_edges: forward_edges.remove(['Dig Game EC', 'Dig Game ES']) - trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1)][0].append(['Dig Game ES']) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][0].append(['Dig Game EC']) + trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC']) if ['Frog WC', 'Frog WS'] in back_edges: back_edges.remove(['Frog WC', 'Frog WS']) - trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1)][1].append(['Frog WS']) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC']) + trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC']) trimmed_groups[group] = (forward_edges, back_edges) parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})} @@ -260,7 +260,7 @@ def link_overworld(world, player): elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'): crossed_candidates = list() for group in trimmed_groups.keys(): - (mode, wrld, dir, terrain, parallel, count) = group + (mode, wrld, dir, terrain, parallel, count, _) = group if wrld == WorldType.Light and mode != OpenStd.Standard: for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): if forward_set[0] in parallel_links_new: @@ -459,7 +459,7 @@ def link_overworld(world, player): random.shuffle(groupKeys) for key in groupKeys: - (mode, wrld, dir, terrain, parallel, count) = key + (mode, wrld, dir, terrain, parallel, count, _) = key (forward_edge_sets, back_edge_sets) = groups[key] def remove_connected(): s = 0 @@ -1227,24 +1227,42 @@ def reorganize_groups(world, groups, player): def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): groups = defaultdict(lambda: ([],[])) limited_crossed = False + custom_groups = dict() if world.customizer: custom_crossed = world.customizer.get_owcrossed() limited_crossed = custom_crossed and (player in custom_crossed) and ('limit_crossed' in custom_crossed[player]) + custom_edge_groups = world.customizer.get_owedges() + if custom_edge_groups and player in custom_edge_groups: + custom_edge_groups = custom_edge_groups[player] + if 'groups' in custom_edge_groups: + custom_groups = dict(custom_edge_groups['groups']) + for name, edges in custom_groups.items(): + custom_groups[name] = [world.get_owedge(e, player).name if e[-1] == '*' else e for e in edges] for (key, group) in trimmed_groups.items(): - (mode, wrld, dir, terrain, parallel, count) = key + (mode, wrld, dir, terrain, parallel, count, custom) = key if mode == OpenStd.Standard: groups[key] = group else: if world.owCrossed[player] == 'unrestricted' and not limited_crossed: - groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0]) - groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1]) + groups[(mode, None, dir, terrain, parallel, count, custom)][0].extend(group[0]) + groups[(mode, None, dir, terrain, parallel, count, custom)][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) + groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count, custom)][i].append(edge_set) + for (key, group) in groups.copy().items(): + (mode, wrld, dir, terrain, parallel, count, custom) = key + if mode != OpenStd.Standard: + for group_name, edges in custom_groups.items(): + for i in range(0, 2): + matches = [s for s in groups[key][i] if any(e in s for e in edges)] + if len(matches) > 0: + for m in matches: + groups[key][i].remove(m) + groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches) return groups def create_flute_exits(world, player): diff --git a/docs/Customizer.md b/docs/Customizer.md index 67aa37e5..d30a7d30 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -89,7 +89,7 @@ You may define an item and a list of locations that an item should not be placed ### ow-edges -This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has one primary subsection: `two-way`. +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`. #### two-way @@ -97,13 +97,24 @@ This must be defined by player. Each player number should be listed with the app `Links House ES*: Stone Bridge WS*` The edge east of Links House will be vanilla, but if Links House screen gets flipped by Tile Flip, then Big Bomb Shop ES will connect to Stone Bridge. +#### groups + +`groups` should be used for defining new pool divisions of overworld edge transitions. Each group must have some unique name with all the edges listed that are desired to exist in the pool. The name of a group can be anything as long as it is valid yaml syntax. These defined groups cannot break up edges that conflict with mode settings, like `Keep Similar Edges Together`. The asterisk `*` notation, described in the `ow-edges/two-way` section, can be used here. + +This example puts these 2 edges in their own pool, while the rest of the edges remain in their existing pools: +``` +someDescription: + - Links House ES* + - Stone Bridge WS* +``` + ### ow-crossed This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_crossed`, `force_noncrossed`, and `limit_crossed`. #### force_crossed / force_noncrossed -`force_crossed` and `force_noncrossed` should be used to define specific overworld edge transitions you wish to be cross-world connected without needing to specify an exact destination. These sections are optional but must contain a list of edge names. An asterisk `*` at the end of an edge name can be used on any parallel edge (an edge that exists in the same place in the opposite world), this will swap the defined edge with its parallel edge if the tile is flipped by Tile Flip. +`force_crossed` and `force_noncrossed` should be used to define specific overworld edge transitions you wish to be cross-world connected without needing to specify an exact destination. These sections are optional but must contain a list of edge names. The asterisk `*` notation, described in the `ow-edges/two-way` section, can be used here. #### limit_crossed diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index 36bfbdde..a9cebd90 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -75,6 +75,17 @@ ow-edges: Kakariko Fortune ES*: Sanctuary WN* Central Bonk Rocks EC: Potion Shop WN Central Bonk Rocks ES: Potion Shop WC + groups: + someDescription: + - Tree Line SC* + - Lake Hylia NC* + - Dark Tree Line SC* + - Ice Lake NC* + 1234: + - Lake Hylia EC* + - Octoballoon WC* + - Ice Lake EC* + - Bomber Corner WC* ow-crossed: 1: force_crossed: From d709ab7a1965cfd5f1a89d4e32d5299b0351a209 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 6 Sep 2023 17:19:56 -0500 Subject: [PATCH 019/123] Fixed some broken randomization --- OverworldShuffle.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 7e3438e7..ba78893a 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -454,7 +454,9 @@ def link_overworld(world, player): groupKeys = list(groups.keys()) if world.mode[player] == 'standard': - random.shuffle(groupKeys[2:]) # keep first 2 groups (Standard) first + subset = groupKeys[2:] + random.shuffle(subset) # keep first 2 groups (Standard) first + groupKeys[2:] = subset else: random.shuffle(groupKeys) @@ -697,7 +699,9 @@ def link_overworld(world, player): if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]): addSpot(0x30, True, True) # guarantee desert/mire access - random.shuffle(sector[1]) + subset = sector[1] + random.shuffle(subset) + sector[1] = subset f = 0 t = 0 while len(new_spots) < target_spots: From 4cb28a11554b92c60996b8782d9512607dd8e5c4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 6 Sep 2023 17:36:09 -0500 Subject: [PATCH 020/123] Force tile group merges if Customizer edge connections force it --- OverworldShuffle.py | 181 ++++++++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 81 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index ba78893a..3e737ac5 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -109,7 +109,6 @@ def link_overworld(world, player): raise NotImplementedError('Invalid OW Edge flip scenario') return new_groups - tile_groups = define_tile_groups(world, player, False) trimmed_groups = copy.deepcopy(OWEdgeGroupsTerrain if world.owTerrain[player] else OWEdgeGroups) swapped_edges = list() @@ -152,7 +151,8 @@ def link_overworld(world, player): # tile shuffle logging.getLogger('').debug('Flipping overworld tiles') if world.owMixed[player]: - swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player) + tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, player, False) + swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, (force_flipped, force_nonflipped, undefined_chance), player) update_world_regions(world, player) @@ -237,7 +237,8 @@ def link_overworld(world, player): # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask ow_crossed_tiles_mask = [[],[],[]] - world.owcrossededges[player] = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player) + tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, player, True) + world.owcrossededges[player] = shuffle_tiles(world, tile_groups, ow_crossed_tiles_mask, True, (force_flipped, force_nonflipped, undefined_chance), 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 @@ -900,7 +901,96 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): except KeyError: raise KeyError('No parallel edge for edge %s' % edgename2) -def shuffle_tiles(world, groups, result_list, do_grouped, player): +def determine_forced_flips(world, tile_ow_groups, player): + undefined_chance = 50 + flipped_groups = list() + nonflipped_groups = list() + merged_owids = list() + if world.customizer: + custom_flips = world.customizer.get_owtileflips() + if custom_flips and player in custom_flips: + custom_flips = custom_flips[player] + forced_flips = list() + forced_nonflips = list() + if 'undefined_chance' in custom_flips: + undefined_chance = custom_flips['undefined_chance'] + if 'force_flip' in custom_flips: + forced_flips = custom_flips['force_flip'] + if 'force_no_flip' in custom_flips: + forced_nonflips = custom_flips['force_no_flip'] + + for group in tile_ow_groups: + if any(owid in group[0]+group[1] for owid in forced_nonflips): + nonflipped_groups.append(group) + if any(owid in group[0]+group[1] for owid in forced_flips): + flipped_groups.append(group) + + if undefined_chance == 0: + nonflipped_groups.extend([g for g in tile_ow_groups if g not in flipped_groups + nonflipped_groups]) + + # ensure any customized connections don't end up crossworld + if world.owCrossed[player] == 'none': + def should_merge_group(s1_owid, s2_owid): + flip_together = (s1_owid & 0x40) == (s2_owid & 0x40) + s1_nonflipped = any(g for g in nonflipped_groups if s1_owid in g) + s1_flipped = any(g for g in flipped_groups if s1_owid in g) + if s1_nonflipped or s1_flipped: + group = next(g for g in tile_ow_groups if s2_owid in g) + if s1_nonflipped == flip_together: + nonflipped_groups.append(group) + else: + flipped_groups.append(group) + else: + s2_nonflipped = any(g for g in nonflipped_groups if s2_owid in g) + s2_flipped = any(g for g in flipped_groups if s2_owid in g) + if s2_nonflipped or s2_flipped: + group = next(g for g in tile_ow_groups if s1_owid in g) + if s2_nonflipped == flip_together: + nonflipped_groups.append(group) + else: + flipped_groups.append(group) + else: + s1_group = next(g for g in tile_ow_groups if s1_owid in g) + s2_group = next(g for g in tile_ow_groups if s2_owid in g) + if not flip_together: + if random.randint(0, 1) > 0: + nonflipped_groups.append(s1_group) + flipped_groups.append(s2_group) + else: + flipped_groups.append(s1_group) + nonflipped_groups.append(s2_group) + else: + return True + return False + if world.owWhirlpoolShuffle[player]: + custom_whirlpools = world.customizer.get_whirlpools() + if custom_whirlpools and player in custom_whirlpools: + custom_whirlpools = custom_whirlpools[player] + if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0: + custom_whirlpools = custom_whirlpools['two-way'] + whirlpool_map = {name:owid for wc in default_whirlpool_connections for (owid, name, _) in wc} + for whirl1, whirl2 in custom_whirlpools.items(): + if [whirlpool_map[whirl1], whirlpool_map[whirl2]] not in merged_owids and should_merge_group(whirlpool_map[whirl1], whirlpool_map[whirl2]): + merged_owids.append([whirlpool_map[whirl1], whirlpool_map[whirl2]]) + if world.owShuffle[player] != 'vanilla': + custom_edges = world.customizer.get_owedges() + if custom_edges and player in custom_edges: + custom_edges = custom_edges[player] + if 'two-way' in custom_edges and len(custom_edges['two-way']) > 0: + custom_edges = custom_edges['two-way'] + for edgename1, edgename2 in custom_edges.items(): + if edgename1[-1] != '*' and edgename2[-1] != '*': + edge1 = world.get_owedge(edgename1, player) + edge2 = world.get_owedge(edgename2, player) + if [edge1.owIndex, edge2.owIndex] not in merged_owids and should_merge_group(edge1.owIndex, edge2.owIndex): + merged_owids.append([edge1.owIndex, edge2.owIndex]) + # Check if there are any groups that appear in both sets + if any(group in flipped_groups for group in nonflipped_groups): + raise GenerationException('Conflict found when flipping tiles') + return flipped_groups, nonflipped_groups, undefined_chance, merged_owids + +def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): + (flipped_groups, nonflipped_groups, undefined_chance) = forced_flips swapped_edges = list() group_parity = {} for group_data in groups: @@ -957,82 +1047,6 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): parity[5] -= 1 group_parity[group[0]] = parity - # customizer adjustments - undefined_chance = 50 - flipped_groups = list() - nonflipped_groups = list() - if world.customizer: - if not do_grouped: - custom_flips = world.customizer.get_owtileflips() - if custom_flips and player in custom_flips: - custom_flips = custom_flips[player] - forced_flips = list() - forced_nonflips = list() - if 'undefined_chance' in custom_flips: - undefined_chance = custom_flips['undefined_chance'] - if 'force_flip' in custom_flips: - forced_flips = custom_flips['force_flip'] - if 'force_no_flip' in custom_flips: - forced_nonflips = custom_flips['force_no_flip'] - - for group in groups: - if any(owid in group[0] for owid in forced_nonflips): - nonflipped_groups.append(group) - if any(owid in group[0] for owid in forced_flips): - flipped_groups.append(group) - - if undefined_chance == 0: - nonflipped_groups.extend([g for g in groups if g not in flipped_groups + nonflipped_groups]) - if world.owCrossed[player] != 'none': - # ensure any customized whirlpool connections don't end up crossworld - custom_whirlpools = world.customizer.get_whirlpools() - if custom_whirlpools and player in custom_whirlpools: - custom_whirlpools = custom_whirlpools[player] - if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0: - custom_whirlpools = custom_whirlpools['two-way'] - whirlpool_map = {name:owid for wc in default_whirlpool_connections for (owid, name, _) in wc} - for whirl1, whirl2 in custom_whirlpools.items(): - w1_owid = whirlpool_map[whirl1] - w2_owid = whirlpool_map[whirl2] - flip_together = (w1_owid & 0x40) == (w2_owid & 0x40) - w1_nonflipped = any(g for g in nonflipped_groups if w1_owid in g[0]) - w1_flipped = any(g for g in flipped_groups if w1_owid in g[0]) - if w1_nonflipped or w1_flipped: - group = next(g for g in groups if w2_owid in g[0]) - if w1_nonflipped == flip_together: - nonflipped_groups.append(group) - else: - flipped_groups.append(group) - else: - w2_nonflipped = any(g for g in nonflipped_groups if w2_owid in g[0]) - w2_flipped = any(g for g in flipped_groups if w2_owid in g[0]) - if w2_nonflipped or w2_flipped: - group = next(g for g in groups if w1_owid in g[0]) - if w2_nonflipped == flip_together: - nonflipped_groups.append(group) - else: - flipped_groups.append(group) - else: - w1_group = next(g for g in groups if w1_owid in g[0]) - w2_group = next(g for g in groups if w2_owid in g[0]) - if not flip_together: - if random.randint(0, 1) > 0: - nonflipped_groups.append(w1_group) - flipped_groups.append(w2_group) - else: - flipped_groups.append(w1_group) - nonflipped_groups.append(w2_group) - else: - if random.randint(1, 100) > undefined_chance: - nonflipped_groups.append(w1_group) - nonflipped_groups.append(w2_group) - else: - flipped_groups.append(w1_group) - flipped_groups.append(w2_group) - # Check if there are any groups that appear in both sets - if any(group in flipped_groups for group in nonflipped_groups): - raise GenerationException('Conflict found when flipping tiles') - attempts = 1 if 0 < undefined_chance < 100: # do roughly 1000 attempts at a full list @@ -1158,6 +1172,11 @@ def define_tile_groups(world, player, do_grouped): if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) + # customizer adjustments + flipped_groups, nonflipped_groups, undefined_chance, merged_owids = determine_forced_flips(world, groups, player) + for owids in merged_owids: + merge_groups([owids]) + tile_groups = [] for group in groups: if can_shuffle_group(group): @@ -1167,7 +1186,7 @@ def define_tile_groups(world, player, do_grouped): (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 + return tile_groups, flipped_groups, nonflipped_groups, undefined_chance def remove_reserved(world, groupedlist, connected_edges, player): new_grouping = {} From 535945477bb0bf4ffb819d167712aa043c0e74e8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 6 Sep 2023 17:46:29 -0500 Subject: [PATCH 021/123] Fixed some broken randomization --- OverworldShuffle.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 3e737ac5..27abd90a 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -700,9 +700,7 @@ def link_overworld(world, player): if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]): addSpot(0x30, True, True) # guarantee desert/mire access - subset = sector[1] - random.shuffle(subset) - sector[1] = subset + random.shuffle(sector[1]) f = 0 t = 0 while len(new_spots) < target_spots: From c0985dacdc29694657ac1ab31d254b5c116de17e Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Sep 2023 11:30:32 -0500 Subject: [PATCH 022/123] Adding Customizer support for grouping tiles together in Mixed OWR --- OverworldShuffle.py | 2 ++ docs/Customizer.md | 11 +++++++++++ docs/customizer_example.yaml | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 27abd90a..d0abce2d 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -916,6 +916,8 @@ def determine_forced_flips(world, tile_ow_groups, player): forced_flips = custom_flips['force_flip'] if 'force_no_flip' in custom_flips: forced_nonflips = custom_flips['force_no_flip'] + if 'force_together' in custom_flips: + merged_owids = list(custom_flips['force_together'].values()) for group in tile_ow_groups: if any(owid in group[0]+group[1] for owid in forced_nonflips): diff --git a/docs/Customizer.md b/docs/Customizer.md index d30a7d30..075e8602 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -149,6 +149,17 @@ force_no_flip: - 0x13 ``` +#### force_together + +`force_together` should be used for defining tiles you want to force to flip as a group. Each group must have some unique name with all the OW Screen IDs listed that are desired to be grouped together. The name of a group can be anything as long as it is valid yaml syntax. + +Here is an example which forces Links House and Sanctuary screens to flip together: +``` +someGroup: + - 0x2c + - 0x13 +``` + #### undefined_chance `undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped. diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index a9cebd90..8849f2e7 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -105,6 +105,10 @@ ow-tileflips: force_no_flip: - 0x2c - 0x18 + force_together: + someDescription: + - 0x16 + - 0x2b undefined_chance: 50 ow-flutespots: 1: From a3d7abf71ac21e25150f15e0407fadba71ef7e24 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Sep 2023 11:31:24 -0500 Subject: [PATCH 023/123] Small bugfix for OWR Customizer stuff --- OverworldShuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d0abce2d..f63fbcea 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -920,9 +920,9 @@ def determine_forced_flips(world, tile_ow_groups, player): merged_owids = list(custom_flips['force_together'].values()) for group in tile_ow_groups: - if any(owid in group[0]+group[1] for owid in forced_nonflips): + if any(owid in group for owid in forced_nonflips): nonflipped_groups.append(group) - if any(owid in group[0]+group[1] for owid in forced_flips): + if any(owid in group for owid in forced_flips): flipped_groups.append(group) if undefined_chance == 0: From e19622b566c78ffb136e8e9706752900308d6b80 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 8 Sep 2023 08:48:15 -0500 Subject: [PATCH 024/123] Fixed and reorganized OWR sections on spoilers --- BaseClasses.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b7e9d812..08ccdd93 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -3188,50 +3188,50 @@ class Spoiler(object): for fairy, bottle in self.bottles.items(): outfile.write(f'{fairy}: {bottle}\n') - if self.overworlds or self.whirlpools or self.maps: - outfile.write('\n\nOverworld:\n\n') - + if self.maps: # flute shuffle for player in range(1, self.world.players + 1): if ('flute', player) in self.maps: - outfile.write('Flute Spots:\n') + outfile.write('\n\nFlute Spots:\n\n') break for player in range(1, self.world.players + 1): if ('flute', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps[('flute', player)]['text'] + '\n\n') + outfile.write(self.maps[('flute', player)]['text']) # overworld tile flips for player in range(1, self.world.players + 1): if ('swaps', player) in self.maps: - outfile.write('OW Tile Flips:\n') + outfile.write('\n\nOW Tile Flips:\n\n') break for player in range(1, self.world.players + 1): if ('swaps', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps[('swaps', player)]['text'] + '\n\n') + outfile.write(self.maps[('swaps', player)]['text']) # crossed groups for player in range(1, self.world.players + 1): if ('groups', player) in self.maps: - outfile.write('OW Crossed Groups:\n') + outfile.write('\n\nOW Crossed Groups:\n\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.whirlpools: - # whirlpools - 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","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()])) + outfile.write(self.maps[('groups', player)]['text']) if self.overworlds: + outfile.write('\n\nOverworld Edges:\n\n') # 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()])) + if self.whirlpools: + outfile.write('\n\nWhirlpools:\n\n') + # whirlpools + 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","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()])) + if self.entrances: # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly outfile.write('\n\nEntrances:\n\n') From 78abcb4196e52408cdfa4f8de5b5a6b79a53c56f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 9 Sep 2023 16:28:09 -0500 Subject: [PATCH 025/123] Implemented remaining Crossed OWR Customizer support --- OverworldShuffle.py | 22 ++++++++++++++-------- docs/Customizer.md | 16 +++++++++++++--- docs/customizer_example.yaml | 1 + 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index f63fbcea..48d714eb 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -151,7 +151,7 @@ def link_overworld(world, player): # tile shuffle logging.getLogger('').debug('Flipping overworld tiles') if world.owMixed[player]: - tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, player, False) + tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, False, player) swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, (force_flipped, force_nonflipped, undefined_chance), player) update_world_regions(world, player) @@ -211,6 +211,7 @@ def link_overworld(world, player): force_noncrossed = set() count_crossed = 0 limited_crossed = -1 + undefined_chance = 50 if world.customizer: custom_crossed = world.customizer.get_owcrossed() if custom_crossed and player in custom_crossed: @@ -225,6 +226,8 @@ def link_overworld(world, player): force_noncrossed.add(edge.name) if 'limit_crossed' in custom_crossed: limited_crossed = custom_crossed['limit_crossed'] + if 'undefined_chance' in custom_crossed: + undefined_chance = custom_crossed['undefined_chance'] if limited_crossed > -1: # connect forced crossed non-parallel edges based on previously determined tile flips @@ -237,7 +240,7 @@ def link_overworld(world, player): # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask ow_crossed_tiles_mask = [[],[],[]] - tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, player, True) + tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, True, player) world.owcrossededges[player] = shuffle_tiles(world, tile_groups, ow_crossed_tiles_mask, True, (force_flipped, force_nonflipped, undefined_chance), player) ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])] @@ -310,13 +313,13 @@ def link_overworld(world, player): if not skip_back: if limited_crossed > -1: crossed_candidates.append(back_set) - elif random.randint(0, 1): + elif random.randint(1, 100) <= undefined_chance: world.owcrossededges[player].extend(back_set) count_crossed = count_crossed + 1 if not skip_forward: if limited_crossed > -1: crossed_candidates.append(forward_set) - elif random.randint(0, 1): + elif random.randint(1, 100) <= undefined_chance: world.owcrossededges[player].extend(forward_set) count_crossed = count_crossed + 1 assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges" @@ -899,13 +902,16 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): except KeyError: raise KeyError('No parallel edge for edge %s' % edgename2) -def determine_forced_flips(world, tile_ow_groups, player): +def determine_forced_flips(world, tile_ow_groups, do_grouped, player): undefined_chance = 50 flipped_groups = list() nonflipped_groups = list() merged_owids = list() if world.customizer: - custom_flips = world.customizer.get_owtileflips() + if do_grouped: + custom_flips = world.customizer.get_owcrossed() + else: + custom_flips = world.customizer.get_owtileflips() if custom_flips and player in custom_flips: custom_flips = custom_flips[player] forced_flips = list() @@ -1113,7 +1119,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): return swapped_edges -def define_tile_groups(world, player, do_grouped): +def define_tile_groups(world, do_grouped, player): groups = [[i, i + 0x40] for i in range(0x40)] def get_group(id): @@ -1173,7 +1179,7 @@ def define_tile_groups(world, player, do_grouped): merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) # customizer adjustments - flipped_groups, nonflipped_groups, undefined_chance, merged_owids = determine_forced_flips(world, groups, player) + flipped_groups, nonflipped_groups, undefined_chance, merged_owids = determine_forced_flips(world, groups, do_grouped, player) for owids in merged_owids: merge_groups([owids]) diff --git a/docs/Customizer.md b/docs/Customizer.md index 075e8602..872003c5 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -110,7 +110,7 @@ someDescription: ### ow-crossed -This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_crossed`, `force_noncrossed`, and `limit_crossed`. +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_crossed`, `force_noncrossed`, `limit_crossed`, and `undefined_chance`. There are also #### force_crossed / force_noncrossed @@ -118,7 +118,17 @@ This must be defined by player. Each player number should be listed with the app #### limit_crossed -`limit_crossed` should be used to limit how many overworld edge transitions end up connecting cross-world. This value can be set to any non-negative integer. A value of 0 means no edges will be cross-world, except for edges that are forced cross-world (either by the previous step or a result of some combination of OWR settings). This option only takes effect if `ow_crossed: unrestricted` is in the `settings` section. +`limit_crossed` should be used to limit how many overworld edge transitions end up connecting cross-world. This value can be set to any non-negative integer. A value of 0 means no edges will be cross-world, except for edges that are forced cross-world (either by the previous step or a result of some combination of OWR settings). This option only takes effect in `Unrestricted` Crossed setting. + +#### undefined_chance + +`undefined_chance` should be used to determine how to handle all the remaining overworld edge transitions that aren't explicitly defined in the earlier steps. This represents the percent chance an edge will be cross-world. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be cross-world. This option only takes effect under two mode combinations: +- 1: `Unrestricted` Crossed with a `Vanilla` OW Layout +- 2: `Grouped` Crossed + +#### (Grouped specific options) + +All four options available in the `ow-tileflips` section below are also available for `Grouped`, but must be defined under the `ow-crossed` section. In addition to the previous `undefined_chance`, the three other subsections are: `force_flip`, `force_no_flip`, and `force_together`. For more information about these sections, see the `ow-tileflips` section below. ### ow-whirlpools @@ -132,7 +142,7 @@ This must be defined by player. Each player number should be listed with the app ### ow-tileflips -This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined_chance`. +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_flip`, `force_no_flip`, `force_together`, and `undefined_chance`. #### force_flip / force_no_flip diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index 8849f2e7..75c997bc 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -94,6 +94,7 @@ ow-crossed: force_noncrossed: - Links House NE limit_crossed: 9 # emulates Limited Crossed + undefined_chance: 25 ow-whirlpools: 1: two-way: From 9adba09c54c28e4129fbea7c9d9758a1934de9d4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 9 Sep 2023 16:31:27 -0500 Subject: [PATCH 026/123] Adding additional info to error message during OWR Layout placement --- OverworldShuffle.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 48d714eb..43e9e70c 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -468,10 +468,12 @@ def link_overworld(world, player): (mode, wrld, dir, terrain, parallel, count, _) = key (forward_edge_sets, back_edge_sets) = groups[key] def remove_connected(): + deleted_edges = [] s = 0 while s < len(forward_edge_sets): forward_set = forward_edge_sets[s] if forward_set[0] in connected_edges: + deleted_edges.extend(forward_edge_sets[s]) del forward_edge_sets[s] continue s += 1 @@ -479,10 +481,14 @@ def link_overworld(world, player): while s < len(back_edge_sets): back_set = back_edge_sets[s] if back_set[0] in connected_edges: + deleted_edges.extend(back_edge_sets[s]) del back_edge_sets[s] continue s += 1 - assert len(forward_edge_sets) == len(back_edge_sets) + if len(forward_edge_sets) != len(back_edge_sets): + x=', '.join(deleted_edges) + x=0 + assert len(forward_edge_sets) == len(back_edge_sets), "OW edge pool is uneven due to prior connections: " + ', '.join(deleted_edges) remove_connected() random.shuffle(forward_edge_sets) From 268bc1b278ab853ec32f3c03089f2de862e45104 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 10 Sep 2023 14:43:15 -0500 Subject: [PATCH 027/123] Adding many OWR customizer mode yamls --- presets/world/owr_districtshuffle-full.yaml | 286 +++++++++++++++++ presets/world/owr_districtshuffle-pangea.yaml | 151 +++++++++ .../owr_districtshuffle-vanillaborders.yaml | 300 ++++++++++++++++++ presets/world/owr_quadrantshuffle-full.yaml | 298 +++++++++++++++++ .../owr_quadrantshuffle-vanillaborders.yaml | 275 ++++++++++++++++ presets/world/owr_shuffle-dark.yaml | 94 ++++++ presets/world/owr_shuffle-horizontal.yaml | 76 +++++ .../owr_shuffle-horizontalbycolumns.yaml | 233 ++++++++++++++ presets/world/owr_shuffle-largescreens.yaml | 210 ++++++++++++ presets/world/owr_shuffle-light.yaml | 84 +++++ .../world/owr_shuffle-separatemountain.yaml | 16 + presets/world/owr_shuffle-smallscreens.yaml | 58 ++++ presets/world/owr_shuffle-vanillaloop.yaml | 41 +++ presets/world/owr_shuffle-vertical.yaml | 87 +++++ presets/world/owr_shuffle-verticalbyrows.yaml | 227 +++++++++++++ presets/world/owr_vanilla-maxcrossed.yaml | 7 + .../world/owr_vanilla-mirroredsimilar.yaml | 148 +++++++++ presets/world/owr_vanilla.yaml | 190 +++++++++++ 18 files changed, 2781 insertions(+) create mode 100644 presets/world/owr_districtshuffle-full.yaml create mode 100644 presets/world/owr_districtshuffle-pangea.yaml create mode 100644 presets/world/owr_districtshuffle-vanillaborders.yaml create mode 100644 presets/world/owr_quadrantshuffle-full.yaml create mode 100644 presets/world/owr_quadrantshuffle-vanillaborders.yaml create mode 100644 presets/world/owr_shuffle-dark.yaml create mode 100644 presets/world/owr_shuffle-horizontal.yaml create mode 100644 presets/world/owr_shuffle-horizontalbycolumns.yaml create mode 100644 presets/world/owr_shuffle-largescreens.yaml create mode 100644 presets/world/owr_shuffle-light.yaml create mode 100644 presets/world/owr_shuffle-separatemountain.yaml create mode 100644 presets/world/owr_shuffle-smallscreens.yaml create mode 100644 presets/world/owr_shuffle-vanillaloop.yaml create mode 100644 presets/world/owr_shuffle-vertical.yaml create mode 100644 presets/world/owr_shuffle-verticalbyrows.yaml create mode 100644 presets/world/owr_vanilla-maxcrossed.yaml create mode 100644 presets/world/owr_vanilla-mirroredsimilar.yaml create mode 100644 presets/world/owr_vanilla.yaml diff --git a/presets/world/owr_districtshuffle-full.yaml b/presets/world/owr_districtshuffle-full.yaml new file mode 100644 index 00000000..6ce0ac7a --- /dev/null +++ b/presets/world/owr_districtshuffle-full.yaml @@ -0,0 +1,286 @@ +settings: + 1: + ow_shuffle: full + ow_keepsimilar: false +ow-edges: + 1: + groups: + NorthwestHyrule: + - Lost Woods NW + - Master Sword Meadow SC + - Lost Woods EN* + - Lumberjack WN* + - Lost Woods SW* + - Lost Woods Pass NW* + - Lost Woods SC* + - Lost Woods Pass NE* + - Lost Woods SE* + - Kakariko Fortune NE* + - Lumberjack SW* + - Mountain Pass NW* + - Mountain Pass SE* + - Kakariko Pond NE* + - Kakariko Fortune EN* + - Kakariko Pond WN* + - Kakariko Fortune ES* + - Kakariko Pond WS* + - Kakariko Pond EN* + - Sanctuary WN* + - Kakariko Pond ES* + - Sanctuary WS* + - Kakariko Pond SW* + - Forgotten Forest NW* + - Kakariko Pond SE* + - Forgotten Forest NE* + - Sanctuary EC* + - Graveyard WC* + - Graveyard EC* + - River Bend WC* + DeathMountain: + - West Death Mountain EN* + - East Death Mountain WN* + - West Death Mountain ES* + - East Death Mountain WS* + - East Death Mountain EN* + - Death Mountain TR Pegs WN* + EasternHyrule: + - Zora Waterfall NE + - Zoras Domain SW + - Zora Waterfall SE* + - Zora Approach NE* + - River Bend EN* + - Potion Shop WN* + - River Bend EC* + - Potion Shop WC* + - River Bend ES* + - Potion Shop WS* + - River Bend SC* + - Wooden Bridge NC* + - River Bend SE* + - Wooden Bridge NE* + - Potion Shop EN* + - Zora Approach WN* + - Potion Shop EC* + - Zora Approach WC* + - Wooden Bridge SW* + - Sand Dunes NW* + - Eastern Palace SW* + - Tree Line NW* + - Eastern Palace SE* + - Eastern Nook NE* + - Sand Dunes SC* + - Stone Bridge NC* + - Stone Bridge EN* + - Tree Line WN* + Kakariko: + - Kakariko ES* + - Blacksmith WS* + - Kakariko SE* + - Kakariko Suburb NE* + - Maze Race ES* + - Kakariko Suburb WS* + CentralHyrule: + - Hyrule Castle SW* + - Central Bonk Rocks NW* + - Hyrule Castle SE* + - Links House NE* + - Flute Boy SW* + - Flute Boy Approach NW* + - Flute Boy SC* + - Flute Boy Approach NC* + - Central Bonk Rocks EN* + - Links House WN* + - Central Bonk Rocks EC* + - Links House WC* + - Central Bonk Rocks ES* + - Links House WS* + - Central Bonk Rocks SW* + - C Whirlpool NW* + - Links House SC* + - Statues NC* + - Flute Boy Approach EC* + - C Whirlpool WC* + - C Whirlpool EN* + - Statues WN* + - C Whirlpool EC* + - Statues WC* + - C Whirlpool ES* + - Statues WS* + - C Whirlpool SC* + - Dam NC* + - Statues SC* + - South Pass NC* + - Dam EC* + - South Pass WC* + LakeHylia: + - Stone Bridge WC + - Hobo EC + - Stone Bridge EC* + - Tree Line WC* + - Stone Bridge SC* + - Lake Hylia NW* + - Tree Line SC* + - Lake Hylia NC* + - Lake Hylia WS* + - South Pass ES* + - Lake Hylia EC* + - Octoballoon WC* + - Lake Hylia ES* + - Octoballoon WS* + - Ice Cave SW* + - Octoballoon NW* + - Ice Cave SE* + - Octoballoon NE* + Desert: + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + NorthwestDarkWorld: + - Skull Woods EN* + - Dark Lumberjack WN* + - Skull Woods SW* + - Skull Woods Pass NW* + - Skull Woods SC* + - Skull Woods Pass NE* + - Skull Woods SE* + - Dark Fortune NE* + - Dark Lumberjack SW* + - Bumper Cave NW* + - Bumper Cave SE* + - Outcast Pond NE* + - Skull Woods Pass SW* + - Village of Outcasts NW* + - Skull Woods Pass SE* + - Village of Outcasts NC* + - Dark Fortune EN* + - Outcast Pond WN* + - Dark Fortune ES* + - Outcast Pond WS* + - Dark Fortune SC* + - Village of Outcasts NE* + - Outcast Pond EN* + - Dark Chapel WN* + - Outcast Pond ES* + - Dark Chapel WS* + - Outcast Pond SW* + - Shield Shop NW* + - Outcast Pond SE* + - Shield Shop NE* + - Dark Chapel EC* + - Dark Graveyard WC* + - Dark Graveyard EC* + - Qirn Jump WC* + - Village of Outcasts ES* + - Hammer Pegs WS* + DarkDeathMountain: + - West Dark Death Mountain EN* + - East Dark Death Mountain WN* + - West Dark Death Mountain ES* + - East Dark Death Mountain WS* + - East Dark Death Mountain EN* + - Turtle Rock WN* + EastDarkWorld: + - Catfish SE* + - Catfish Approach NE* + - Dark Witch EN* + - Catfish Approach WN* + - Dark Witch EC* + - Catfish Approach WC* + - Qirn Jump EN* + - Dark Witch WN* + - Qirn Jump EC* + - Dark Witch WC* + - Qirn Jump ES* + - Dark Witch WS* + - Qirn Jump SC* + - Broken Bridge NC* + - Qirn Jump SE* + - Broken Bridge NE* + - Broken Bridge SW* + - Dark Dunes NW* + - Pyramid ES* + - Dark Dunes WN* + - Dark Dunes SC* + - Hammer Bridge NC* + - Palace of Darkness SW* + - Dark Tree Line NW* + - Palace of Darkness SE* + - Palace of Darkness Nook NE* + - Hammer Bridge EN* + - Dark Tree Line WN* + SouthDarkWorld: + - Dig Game EC + - Frog WC + - Dig Game ES* + - Frog WS* + - Frog ES* + - Stumpy WS* + - Stumpy SW* + - Stumpy Approach NW* + - Stumpy SC* + - Stumpy Approach NC* + - Dark Bonk Rocks EN* + - Big Bomb Shop WN* + - Dark Bonk Rocks EC* + - Big Bomb Shop WC* + - Dark Bonk Rocks ES* + - Big Bomb Shop WS* + - Dark Bonk Rocks SW* + - Dark C Whirlpool NW* + - Big Bomb Shop SC* + - Hype Cave NC* + - Big Bomb Shop ES* + - Hammer Bridge WS* + - Hammer Bridge EC* + - Dark Tree Line WC* + - Hammer Bridge SC* + - Ice Lake NW* + - Dark Tree Line SC* + - Ice Lake NC* + - Stumpy Approach EC* + - Dark C Whirlpool WC* + - Dark C Whirlpool EN* + - Hype Cave WN* + - Dark C Whirlpool EC* + - Hype Cave WC* + - Dark C Whirlpool ES* + - Hype Cave WS* + - Dark C Whirlpool SC* + - Swamp NC* + - Hype Cave SC* + - Dark South Pass NC* + - Ice Lake WS* + - Dark South Pass ES* + - Ice Lake EC* + - Bomber Corner WC* + - Ice Lake ES* + - Bomber Corner WS* + - Shopping Mall SW* + - Bomber Corner NW* + - Shopping Mall SE* + - Bomber Corner NE* + - Swamp Nook EC* + - Swamp WC* + - Swamp Nook ES* + - Swamp WS* + - Swamp EC* + - Dark South Pass WC* +ow-tileflips: + 1: + force_no_flips: + - 0x30 + - 0x3a + force_together: + LostWoodsPedestal: + - 0x00 + - 0x80 + ZoraWaterfall: + - 0x0f + - 0x81 + MazeRaceSuburb: + - 0x28 + - 0x29 + HyruleCastleWest: + - 0x1a + - 0x1b \ No newline at end of file diff --git a/presets/world/owr_districtshuffle-pangea.yaml b/presets/world/owr_districtshuffle-pangea.yaml new file mode 100644 index 00000000..e4d1ff3c --- /dev/null +++ b/presets/world/owr_districtshuffle-pangea.yaml @@ -0,0 +1,151 @@ +settings: + 1: + ow_shuffle: full + ow_keepsimilar: false +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Mountain Pass SE*: Kakariko Pond NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Sanctuary EC*: Graveyard WC* + Graveyard EC*: River Bend WC* + + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Stone Bridge EN*: Tree Line WN* + + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Maze Race ES*: Kakariko Suburb WS* + + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Links House SC*: Statues NC* + Flute Boy Approach EC*: C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Statues SC*: South Pass NC* + Dam EC*: South Pass WC* + + Stone Bridge WC: Hobo EC + Stone Bridge EC*: Tree Line WC* + Stone Bridge SC*: Lake Hylia NW* + Tree Line SC*: Lake Hylia NC* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Dark Lumberjack SW*: Bumper Cave NW* + Bumper Cave SE*: Outcast Pond NE* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Dark Fortune SC*: Village of Outcasts NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Dark Chapel EC*: Dark Graveyard WC* + Dark Graveyard EC*: Qirn Jump WC* + Village of Outcasts ES*: Hammer Pegs WS* + + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Dark Death Mountain EN*: Turtle Rock WN* + + Catfish SE*: Catfish Approach NE* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Broken Bridge SW*: Dark Dunes NW* + Pyramid ES*: Dark Dunes WN* + Dark Dunes SC*: Hammer Bridge NC* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Hammer Bridge EN*: Dark Tree Line WN* + + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Frog ES*: Stumpy WS* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Hammer Bridge EC*: Dark Tree Line WC* + Hammer Bridge SC*: Ice Lake NW* + Dark Tree Line SC*: Ice Lake NC* + Stumpy Approach EC*: Dark C Whirlpool WC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Hype Cave SC*: Dark South Pass NC* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Swamp EC*: Dark South Pass WC* +ow-tileflips: + 1: + force_no_flips: + - 0x30 + - 0x3a + force_together: + HyruleCastleWest: + - 0x1a + - 0x1b \ No newline at end of file diff --git a/presets/world/owr_districtshuffle-vanillaborders.yaml b/presets/world/owr_districtshuffle-vanillaborders.yaml new file mode 100644 index 00000000..6e622648 --- /dev/null +++ b/presets/world/owr_districtshuffle-vanillaborders.yaml @@ -0,0 +1,300 @@ +settings: + 1: + ow_shuffle: full + ow_keepsimilar: false +ow-edges: + 1: + two-way: + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Kakariko Fortune SC*: Kakariko NE* + River Bend SW*: Wooden Bridge NW* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Kakariko Suburb ES*: Flute Boy WS* + Links House ES*: Stone Bridge WS* + Tree Line SE*: Lake Hylia NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Qirn Jump SW*: Broken Bridge NW* + Village of Outcasts SE*: Frog NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Dark Tree Line SE*: Ice Lake NE* + groups: + NorthwestHyrule: + - Lost Woods NW + - Master Sword Meadow SC + - Lost Woods EN* + - Lumberjack WN* + - Lost Woods SW* + - Lost Woods Pass NW* + - Lost Woods SC* + - Lost Woods Pass NE* + - Lost Woods SE* + - Kakariko Fortune NE* + - Lumberjack SW* + - Mountain Pass NW* + - Mountain Pass SE* + - Kakariko Pond NE* + - Kakariko Fortune EN* + - Kakariko Pond WN* + - Kakariko Fortune ES* + - Kakariko Pond WS* + - Kakariko Pond EN* + - Sanctuary WN* + - Kakariko Pond ES* + - Sanctuary WS* + - Kakariko Pond SW* + - Forgotten Forest NW* + - Kakariko Pond SE* + - Forgotten Forest NE* + - Sanctuary EC* + - Graveyard WC* + - Graveyard EC* + - River Bend WC* + DeathMountain: + - West Death Mountain EN* + - East Death Mountain WN* + - West Death Mountain ES* + - East Death Mountain WS* + - East Death Mountain EN* + - Death Mountain TR Pegs WN* + EasternHyrule: + - Zora Waterfall NE + - Zoras Domain SW + - Zora Waterfall SE* + - Zora Approach NE* + - River Bend EN* + - Potion Shop WN* + - River Bend EC* + - Potion Shop WC* + - River Bend ES* + - Potion Shop WS* + - River Bend SC* + - Wooden Bridge NC* + - River Bend SE* + - Wooden Bridge NE* + - Potion Shop EN* + - Zora Approach WN* + - Potion Shop EC* + - Zora Approach WC* + - Wooden Bridge SW* + - Sand Dunes NW* + - Eastern Palace SW* + - Tree Line NW* + - Eastern Palace SE* + - Eastern Nook NE* + - Sand Dunes SC* + - Stone Bridge NC* + - Stone Bridge EN* + - Tree Line WN* + Kakariko: + - Kakariko ES* + - Blacksmith WS* + - Kakariko SE* + - Kakariko Suburb NE* + - Maze Race ES* + - Kakariko Suburb WS* + CentralHyrule: + - Hyrule Castle SW* + - Central Bonk Rocks NW* + - Hyrule Castle SE* + - Links House NE* + - Flute Boy SW* + - Flute Boy Approach NW* + - Flute Boy SC* + - Flute Boy Approach NC* + - Central Bonk Rocks EN* + - Links House WN* + - Central Bonk Rocks EC* + - Links House WC* + - Central Bonk Rocks ES* + - Links House WS* + - Central Bonk Rocks SW* + - C Whirlpool NW* + - Links House SC* + - Statues NC* + - Flute Boy Approach EC* + - C Whirlpool WC* + - C Whirlpool EN* + - Statues WN* + - C Whirlpool EC* + - Statues WC* + - C Whirlpool ES* + - Statues WS* + - C Whirlpool SC* + - Dam NC* + - Statues SC* + - South Pass NC* + - Dam EC* + - South Pass WC* + LakeHylia: + - Stone Bridge WC + - Hobo EC + - Stone Bridge EC* + - Tree Line WC* + - Stone Bridge SC* + - Lake Hylia NW* + - Tree Line SC* + - Lake Hylia NC* + - Lake Hylia WS* + - South Pass ES* + - Lake Hylia EC* + - Octoballoon WC* + - Lake Hylia ES* + - Octoballoon WS* + - Ice Cave SW* + - Octoballoon NW* + - Ice Cave SE* + - Octoballoon NE* + Desert: + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + NorthwestDarkWorld: + - Skull Woods EN* + - Dark Lumberjack WN* + - Skull Woods SW* + - Skull Woods Pass NW* + - Skull Woods SC* + - Skull Woods Pass NE* + - Skull Woods SE* + - Dark Fortune NE* + - Dark Lumberjack SW* + - Bumper Cave NW* + - Bumper Cave SE* + - Outcast Pond NE* + - Skull Woods Pass SW* + - Village of Outcasts NW* + - Skull Woods Pass SE* + - Village of Outcasts NC* + - Dark Fortune EN* + - Outcast Pond WN* + - Dark Fortune ES* + - Outcast Pond WS* + - Dark Fortune SC* + - Village of Outcasts NE* + - Outcast Pond EN* + - Dark Chapel WN* + - Outcast Pond ES* + - Dark Chapel WS* + - Outcast Pond SW* + - Shield Shop NW* + - Outcast Pond SE* + - Shield Shop NE* + - Dark Chapel EC* + - Dark Graveyard WC* + - Dark Graveyard EC* + - Qirn Jump WC* + - Village of Outcasts ES* + - Hammer Pegs WS* + DarkDeathMountain: + - West Dark Death Mountain EN* + - East Dark Death Mountain WN* + - West Dark Death Mountain ES* + - East Dark Death Mountain WS* + - East Dark Death Mountain EN* + - Turtle Rock WN* + EastDarkWorld: + - Catfish SE* + - Catfish Approach NE* + - Dark Witch EN* + - Catfish Approach WN* + - Dark Witch EC* + - Catfish Approach WC* + - Qirn Jump EN* + - Dark Witch WN* + - Qirn Jump EC* + - Dark Witch WC* + - Qirn Jump ES* + - Dark Witch WS* + - Qirn Jump SC* + - Broken Bridge NC* + - Qirn Jump SE* + - Broken Bridge NE* + - Broken Bridge SW* + - Dark Dunes NW* + - Pyramid ES* + - Dark Dunes WN* + - Dark Dunes SC* + - Hammer Bridge NC* + - Palace of Darkness SW* + - Dark Tree Line NW* + - Palace of Darkness SE* + - Palace of Darkness Nook NE* + - Hammer Bridge EN* + - Dark Tree Line WN* + SouthDarkWorld: + - Dig Game EC + - Frog WC + - Dig Game ES* + - Frog WS* + - Frog ES* + - Stumpy WS* + - Stumpy SW* + - Stumpy Approach NW* + - Stumpy SC* + - Stumpy Approach NC* + - Dark Bonk Rocks EN* + - Big Bomb Shop WN* + - Dark Bonk Rocks EC* + - Big Bomb Shop WC* + - Dark Bonk Rocks ES* + - Big Bomb Shop WS* + - Dark Bonk Rocks SW* + - Dark C Whirlpool NW* + - Big Bomb Shop SC* + - Hype Cave NC* + - Big Bomb Shop ES* + - Hammer Bridge WS* + - Hammer Bridge EC* + - Dark Tree Line WC* + - Hammer Bridge SC* + - Ice Lake NW* + - Dark Tree Line SC* + - Ice Lake NC* + - Stumpy Approach EC* + - Dark C Whirlpool WC* + - Dark C Whirlpool EN* + - Hype Cave WN* + - Dark C Whirlpool EC* + - Hype Cave WC* + - Dark C Whirlpool ES* + - Hype Cave WS* + - Dark C Whirlpool SC* + - Swamp NC* + - Hype Cave SC* + - Dark South Pass NC* + - Ice Lake WS* + - Dark South Pass ES* + - Ice Lake EC* + - Bomber Corner WC* + - Ice Lake ES* + - Bomber Corner WS* + - Shopping Mall SW* + - Bomber Corner NW* + - Shopping Mall SE* + - Bomber Corner NE* + - Swamp Nook EC* + - Swamp WC* + - Swamp Nook ES* + - Swamp WS* + - Swamp EC* + - Dark South Pass WC* +ow-tileflips: + 1: + force_no_flips: + - 0x30 + - 0x3a + force_together: + LostWoodsPedestal: + - 0x00 + - 0x80 + ZoraWaterfall: + - 0x0f + - 0x81 + MazeRaceSuburb: + - 0x28 + - 0x29 \ No newline at end of file diff --git a/presets/world/owr_quadrantshuffle-full.yaml b/presets/world/owr_quadrantshuffle-full.yaml new file mode 100644 index 00000000..8538f709 --- /dev/null +++ b/presets/world/owr_quadrantshuffle-full.yaml @@ -0,0 +1,298 @@ +ow-edges: + 1: + groups: + northwest: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Dark Chapel WN + - Dark Chapel WS + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Master Sword Meadow SC + northeast: + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Graveyard EC + - Dark Graveyard EC + - River Bend WC + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Zoras Domain SW + southwest: + - Kakariko ES + - Kakariko SE + - Village of Outcasts ES + - Village of Outcasts SE + - Hyrule Castle SW + - Pyramid SW + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Flute Boy SW + - Flute Boy SC + - Stumpy WS + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks SW + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool SC + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Swamp NC + - Swamp WC + - Swamp WS + southeast: + - Hyrule Castle ES + - Hyrule Castle SE + - Pyramid ES + - Pyramid SE + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes WN + - Dark Dunes SC + - Links House NE + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge NC + - Hammer Bridge WS + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Statues NC + - Statues SC + - Hype Cave NC + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia NC + - Lake Hylia NE + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake NC + - Ice Lake NE + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - South Pass NC + - South Pass ES + - Dark South Pass NC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC + borders: + - Zora Whirlpool + - Kakariko Pond Whirlpool + - Sanctuary EC + - Dark Chapel EC + - Graveyard WC + - Dark Graveyard WC + - River Bend Whirlpool + - Qirn Jump Whirlpool + - Wooden Bridge SW + - Broken Bridge SW + - Sand Dunes NW + - Dark Dunes NW + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - C Whirlpool + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Lake Hylia Whirlpool + - Dam EC + - Swamp EC + - South Pass WC + - Dark South Pass WC + - Octoballoon Whirlpool + - Bomber Corner Whirlpool diff --git a/presets/world/owr_quadrantshuffle-vanillaborders.yaml b/presets/world/owr_quadrantshuffle-vanillaborders.yaml new file mode 100644 index 00000000..f446011c --- /dev/null +++ b/presets/world/owr_quadrantshuffle-vanillaborders.yaml @@ -0,0 +1,275 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + groups: + northwest: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Dark Chapel WN + - Dark Chapel WS + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Master Sword Meadow SC + northeast: + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Graveyard EC + - Dark Graveyard EC + - River Bend WC + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Zoras Domain SW + southwest: + - Kakariko ES + - Kakariko SE + - Village of Outcasts ES + - Village of Outcasts SE + - Hyrule Castle SW + - Pyramid SW + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Flute Boy SW + - Flute Boy SC + - Stumpy WS + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks SW + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool SC + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Swamp NC + - Swamp WC + - Swamp WS + southeast: + - Hyrule Castle ES + - Hyrule Castle SE + - Pyramid ES + - Pyramid SE + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes WN + - Dark Dunes SC + - Links House NE + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge NC + - Hammer Bridge WS + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Statues NC + - Statues SC + - Hype Cave NC + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia NC + - Lake Hylia NE + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake NC + - Ice Lake NE + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - South Pass NC + - South Pass ES + - Dark South Pass NC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC \ No newline at end of file diff --git a/presets/world/owr_shuffle-dark.yaml b/presets/world/owr_shuffle-dark.yaml new file mode 100644 index 00000000..3b27e6d2 --- /dev/null +++ b/presets/world/owr_shuffle-dark.yaml @@ -0,0 +1,94 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Lumberjack SW*: Mountain Pass NW* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + Mountain Pass SE*: Kakariko Pond NE* + Zora Waterfall NE*: Zoras Domain SW* + Zora Waterfall SE*: Zora Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Kakariko Fortune SC*: Kakariko NE* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Sanctuary EC*: Graveyard WC* + Graveyard EC*: River Bend WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Wooden Bridge SW*: Sand Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Kakariko Suburb ES*: Flute Boy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Stone Bridge SC*: Lake Hylia NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC*: C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Statues SC*: South Pass NC* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Dam EC*: South Pass WC* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool + Kakariko Pond Whirlpool: Octoballoon Whirlpool + River Bend Whirlpool: C Whirlpool +ow-tileflips: + 1: + force_no_flip: + - 0x0f + - 0x12 + - 0x15 + - 0x33 + - 0x35 + - 0x3f \ No newline at end of file diff --git a/presets/world/owr_shuffle-horizontal.yaml b/presets/world/owr_shuffle-horizontal.yaml new file mode 100644 index 00000000..9eba3118 --- /dev/null +++ b/presets/world/owr_shuffle-horizontal.yaml @@ -0,0 +1,76 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Big Bomb Shop SC*: Hype Cave NC* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + +ow-tileflips: + 1: + force_together: + Hobo: + - 0x2d + - 0x80 \ No newline at end of file diff --git a/presets/world/owr_shuffle-horizontalbycolumns.yaml b/presets/world/owr_shuffle-horizontalbycolumns.yaml new file mode 100644 index 00000000..117b81d5 --- /dev/null +++ b/presets/world/owr_shuffle-horizontalbycolumns.yaml @@ -0,0 +1,233 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Big Bomb Shop SC*: Hype Cave NC* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + groups: + Column1: + - Maze Race ES + - Kakariko Suburb WS + - Dig Game EC + - Frog WC + - Dig Game ES + - Frog WS + Column2: + - Lost Woods EN + - Lumberjack WN + - Skull Woods EN + - Dark Lumberjack WN + - Kakariko Fortune EN + - Kakariko Pond WN + - Kakariko Fortune ES + - Kakariko Pond WS + - Dark Fortune EN + - Outcast Pond WN + - Dark Fortune ES + - Outcast Pond WS + - Kakariko ES + - Blacksmith WS + - Village of Outcasts ES + - Hammer Pegs WS + - Kakariko Suburb ES + - Flute Boy WS + - Frog ES + - Stumpy WS + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + Column3: + - Kakariko Pond EN + - Sanctuary WN + - Kakariko Pond ES + - Sanctuary WS + - Outcast Pond EN + - Dark Chapel WN + - Outcast Pond ES + - Dark Chapel WS + - Forgotten Forest ES + - Hyrule Castle WN + - Flute Boy Approach EC + - C Whirlpool WC + - Stumpy Approach EC + - Dark C Whirlpool WC + - Desert Pass EC + - Dam WC + - Desert Pass ES + - Dam WS + - Swamp Nook EC + - Swamp WC + - Swamp Nook ES + - Swamp WS + Column4: + - Sanctuary EC + - Graveyard WC + - Dark Chapel EC + - Dark Graveyard WC + - Central Bonk Rocks EN + - Links House WN + - Central Bonk Rocks EC + - Links House WC + - Central Bonk Rocks ES + - Links House WS + - Dark Bonk Rocks EN + - Big Bomb Shop WN + - Dark Bonk Rocks EC + - Big Bomb Shop WC + - Dark Bonk Rocks ES + - Big Bomb Shop WS + - C Whirlpool EN + - Statues WN + - C Whirlpool EC + - Statues WC + - C Whirlpool ES + - Statues WS + - Dark C Whirlpool EN + - Hype Cave WN + - Dark C Whirlpool EC + - Hype Cave WC + - Dark C Whirlpool ES + - Hype Cave WS + - Dam EC + - South Pass WC + - Swamp EC + - Dark South Pass WC + Column5: + - West Death Mountain EN + - East Death Mountain WN + - West Death Mountain ES + - East Death Mountain WS + - West Dark Death Mountain EN + - East Dark Death Mountain WN + - West Dark Death Mountain ES + - East Dark Death Mountain WS + - Graveyard EC + - River Bend WC + - Dark Graveyard EC + - Qirn Jump WC + - Hyrule Castle ES + - Sand Dunes WN + - Pyramid ES + - Dark Dunes WN + - Links House ES + - Stone Bridge WS + - Big Bomb Shop ES + - Hammer Bridge WS + - Lake Hylia WS + - South Pass ES + - Ice Lake WS + - Dark South Pass ES + Column6: + - River Bend EN + - Potion Shop WN + - River Bend EC + - Potion Shop WC + - River Bend ES + - Potion Shop WS + - Qirn Jump EN + - Dark Witch WN + - Qirn Jump EC + - Dark Witch WC + - Qirn Jump ES + - Dark Witch WS + - Stone Bridge EN + - Tree Line WN + - Stone Bridge EC + - Tree Line WC + - Hammer Bridge EN + - Dark Tree Line WN + - Hammer Bridge EC + - Dark Tree Line WC + - Stone Bridge WC + - Hobo EC + Column7: + - East Death Mountain EN + - Death Mountain TR Pegs WN + - East Dark Death Mountain EN + - Turtle Rock WN + - Potion Shop EN + - Zora Approach WN + - Potion Shop EC + - Zora Approach WC + - Dark Witch EN + - Catfish Approach WN + - Dark Witch EC + - Catfish Approach WC + - Lake Hylia EC + - Octoballoon WC + - Lake Hylia ES + - Octoballoon WS + - Ice Lake EC + - Bomber Corner WC + - Ice Lake ES + - Bomber Corner WS +ow-tileflips: + 1: + force_together: + Hobo: + - 0x2d + - 0x80 \ No newline at end of file diff --git a/presets/world/owr_shuffle-largescreens.yaml b/presets/world/owr_shuffle-largescreens.yaml new file mode 100644 index 00000000..6c54658c --- /dev/null +++ b/presets/world/owr_shuffle-largescreens.yaml @@ -0,0 +1,210 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + groups: + 1: + - Lost Woods NW + - Master Sword Meadow SC + - Lost Woods EN + - Lumberjack WN + - Skull Woods EN + - Dark Lumberjack WN + - East Death Mountain EN + - Death Mountain TR Pegs WN + - East Dark Death Mountain EN + - Turtle Rock WN + - Lost Woods Pass SW + - Kakariko NW + - Lost Woods Pass SE + - Kakariko NC + - Skull Woods Pass SW + - Village of Outcasts NW + - Skull Woods Pass SE + - Village of Outcasts NC + - Kakariko Fortune SC + - Kakariko NE + - Dark Fortune SC + - Village of Outcasts NE + - Kakariko ES + - Blacksmith WS + - Village of Outcasts ES + - Hammer Pegs WS + - Hyrule Castle ES + - Sand Dunes WN + - Pyramid ES + - Dark Dunes WN + - Stone Bridge SC + - Lake Hylia NW + - Hammer Bridge SC + - Ice Lake NW + - Tree Line SC + - Lake Hylia NC + - Tree Line SE + - Lake Hylia NE + - Dark Tree Line SC + - Ice Lake NC + - Dark Tree Line SE + - Ice Lake NE + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + - Lake Hylia EC + - Octoballoon WC + - Lake Hylia ES + - Octoballoon WS + - Ice Lake EC + - Bomber Corner WC + - Ice Lake ES + - Bomber Corner WS + 2: + - Lost Woods SW + - Lost Woods Pass NW + - Lost Woods SC + - Lost Woods Pass NE + - Lost Woods SE + - Kakariko Fortune NE + - Skull Woods SW + - Skull Woods Pass NW + - Skull Woods SC + - Skull Woods Pass NE + - Skull Woods SE + - Dark Fortune NE + - Zora Waterfall NE + - Zoras Domain SW + - Kakariko SE + - Kakariko Suburb NE + - Village of Outcasts SE + - Frog NE + - Forgotten Forest ES + - Hyrule Castle WN + - Hyrule Castle SW + - Central Bonk Rocks NW + - Hyrule Castle SE + - Links House NE + - Pyramid SW + - Dark Bonk Rocks NW + - Pyramid SE + - Big Bomb Shop NE + - Eastern Palace SW + - Tree Line NW + - Eastern Palace SE + - Eastern Nook NE + - Palace of Darkness SW + - Dark Tree Line NW + - Palace of Darkness SE + - Palace of Darkness Nook NE + - Lake Hylia WS + - South Pass ES + - Ice Lake WS + - Dark South Pass ES + - West Death Mountain EN + - East Death Mountain WN + - West Death Mountain ES + - East Death Mountain WS + - West Dark Death Mountain EN + - East Dark Death Mountain WN + - West Dark Death Mountain ES + - East Dark Death Mountain WS +ow-tileflips: + 1: + force_together: + 1: + - 0x00 + - 0x80 + 2: + - 0x0f + - 0x81 \ No newline at end of file diff --git a/presets/world/owr_shuffle-light.yaml b/presets/world/owr_shuffle-light.yaml new file mode 100644 index 00000000..4feaa75e --- /dev/null +++ b/presets/world/owr_shuffle-light.yaml @@ -0,0 +1,84 @@ +ow-edges: + 1: + two-way: + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Dark Lumberjack SW*: Bumper Cave NW* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Dark Death Mountain EN*: Turtle Rock WN* + Bumper Cave SE*: Outcast Pond NE* + Catfish SE*: Catfish Approach NE* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Dark Fortune SC*: Village of Outcasts NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Dark Chapel EC*: Dark Graveyard WC* + Dark Graveyard EC*: Qirn Jump WC* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Broken Bridge SW*: Dark Dunes NW* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Dark Dunes SC*: Hammer Bridge NC* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Frog ES*: Stumpy WS* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Hammer Bridge SC*: Ice Lake NW* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Stumpy Approach EC*: Dark C Whirlpool WC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Hype Cave SC*: Dark South Pass NC* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Swamp EC*: Dark South Pass WC* +ow-whirlpools: + 1: + two-way: + Qirn Jump Whirlpool: Bomber Corner Whirlpool +ow-tileflips: + 1: + force_no_flip: + - 0x0f + - 0x12 + - 0x15 + - 0x33 + - 0x35 + - 0x3f \ No newline at end of file diff --git a/presets/world/owr_shuffle-separatemountain.yaml b/presets/world/owr_shuffle-separatemountain.yaml new file mode 100644 index 00000000..b1078849 --- /dev/null +++ b/presets/world/owr_shuffle-separatemountain.yaml @@ -0,0 +1,16 @@ +ow-edges: + 1: + groups: + mountain: + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN diff --git a/presets/world/owr_shuffle-smallscreens.yaml b/presets/world/owr_shuffle-smallscreens.yaml new file mode 100644 index 00000000..a12ad95d --- /dev/null +++ b/presets/world/owr_shuffle-smallscreens.yaml @@ -0,0 +1,58 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Zora Waterfall NE: Zoras Domain SW + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool \ No newline at end of file diff --git a/presets/world/owr_shuffle-vanillaloop.yaml b/presets/world/owr_shuffle-vanillaloop.yaml new file mode 100644 index 00000000..bc4223fb --- /dev/null +++ b/presets/world/owr_shuffle-vanillaloop.yaml @@ -0,0 +1,41 @@ +ow-edges: + 1: + two-way: + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Lost Woods Pass SW*: Kakariko NW* + Skull Woods Pass SW*: Village of Outcasts NW* + Kakariko Pond ES*: Sanctuary WS* + Outcast Pond ES*: Dark Chapel WS* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend SW*: Wooden Bridge NW* + Qirn Jump SW*: Broken Bridge NW* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Stumpy SW*: Stumpy Approach NW* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* diff --git a/presets/world/owr_shuffle-vertical.yaml b/presets/world/owr_shuffle-vertical.yaml new file mode 100644 index 00000000..222834c9 --- /dev/null +++ b/presets/world/owr_shuffle-vertical.yaml @@ -0,0 +1,87 @@ +ow-edges: + 1: + two-way: + Lost Woods EN*: Lumberjack WN* + Skull Woods EN*: Dark Lumberjack WN* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Kakariko ES*: Blacksmith WS* + Village of Outcasts ES*: Hammer Pegs WS* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Pyramid ES*: Dark Dunes WN* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* +ow-tileflips: + 1: + force_together: + 1: + - 0x00 + - 0x80 + 2: + - 0x0f + - 0x81 \ No newline at end of file diff --git a/presets/world/owr_shuffle-verticalbyrows.yaml b/presets/world/owr_shuffle-verticalbyrows.yaml new file mode 100644 index 00000000..0799a25b --- /dev/null +++ b/presets/world/owr_shuffle-verticalbyrows.yaml @@ -0,0 +1,227 @@ +ow-edges: + 1: + two-way: + Lost Woods EN*: Lumberjack WN* + Skull Woods EN*: Dark Lumberjack WN* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Kakariko ES*: Blacksmith WS* + Village of Outcasts ES*: Hammer Pegs WS* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Pyramid ES*: Dark Dunes WN* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + groups: + Row1/Special: + - Lost Woods NW + - Master Sword Meadow SC + - Lumberjack SW + - Mountain Pass NW + - Dark Lumberjack SW + - Bumper Cave NW + - Zora Waterfall NE + - Zoras Domain SW + Row2: + - Lost Woods SW + - Lost Woods Pass NW + - Lost Woods SC + - Lost Woods Pass NE + - Lost Woods SE + - Kakariko Fortune NE + - Skull Woods SW + - Skull Woods Pass NW + - Skull Woods SC + - Skull Woods Pass NE + - Skull Woods SE + - Dark Fortune NE + - Mountain Pass SE + - Kakariko Pond NE + - Bumper Cave SE + - Outcast Pond NE + - Zora Waterfall SE + - Zora Approach NE + - Catfish SE + - Catfish Approach NE + Row3: + - Lost Woods Pass SW + - Kakariko NW + - Lost Woods Pass SE + - Kakariko NC + - Skull Woods Pass SW + - Village of Outcasts NW + - Skull Woods Pass SE + - Village of Outcasts NC + - Kakariko Fortune SC + - Kakariko NE + - Dark Fortune SC + - Village of Outcasts NE + - Kakariko Pond SW + - Forgotten Forest NW + - Kakariko Pond SE + - Forgotten Forest NE + - Outcast Pond SW + - Shield Shop NW + - Outcast Pond SE + - Shield Shop NE + - River Bend SW + - Wooden Bridge NW + - River Bend SC + - Wooden Bridge NC + - River Bend SE + - Wooden Bridge NE + - Qirn Jump SW + - Broken Bridge NW + - Qirn Jump SC + - Broken Bridge NC + - Qirn Jump SE + - Broken Bridge NE + Row4: + - Wooden Bridge SW + - Sand Dunes NW + - Broken Bridge SW + - Dark Dunes NW + Row5: + - Kakariko SE + - Kakariko Suburb NE + - Village of Outcasts SE + - Frog NE + - Hyrule Castle SW + - Central Bonk Rocks NW + - Hyrule Castle SE + - Links House NE + - Pyramid SW + - Dark Bonk Rocks NW + - Pyramid SE + - Big Bomb Shop NE + - Sand Dunes SC + - Stone Bridge NC + - Dark Dunes SC + - Hammer Bridge NC + - Eastern Palace SW + - Tree Line NW + - Eastern Palace SE + - Eastern Nook NE + - Palace of Darkness SW + - Dark Tree Line NW + - Palace of Darkness SE + - Palace of Darkness Nook NE + Row6: + - Flute Boy SW + - Flute Boy Approach NW + - Flute Boy SC + - Flute Boy Approach NC + - Stumpy SW + - Stumpy Approach NW + - Stumpy SC + - Stumpy Approach NC + - Central Bonk Rocks SW + - C Whirlpool NW + - Dark Bonk Rocks SW + - Dark C Whirlpool NW + - Links House SC + - Statues NC + - Big Bomb Shop SC + - Hype Cave NC + - Stone Bridge SC + - Lake Hylia NW + - Hammer Bridge SC + - Ice Lake NW + - Tree Line SC + - Lake Hylia NC + - Tree Line SE + - Lake Hylia NE + - Dark Tree Line SC + - Ice Lake NC + - Dark Tree Line SE + - Ice Lake NE + Row7: + - C Whirlpool SC + - Dam NC + - Dark C Whirlpool SC + - Swamp NC + - Statues SC + - South Pass NC + - Hype Cave SC + - Dark South Pass NC + - Ice Cave SW + - Octoballoon NW + - Ice Cave SE + - Octoballoon NE + - Shopping Mall SW + - Bomber Corner NW + - Shopping Mall SE + - Bomber Corner NE +ow-tileflips: + 1: + force_together: + 1: + - 0x00 + - 0x80 + 2: + - 0x0f + - 0x81 \ No newline at end of file diff --git a/presets/world/owr_vanilla-maxcrossed.yaml b/presets/world/owr_vanilla-maxcrossed.yaml new file mode 100644 index 00000000..d77d78e5 --- /dev/null +++ b/presets/world/owr_vanilla-maxcrossed.yaml @@ -0,0 +1,7 @@ +settings: + 1: + ow_shuffle: vanilla + ow_crossed: unrestricted +ow-crossed: + 1: + undefined_chance: 100 \ No newline at end of file diff --git a/presets/world/owr_vanilla-mirroredsimilar.yaml b/presets/world/owr_vanilla-mirroredsimilar.yaml new file mode 100644 index 00000000..f9411f08 --- /dev/null +++ b/presets/world/owr_vanilla-mirroredsimilar.yaml @@ -0,0 +1,148 @@ +settings: + 1: + ow_shuffle: full + ow_terrain: true +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NE* + Lost Woods SC*: Lost Woods Pass NW* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NE* + Skull Woods SC*: Skull Woods Pass NW* + Skull Woods SE*: Dark Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NC* + Lost Woods Pass SE*: Kakariko NW* + Skull Woods Pass SW*: Village of Outcasts NC* + Skull Woods Pass SE*: Village of Outcasts NW* + Kakariko Fortune EN*: Kakariko Pond WS* + Kakariko Fortune ES*: Kakariko Pond WN* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune EN*: Outcast Pond WS* + Dark Fortune ES*: Outcast Pond WN* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko Pond EN*: Sanctuary WS* + Kakariko Pond ES*: Sanctuary WN* + Kakariko Pond SW*: Forgotten Forest NE* + Kakariko Pond SE*: Forgotten Forest NW* + Outcast Pond EN*: Dark Chapel WS* + Outcast Pond ES*: Dark Chapel WN* + Outcast Pond SW*: Shield Shop NE* + Outcast Pond SE*: Shield Shop NW* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WS* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WN* + River Bend SW*: Wooden Bridge NE* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NW* + Qirn Jump EN*: Dark Witch WS* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WN* + Qirn Jump SW*: Broken Bridge NE* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NW* + Potion Shop EN*: Zora Approach WC* + Potion Shop EC*: Zora Approach WN* + Dark Witch EN*: Catfish Approach WC* + Dark Witch EC*: Catfish Approach WN* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WS + Dig Game ES*: Frog WC* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NC* + Flute Boy SC*: Flute Boy Approach NW* + Stumpy SW*: Stumpy Approach NC* + Stumpy SC*: Stumpy Approach NW* + Central Bonk Rocks EN*: Links House WS* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WN* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WS* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WN* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WC* + Stone Bridge EC*: Tree Line WN* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge EN*: Dark Tree Line WC* + Hammer Bridge EC*: Dark Tree Line WN* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NE* + Tree Line SE*: Lake Hylia NC* + Dark Tree Line SC*: Ice Lake NE* + Dark Tree Line SE*: Ice Lake NC* + Desert EC: Desert Pass WS + Desert ES: Desert Pass WC + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WS* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WN* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WS* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WN* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WS* + Lake Hylia ES*: Octoballoon WC* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WS* + Ice Lake ES*: Bomber Corner WC* + Ice Cave SW*: Octoballoon NE* + Ice Cave SE*: Octoballoon NW* + Shopping Mall SW*: Bomber Corner NE* + Shopping Mall SE*: Bomber Corner NW* + Desert Pass EC*: Dam WS* + Desert Pass ES*: Dam WC* + Swamp Nook EC*: Swamp WS* + Swamp Nook ES*: Swamp WC* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC \ No newline at end of file diff --git a/presets/world/owr_vanilla.yaml b/presets/world/owr_vanilla.yaml new file mode 100644 index 00000000..d26bb8fa --- /dev/null +++ b/presets/world/owr_vanilla.yaml @@ -0,0 +1,190 @@ +ow-edges: + 1: + two-way: + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool + Kakariko Pond Whirlpool: Octoballoon Whirlpool + River Bend Whirlpool: C Whirlpool + Qirn Jump Whirlpool: Bomber Corner Whirlpool \ No newline at end of file From 9bb29d32d85e155ce787cc7adb4f579dfa791c76 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 10 Sep 2023 14:44:46 -0500 Subject: [PATCH 028/123] Changing TakeAny sword to count towards item collection --- ItemList.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ItemList.py b/ItemList.py index c6462f62..fcbffadf 100644 --- a/ItemList.py +++ b/ItemList.py @@ -543,7 +543,7 @@ def set_up_take_anys(world, player, skip_adjustments=False): else: if world.shopsanity[player] and not skip_adjustments: world.itempool.append(ItemFactory('Rupees (300)', player)) - old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0, create_location=world.shopsanity[player]) + old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0, create_location=True) take_any_type = ShopType.Shop if world.shopsanity[player] else ShopType.TakeAny for num in range(4): @@ -558,7 +558,7 @@ def set_up_take_anys(world, player, skip_adjustments=False): take_any.shop = Shop(take_any, room_id, take_any_type, 0xE3, True, not world.shopsanity[player], 33 + num*2) world.shops[player].append(take_any.shop) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0, create_location=world.shopsanity[player]) - take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0, create_location=world.shopsanity[player]) + take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0, create_location=True) if world.shopsanity[player] and not skip_adjustments: world.itempool.append(ItemFactory('Blue Potion', player)) world.itempool.append(ItemFactory('Boss Heart Container', player)) From 10241268356e263c458edf9893bd1805ab5fc7f2 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 10 Sep 2023 14:51:43 -0500 Subject: [PATCH 029/123] Moving Display Collection Rate option away from post-gen options --- resources/app/cli/lang/en.json | 2 +- resources/app/gui/lang/en.json | 2 +- resources/app/gui/randomize/gameoptions/widgets.json | 1 - resources/app/gui/randomize/item/widgets.json | 1 + source/classes/constants.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index ca4543d0..6d41b23d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -375,6 +375,7 @@ "Random: Take any caves can replace a random set of un-interesting caves. See documentation for full list", "Fixed: Take any caves will replace certain location. See documentation for full list" ], + "collection_rate": [ "Display collection rate (default: %(default)s)" ], "pseudoboots": [ " Start with pseudo boots that allow dashing but no item checks (default: %(default)s)"], "bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], @@ -425,7 +426,6 @@ "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], "msu_resume": [ "Enable MSU Resume (default: %(default)s)" ], - "collection_rate": [ "Display collection rate (default: %(default)s)" ], "create_rom": [ "Create an output rom file. (default: %(default)s)" ], "gui": [ "Launch the GUI. (default: %(default)s)" ], "jsonout": [ diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 95336905..66aefd98 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -191,7 +191,6 @@ "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", "randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects", "randomizer.gameoptions.msu_resume": "MSU Resume", - "randomizer.gameoptions.collection_rate": "Display Collection Rate", "randomizer.gameoptions.heartcolor": "Heart Color", "randomizer.gameoptions.heartcolor.red": "Red", @@ -249,6 +248,7 @@ "randomizer.item.race": "Generate \"Race\" ROM", "randomizer.item.retro": "Retro mode", "randomizer.item.pseudoboots": "Pseudoboots", + "randomizer.item.collection_rate": "Display Collection Rate", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", diff --git a/resources/app/gui/randomize/gameoptions/widgets.json b/resources/app/gui/randomize/gameoptions/widgets.json index 7b84b8a9..5d8faeba 100644 --- a/resources/app/gui/randomize/gameoptions/widgets.json +++ b/resources/app/gui/randomize/gameoptions/widgets.json @@ -2,7 +2,6 @@ "checkboxes": { "nobgm": { "type": "checkbox" }, "msu_resume": { "type": "checkbox" }, - "collection_rate": {"type": "checkbox"}, "quickswap": { "type": "checkbox" }, "reduce_flashing": { "type": "checkbox" }, "shuffle_sfx": { "type": "checkbox" } diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 21d95b94..652554ab 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -2,6 +2,7 @@ "checkboxes": { "hints": { "type": "checkbox" }, "pseudoboots": { "type": "checkbox" }, + "collection_rate": {"type": "checkbox"}, "race": { "type": "checkbox" } }, "leftItemFrame": { diff --git a/source/classes/constants.py b/source/classes/constants.py index 69831169..b41d03a6 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -57,6 +57,7 @@ SETTINGSTOPROCESS = { "item": { "hints": "hints", "pseudoboots": "pseudoboots", + 'collection_rate': 'collection_rate', "race": "race", "worldstate": "mode", @@ -139,7 +140,6 @@ SETTINGSTOPROCESS = { "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", 'msu_resume': 'msu_resume', - 'collection_rate': 'collection_rate', }, "generation": { "bps": "bps", From 3a06049bc4d69090bdba44da5dfad3d42c02c385 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 10 Sep 2023 15:40:40 -0500 Subject: [PATCH 030/123] Adding many OWR customizer mode yamls --- ...wr_vanilla-maxcrossed.yaml => owr_crossed-limited.yaml} | 3 +-- presets/world/owr_crossed-max.yaml | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) rename presets/world/{owr_vanilla-maxcrossed.yaml => owr_crossed-limited.yaml} (55%) create mode 100644 presets/world/owr_crossed-max.yaml diff --git a/presets/world/owr_vanilla-maxcrossed.yaml b/presets/world/owr_crossed-limited.yaml similarity index 55% rename from presets/world/owr_vanilla-maxcrossed.yaml rename to presets/world/owr_crossed-limited.yaml index d77d78e5..54d07a32 100644 --- a/presets/world/owr_vanilla-maxcrossed.yaml +++ b/presets/world/owr_crossed-limited.yaml @@ -1,7 +1,6 @@ settings: 1: - ow_shuffle: vanilla ow_crossed: unrestricted ow-crossed: 1: - undefined_chance: 100 \ No newline at end of file + limit_crossed: 9 \ No newline at end of file diff --git a/presets/world/owr_crossed-max.yaml b/presets/world/owr_crossed-max.yaml new file mode 100644 index 00000000..63409850 --- /dev/null +++ b/presets/world/owr_crossed-max.yaml @@ -0,0 +1,7 @@ +settings: + 1: + ow_crossed: unrestricted +ow-crossed: + 1: + limit_crossed: 9999 + undefined_chance: 100 \ No newline at end of file From a9b5dffcfa016c1cbcf03b85d111576d33710ec4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 10 Sep 2023 17:11:26 -0500 Subject: [PATCH 031/123] Version bump 0.3.3.0 --- CHANGELOG.md | 6 ++++++ OverworldShuffle.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f1c8fbb..041d2816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.3.0 +- Added Customizer support for all remaining OWR options +- Added several OWR preset yamls (many ideas are thanks to Catobat) +- Fixed issue with Mixed OWR + Flute Shuffle placing spots on large screens +- Removed Limited Crossed OWR and renamed Chaos Crossed to Unrestricted Crossed + ## 0.3.2.2 - Added Customizer support for Flute Shuffle (thanks Catobat) - Fixed bad Old Man rescue possibility in Swapped ER diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 43e9e70c..a4e54cca 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.2.2' +version_number = '0.3.3.0' # branch indicator is intentionally different across branches version_branch = '-u' From 2b3c635680f935d1731cfdd38f8f93024c2a57af Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 24 Sep 2023 01:17:55 -0500 Subject: [PATCH 032/123] Fixed issue with limit_crossed affecting other modes outside Unrestricted Crossed OWR --- OverworldShuffle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a4e54cca..354e6941 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -225,7 +225,8 @@ def link_overworld(world, player): edge = world.get_owedge(edgename, player) force_noncrossed.add(edge.name) if 'limit_crossed' in custom_crossed: - limited_crossed = custom_crossed['limit_crossed'] + if world.owCrossed[player] == 'unrestricted': + limited_crossed = custom_crossed['limit_crossed'] if 'undefined_chance' in custom_crossed: undefined_chance = custom_crossed['undefined_chance'] @@ -1266,6 +1267,7 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): if world.customizer: custom_crossed = world.customizer.get_owcrossed() limited_crossed = custom_crossed and (player in custom_crossed) and ('limit_crossed' in custom_crossed[player]) + limited_crossed = limited_crossed and world.owCrossed[player] == 'unrestricted' custom_edge_groups = world.customizer.get_owedges() if custom_edge_groups and player in custom_edge_groups: custom_edge_groups = custom_edge_groups[player] From 4f5779143dc19d03747a2f101153823bcfb383a8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 24 Sep 2023 01:19:56 -0500 Subject: [PATCH 033/123] Restructured forced crossed/noncrossed to share code --- OverworldShuffle.py | 124 +++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 75 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 354e6941..2caf6725 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -444,6 +444,23 @@ def link_overworld(world, player): connected_edge_cache = connected_edges.copy() groups_cache = copy.deepcopy(groups) while not valid_layout and tries > 0: + def remove_connected(forward_sets, back_sets): + deleted_edges = [] + def remove_from_sets(sets): + s = 0 + while s < len(sets): + if sets[s][0] in connected_edges: + deleted_edges.extend(sets[s]) + del sets[s] + continue + s += 1 + remove_from_sets(forward_sets) + remove_from_sets(back_sets) + if len(forward_sets) != len(back_sets): + x=', '.join(deleted_edges) + x=0 + assert len(forward_sets) == len(back_sets), "OW edge pool is uneven due to prior connections: " + ', '.join(deleted_edges) + def connect_set(forward_set, back_set, connected_edges): if forward_set is not None and back_set is not None: assert len(forward_set) == len(back_set) @@ -468,32 +485,10 @@ def link_overworld(world, player): for key in groupKeys: (mode, wrld, dir, terrain, parallel, count, _) = key (forward_edge_sets, back_edge_sets) = groups[key] - def remove_connected(): - deleted_edges = [] - s = 0 - while s < len(forward_edge_sets): - forward_set = forward_edge_sets[s] - if forward_set[0] in connected_edges: - deleted_edges.extend(forward_edge_sets[s]) - del forward_edge_sets[s] - continue - s += 1 - s = 0 - while s < len(back_edge_sets): - back_set = back_edge_sets[s] - if back_set[0] in connected_edges: - deleted_edges.extend(back_edge_sets[s]) - del back_edge_sets[s] - continue - s += 1 - if len(forward_edge_sets) != len(back_edge_sets): - x=', '.join(deleted_edges) - x=0 - assert len(forward_edge_sets) == len(back_edge_sets), "OW edge pool is uneven due to prior connections: " + ', '.join(deleted_edges) - remove_connected() random.shuffle(forward_edge_sets) random.shuffle(back_edge_sets) + if wrld is None and len(force_crossed) + len(force_noncrossed) > 0: # divide forward/back sets into LW/DW forward_lw_sets, forward_dw_sets = [], [] @@ -501,24 +496,20 @@ def link_overworld(world, player): forward_parallel_lw_sets, forward_parallel_dw_sets = [], [] back_parallel_lw_sets, back_parallel_dw_sets = [], [] + def add_edgeset_to_worldsets(edge_set, sets, parallel_sets): + sets.append(edge_set) + if parallel == IsParallel.Yes: + parallel_sets.append([parallel_links_new[e] for e in edge_set]) for edge_set in forward_edge_sets: if world.get_owedge(edge_set[0], player).is_lw(world): - forward_lw_sets.append(edge_set) - if parallel == IsParallel.Yes: - forward_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set]) + add_edgeset_to_worldsets(edge_set, forward_lw_sets, forward_parallel_lw_sets) else: - forward_dw_sets.append(edge_set) - if parallel == IsParallel.Yes: - forward_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set]) + add_edgeset_to_worldsets(edge_set, forward_dw_sets, forward_parallel_dw_sets) for edge_set in back_edge_sets: if world.get_owedge(edge_set[0], player).is_lw(world): - back_lw_sets.append(edge_set) - if parallel == IsParallel.Yes: - back_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set]) + add_edgeset_to_worldsets(edge_set, back_lw_sets, back_parallel_lw_sets) else: - back_dw_sets.append(edge_set) - if parallel == IsParallel.Yes: - back_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set]) + add_edgeset_to_worldsets(edge_set, back_dw_sets, back_parallel_dw_sets) crossed_sets = [] noncrossed_sets = [] @@ -527,7 +518,7 @@ def link_overworld(world, player): affected_edges = set(sets[i]+(parallel_sets[i] if parallel == IsParallel.Yes else [])) if sets[i] not in crossed_sets and len(set.intersection(set(force_crossed), affected_edges)) > 0: crossed_sets.append(sets[i]) - if sets not in noncrossed_sets and len(set.intersection(set(force_noncrossed), affected_edges)) > 0: + if sets[i] not in noncrossed_sets and len(set.intersection(set(force_noncrossed), affected_edges)) > 0: noncrossed_sets.append(sets[i]) if sets[i] in crossed_sets and sets[i] in noncrossed_sets: raise GenerationException('Conflict in force crossed/non-crossed definition') @@ -537,50 +528,33 @@ def link_overworld(world, player): add_to_crossed_sets(back_dw_sets, back_parallel_dw_sets) # random connect forced crossed/noncrossed - c = 0 - while c < len(noncrossed_sets): - if noncrossed_sets[c] in forward_edge_sets: - forward_set = noncrossed_sets[c] - if forward_set in forward_lw_sets: - back_set = next(s for s in back_lw_sets if s in back_edge_sets and s not in crossed_sets) + def connect_forced(forced_sets, is_crossed, opposite_sets=[]): + c = 0 + while c < len(forced_sets): + if forced_sets[c] in forward_edge_sets: + forward_set = forced_sets[c] + if (forward_set in forward_lw_sets) != is_crossed: + back_set = next(s for s in back_lw_sets if s in back_edge_sets and s not in opposite_sets) + else: + back_set = next(s for s in back_dw_sets if s in back_edge_sets and s not in opposite_sets) + elif forced_sets[c] in back_edge_sets: + back_set = forced_sets[c] + if (back_set in back_lw_sets) != is_crossed: + forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets and s not in opposite_sets) + else: + forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets and s not in opposite_sets) else: - back_set = next(s for s in back_dw_sets if s in back_edge_sets and s not in crossed_sets) - elif noncrossed_sets[c] in back_edge_sets: - back_set = noncrossed_sets[c] - if back_set in back_lw_sets: - forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets and s not in crossed_sets) - else: - forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets and s not in crossed_sets) - else: + c = c + 1 + continue + connect_set(forward_set, back_set, connected_edges) + remove_connected(forward_edge_sets, back_edge_sets) c = c + 1 - continue - connect_set(forward_set, back_set, connected_edges) - remove_connected() - c = c + 1 - c = 0 - while c < len(crossed_sets): - if crossed_sets[c] in forward_edge_sets: - forward_set = crossed_sets[c] - if forward_set in forward_lw_sets: - back_set = next(s for s in back_dw_sets if s in back_edge_sets) - else: - back_set = next(s for s in back_lw_sets if s in back_edge_sets) - elif crossed_sets[c] in back_edge_sets: - back_set = crossed_sets[c] - if back_set in back_lw_sets: - forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets) - else: - forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets) - else: - c = c + 1 - continue - connect_set(forward_set, back_set, connected_edges) - remove_connected() - c = c + 1 + connect_forced(noncrossed_sets, False, crossed_sets) + connect_forced(crossed_sets, True) while len(forward_edge_sets) > 0 and len(back_edge_sets) > 0: connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges) - remove_connected() + remove_connected(forward_edge_sets, back_edge_sets) assert len(connected_edges) == len(default_connections) * 2, connected_edges world.owsectors[player] = build_sectors(world, player) From 9c04140134f797a38a3abc7f33e127c596b5870d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 24 Sep 2023 01:21:13 -0500 Subject: [PATCH 034/123] Remove some commented out code --- OverworldShuffle.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 2caf6725..ca31e3c3 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1459,18 +1459,6 @@ def build_sectors(world, player): else: sectors2.append(explored_regions) sectors[s] = sectors2 - - #TODO: Keep largest LW sector for Links House consideration, keep sector containing WDM for Old Man consideration - # sector_entrances = list() - # for sector in sectors: - # entrances = list() - # for s2 in sector: - # for region_name in s2: - # region = world.get_region(region_name, player) - # for exit in region.exits: - # if exit.spot_type == 'Entrance' and exit.name in entrance_pool: - # entrances.append(exit.name) - # sector_entrances.append(entrances) return sectors From 32c55cc0a5c9e70c32b7774df1ad685718ac0399 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 25 Sep 2023 19:19:03 -0500 Subject: [PATCH 035/123] Converting flute data to owid-based rather than by owslot --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 27a1a38f..8837e1de 100644 --- a/Rom.py +++ b/Rom.py @@ -694,7 +694,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): offset = 0 data = flute_data[owid] - if world.is_tile_swapped(data[1], player): + if world.is_tile_swapped(owid, player): offset = 0x40 write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid From d8b4419b041f25f1960bc37ee6fddc02850d60fa Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 25 Sep 2023 19:19:03 -0500 Subject: [PATCH 036/123] Converting flute data to owid-based rather than by owslot --- OverworldShuffle.py | 2 +- Rom.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index ca31e3c3..20ad2d9e 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -2523,7 +2523,7 @@ isolated_regions = [ ] flute_data = { - #Slot LW Region DW Region OWID VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX + #OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX 0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290), 0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550), 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), diff --git a/Rom.py b/Rom.py index 27a1a38f..8837e1de 100644 --- a/Rom.py +++ b/Rom.py @@ -694,7 +694,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): offset = 0 data = flute_data[owid] - if world.is_tile_swapped(data[1], player): + if world.is_tile_swapped(owid, player): offset = 0x40 write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid From 27ed92cfcb60108ff4b80c160c4d0d5c905a2abf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 26 Sep 2023 13:03:18 -0500 Subject: [PATCH 037/123] Restructured forced crossed/noncrossed to share code --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 20ad2d9e..76b2e1e9 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -485,7 +485,7 @@ def link_overworld(world, player): for key in groupKeys: (mode, wrld, dir, terrain, parallel, count, _) = key (forward_edge_sets, back_edge_sets) = groups[key] - remove_connected() + remove_connected(forward_edge_sets, back_edge_sets) random.shuffle(forward_edge_sets) random.shuffle(back_edge_sets) From e722a1129f3b704787d873d6540db89e6e6190de Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Sep 2023 10:31:02 -0600 Subject: [PATCH 038/123] Rule fix for zelda to throne room --- Rules.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Rules.py b/Rules.py index 0b390f3d..4140469c 100644 --- a/Rules.py +++ b/Rules.py @@ -1322,13 +1322,15 @@ def standard_rules(world, player): add_rule(ent, lambda state: standard_escape_rule(state)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) - set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player)) set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player)) def check_rule_list(state, r_list): return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) rule_list, debug_path = find_rules_for_zelda_delivery(world, player) - set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) + set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), + lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) + set_rule(world.get_location('Zelda Drop Off', player), + lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest']: add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player)) From dc96c53a95bacde8b761307c77be4bc80268cbf1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 21 Aug 2023 08:25:17 -0600 Subject: [PATCH 039/123] Fix for Ganonhunt and various algorithms --- source/item/FillUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 595e86c9..8b5225b0 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -357,7 +357,7 @@ def determine_major_items(world, player): major_item_set.add('Single Arrow') if world.keyshuffle[player] == 'universal': major_item_set.add('Small Key (Universal)') - if world.goal[player] == 'triforcehunt': + if world.goal[player] in {'triforcehunt', 'ganonhunt'}: major_item_set.add('Triforce Piece') if world.bombbag[player]: major_item_set.add('Bomb Upgrade (+10)') From 329367e04a58a3dd2d0f2e8c32d2f4a1dfa7a1a0 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 14 Sep 2023 11:09:58 -0600 Subject: [PATCH 040/123] Account for vanilla BK doors in Stitcher --- source/dungeon/DungeonStitcher.py | 90 ++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 504fc03b..84139157 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -72,11 +72,13 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] doors_to_connect, idx = {}, 0 all_regions = set() + bk_special = False for sector in builder.sectors: for door in sector.outstanding_doors: doors_to_connect[door.name] = door, idx idx += 1 all_regions.update(sector.regions) + bk_special |= check_for_special(sector.regions) finished = False # flag if standard and this is hyrule castle paths = determine_paths_for_dungeon(world, player, all_regions, name) @@ -95,9 +97,9 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon if hash_code not in hash_code_set: hash_code_set.add(hash_code) explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect, - world, player) + bk_special, world, player) if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions, - paths, entrance_regions, world, player): + paths, entrance_regions, bk_special, world, player): finished = True else: proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect, @@ -229,21 +231,24 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se return proposed_map, hash_code -def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player): +def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, bk_special, world, player): start = ExplorationState(dungeon=name) + bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special + start.big_key_special = bk_special original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map, - all_regions, valid_doors, world, player) + all_regions, valid_doors, bk_relevant, world, player) return original_state def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions, - paths, entrance_regions, world, player): + paths, entrance_regions, bk_special, world, player): all_visited = set() all_visited.update(exploration_state.visited_blue) all_visited.update(exploration_state.visited_orange) if len(all_regions.difference(all_visited)) > 0: return False - if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player): + if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, + bk_special, world, player): return False return True @@ -266,7 +271,7 @@ def check_for_special(regions): return False -def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player): +def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, bk_special, world, player): for path in paths: if type(path) is tuple: target = path[1] @@ -278,12 +283,13 @@ def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, propose else: target = path start_regions = entrance_regions - if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player): + if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, + bk_special, world, player): return False return True -def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player): +def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, bk_special, world, player): target_regions = set() if type(target) is not list: for region in all_regions: @@ -296,8 +302,10 @@ def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_re target_regions.add(region) start = ExplorationState(dungeon=name) + bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special + start.big_key_special = bk_special original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions, - valid_doors, world, player) + valid_doors, bk_relevant, world, player) for exp_door in original_state.unattached_doors: if not exp_door.door.blocked or exp_door.door.trapFlag != 0: @@ -531,7 +539,7 @@ class ExplorationState(object): self.crystal = exp_door.crystal return exp_door - def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False): + def visit_region(self, region, key_region=None, key_checks=False, bk_relevant=False): if region.type != RegionType.Dungeon: self.crystal = CrystalBarrier.Orange if self.crystal == CrystalBarrier.Either: @@ -552,8 +560,14 @@ class ExplorationState(object): self.ttl_locations += 1 if location not in self.found_locations: self.found_locations.append(location) - if not bk_flag: - self.bk_found.add(location) + if bk_relevant: + if self.big_key_special: + if special_big_key_found(self): + self.bk_found.add(location) + self.re_add_big_key_doors() + else: + self.bk_found.add(location) + self.re_add_big_key_doors() if location.name in dungeon_events and location.name not in self.events: if self.flooded_key_check(location): self.perform_event(location.name, key_region) @@ -574,6 +588,14 @@ class ExplorationState(object): return True return False + def re_add_big_key_doors(self): + self.big_key_opened = True + queue = collections.deque(self.big_doors) + while len(queue) > 0: + exp_door = queue.popleft() + self.avail_doors.append(exp_door) + self.big_doors.remove(exp_door) + def perform_event(self, location_name, key_region): self.events.add(location_name) queue = collections.deque(self.event_doors) @@ -640,7 +662,7 @@ class ExplorationState(object): self.append_door_to_list(door, self.avail_doors, flag) # same as above but traps are ignored, and flag is not used - def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player): + def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, bk_relevant, world, player): for door in get_doors(world, region, player): if door in proposed_map and door.name in valid_doors: self.visited_doors.add(door) @@ -654,14 +676,18 @@ class ExplorationState(object): other = self.find_door_in_list(door, self.unattached_doors) if self.crystal != other.crystal: other.crystal = CrystalBarrier.Either - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, - self.event_doors): + elif (door.req_event is not None and door.req_event not in self.events + and not self.in_door_list(door, self.event_doors)): self.append_door_to_list(door, self.event_doors) + elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player)) + and not self.big_key_opened): + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) # same as above but traps are checked for - def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player): + def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, bk_relevant, world, player): for door in get_doors(world, region, player): if door in proposed_map and door.name in valid_doors: self.visited_doors.add(door) @@ -675,9 +701,13 @@ class ExplorationState(object): other = self.find_door_in_list(door, self.unattached_doors) if self.crystal != other.crystal: other.crystal = CrystalBarrier.Either - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, - self.event_doors): + elif (door.req_event is not None and door.req_event not in self.events + and not self.in_door_list(door, self.event_doors)): self.append_door_to_list(door, self.event_doors) + elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player)) + and not self.big_key_opened): + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) @@ -863,16 +893,22 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg return local_state -def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player): +# bk_relevant means the big key doors need to be checks +def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, bk_relevant, + world, player): local_state = state.copy() for region in search_regions: - local_state.visit_region(region) + local_state.visit_region(region, bk_relevant=bk_relevant) if world.trap_door_mode[player] == 'vanilla': - local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, bk_relevant, world, player) else: - local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, bk_relevant, world, player) while len(local_state.avail_doors) > 0: explorable_door = local_state.next_avail_door() + if explorable_door.door.bigKey: + if bk_relevant and (not special_big_key_found(local_state) if local_state.big_key_special + else local_state.count_locations_exclude_specials(world, player) == 0): + continue if explorable_door.door in proposed_map: connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region else: @@ -880,11 +916,13 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi if connect_region is not None: if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player) and not local_state.visited(connect_region)): - local_state.visit_region(connect_region) + local_state.visit_region(connect_region, bk_relevant=bk_relevant) if world.trap_door_mode[player] == 'vanilla': - local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, + bk_relevant, world, player) else: - local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, + bk_relevant, world, player) return local_state From fd749553a97ffacbc329bf363a40119def629a93 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 28 Sep 2023 22:05:31 -0500 Subject: [PATCH 041/123] New solution for mirror portal disabling in Crossed OWR --- Rom.py | 3 ++- data/base2current.bps | Bin 107722 -> 107648 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 8837e1de..de4d9cbc 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'da111397d4118054e5ab4b9375cfb9e4' +RANDOMIZERBASEHASH = '86a1b18573f57f5035a6937463d5d2cc' class JsonRom(object): @@ -1403,6 +1403,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier + rom.write_byte(0x02AF79, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) rom.write_byte(0x03A943, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) rom.write_byte(0x03A96D, 0xF0 if world.mode[player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) rom.write_byte(0x03A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) diff --git a/data/base2current.bps b/data/base2current.bps index eaa221120b9a2369d26459c3f834b8664ce2f261..b8fe1dc27a4202adcb7a7bd05c87e9f74a961d59 100644 GIT binary patch delta 16292 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 OI-WePXK(x+&;JL%ipTr_ From 7a16ea4fe11a0afa36b52ba160ed246cbf513d89 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 29 Sep 2023 09:33:33 -0600 Subject: [PATCH 042/123] Rule fix for zelda to throne room Minor fix to standard generation and spoilers --- DoorShuffle.py | 10 +++++++++- Main.py | 2 +- RELEASENOTES.md | 4 ++++ Rom.py | 2 +- data/base2current.bps | Bin 94175 -> 94199 bytes test/customizer/zelda_escape.yaml | 14 ++++++++++++++ 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 test/customizer/zelda_escape.yaml diff --git a/DoorShuffle.py b/DoorShuffle.py index aa988351..b8c78f9f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -177,6 +177,7 @@ def create_door_spoiler(world, player): queue = deque(world.dungeon_layouts[player].values()) while len(queue) > 0: builder = queue.popleft() + std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' and world.shuffle[player] == 'vanilla' done = set() start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic reg_queue = deque(start_regions) @@ -205,11 +206,15 @@ def create_door_spoiler(world, player): logger.warning('This is a bug during door spoiler') elif not isinstance(door_b, Region): logger.warning('Door not connected: %s', door_a.name) - if connect and connect.type == RegionType.Dungeon and connect not in visited: + if valid_connection(connect, std_flag, world, player) and connect not in visited: visited.add(connect) reg_queue.append(connect) +def valid_connection(region, std_flag, world, player): + return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player] or + (std_flag and region.name == 'Hyrule Castle Ledge')) + def vanilla_key_logic(world, player): builders = [] world.dungeon_layouts[player] = {} @@ -3328,6 +3333,9 @@ def find_inaccessible_regions(world, player): ledge = world.get_region('Hyrule Castle Ledge', player) if any(x for x in ledge.exits if x.connected_region.name == 'Agahnims Tower Portal'): world.inaccessible_regions[player].append('Hyrule Castle Ledge') + # this should be considered as part of the inaccessible regions, dungeonssimple? + if world.mode[player] == 'standard' and world.shuffle[player] == 'vanilla': + world.inaccessible_regions[player].append('Hyrule Castle Ledge') logger = logging.getLogger('') logger.debug('Inaccessible Regions:') for r in world.inaccessible_regions[player]: diff --git a/Main.py b/Main.py index 259d7a4b..24fa0080 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.20' +version_number = '1.2.0.21' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 336c604e..cae2bc56 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,10 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.21u + * Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory + * Small fix for Tavern Shuffle (thanks Catobat) + * Several small generation fixes * 1.2.0.20u * New generation feature that allows Spiral Stair to link to themselves (thank Catobat) * Added logic for trap doors that could be opened using existing room triggers diff --git a/Rom.py b/Rom.py index 5dc3652a..9351970c 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '61662913cc0cb12fb870d794937d88d9' +RANDOMIZERBASEHASH = '9b6e57f6e9d92934ce14276afd715849' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 23872f82d3be21e2fabd52ac809761a819691b9f..643e68f74b79b1aa1e7712e5e48e71831c6eedc5 100644 GIT binary patch delta 5989 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 delta 5929 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 E50@W(RR910 diff --git a/test/customizer/zelda_escape.yaml b/test/customizer/zelda_escape.yaml new file mode 100644 index 00000000..e779bbac --- /dev/null +++ b/test/customizer/zelda_escape.yaml @@ -0,0 +1,14 @@ +meta: + players: 1 +settings: + 1: + door_shuffle: crossed + intensity: 3 + mode: standard + pottery: keys + dropshuffle: 'on' +doors: + 1: + doors: + Hyrule Dungeon Cellblock Up Stairs: + dest: Ice Hammer Block Down Stairs From 9d6e2c770d3e6b6d7bb066b8e5192b859e5f9d09 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 1 Oct 2023 06:13:36 -0500 Subject: [PATCH 043/123] New solution for mirror portal disabling in Crossed OWR Map check correctly displays mirror portal sprite --- Rom.py | 2 +- asm/owrando.asm | 113 +++++++++++++++++++----------------------- data/base2current.bps | Bin 107648 -> 107671 bytes 3 files changed, 52 insertions(+), 63 deletions(-) diff --git a/Rom.py b/Rom.py index de4d9cbc..a99e98c7 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '86a1b18573f57f5035a6937463d5d2cc' +RANDOMIZERBASEHASH = '52317b2dd4fb303887f26ecc40a4cae3' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index d74718dd..adc7635e 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -56,16 +56,17 @@ Link_ResetSwimmingState: ; mirror hooks -org $02FBAB -JSL OWMirrorSpriteRestore : NOP +org $0283DC ; override world check when spawning mirror portal sprite in Crossed OWR +jsl.l OWLightWorldOrCrossed org $05AF75 Sprite_6C_MirrorPortal: -jsl OWPreserveMirrorSprite : nop #2 ; LDA $7EF3CA : BNE $05AFDF +jsl OWMirrorSpriteDisable ; LDA $7EF3CA +org $05AF88 +jsl OWMirrorSpriteSkipDraw : NOP ; LDA.w $0FC6 : CMP.b #$03 org $05AFDF Sprite_6C_MirrorPortal_missing_mirror: -JML OWMirrorSpriteDelete : NOP ; STZ $0DD0,X : BRA $05AFF1 -org $0ABFBF -JSL OWMirrorSpriteOnMap : BRA + : NOP #6 : + +org $0ABFB6 +jsl OWMirrorSpriteOnMap : NOP ; LDA.w $008A : CMP.b #$40 ; whirlpool shuffle cross world change org $02b3bd @@ -100,10 +101,6 @@ jsl OWOldManSpeed ;org $09c957 ; <- 4c957 ;dw #$cb5f ; matches value on Central Bonk Rocks screen -; override world check when spawning mirror portal sprite in Crossed OWR -org $0283dc -jsl.l OWLightWorldOrCrossed - ; override world check when viewing overworld (incl. title screen portion) org $0aba6c ; < ? - Bank0a.asm:474 () jsl.l OWMapWorldCheck16 : nop @@ -172,6 +169,11 @@ plb : rtl nop #3 + +; follower hooks +;org $8689D9 +;SpritePrep_BombShoppe: +;JML BombShoppe_ConditionalSpawn : NOP + ;Code org $aa8800 OWTransitionDirection: @@ -257,66 +259,42 @@ OWDestroyItemSprites: DEX : BPL .nextSprite PLX : 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 + JSL OWWorldCheck + CMP.b #$40 ; part of what we wrote over + RTL } -OWPreserveMirrorSprite: +OWMirrorSpriteDisable: { - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla ; if OW Crossed, skip world check and continue - lda.b $10 : cmp.b #$0f : beq .vanilla ; if performing mirror superbunny - rtl + LDA.b $10 : CMP.b #$0F : BNE + ; avoid rare freeze during mirror superbunny + PLA : PLA : PLA : JML Sprite_6C_MirrorPortal_missing_mirror + + + + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla + lda.l InvertedMode : beq + + lda.b #$40 + + rtl .vanilla - lda.l InvertedMode : beq + - lda.l CurrentWorld : beq .deleteMirror - rtl - + lda.l CurrentWorld : bne .deleteMirror - rtl - - .deleteMirror - 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.w $1acf : ora.b #$40 : sta.w $1acf - + rts -} -OWMirrorSpriteBonk: -{ - jsr.w OWMirrorSpriteMove - lda.b #$2c : jml 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.l InvertedMode : beq + - lda.l CurrentWorld : beq .return - bra .restorePortal - + lda.l CurrentWorld : bne .return - - .restorePortal - lda.w $1acf : and.b #$0f : sta.w $1acf - - .return - rep #$30 : lda.w $04AC ; what we wrote over + lda.l CurrentWorld ; what we wrote over rtl } +OWMirrorSpriteSkipDraw: +{ + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla + lda.l InvertedMode : beq + + lda.l CurrentWorld : eor.b #$40 + bra ++ + + lda.l CurrentWorld : ++ beq .vanilla + stz.w $0D90,x ; disables collision + sec : rtl + + .vanilla + LDA.w $0FC6 : CMP.b #$03 ; what we wrote over + RTL +} OWLightWorldOrCrossed: { lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq ++ @@ -563,6 +541,7 @@ OWBonkDrops: INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 1,S : PHA : BNE + ; S = Collected, FlagBitmask, X (row + 2) LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx + ; JSLSpriteSFX_QueueSFX3WithPan + LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + @@ -894,6 +873,7 @@ OWNewDestination: ++ lda $84 : !add 1,s : sta $84 : pla : pla .adjustMainAxis + ;LDA $84 : SEC : SBC #$0400 : AND #$0F80 : ASL : XBA : STA $88 ; vram LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 @@ -956,6 +936,7 @@ OWNewDestination: sep #$30 : lda $04 : and #$3f : !add OWOppSlotOffset,y : asl : sta $700 ; crossed OW shuffle and terrain + ;lda $8a : JSR OWDetermineScreensPaletteSet : STX $04 ldx $05 : ldy $08 : jsr OWWorldTerrainUpdate ldx $8a : lda $05 : sta $8a : stx $05 ; $05 is prev screen id, $8a is dest screen @@ -1180,6 +1161,14 @@ OWEndScrollTransition: RTL } +; BombShoppe_ConditionalSpawn: +; { +; nop +; INC.w $0BA0,X : LDA.b #$B5 ; what we wrote over +; JML SpritePrep_BombShoppe+5 +; nop#20 +; } + ;Data org $aaa000 OWEdgeOffsets: diff --git a/data/base2current.bps b/data/base2current.bps index b8fe1dc27a4202adcb7a7bd05c87e9f74a961d59..3c56274a00f1b9d319f2082083e17de09264ec86 100644 GIT binary patch delta 489 zcmVP`fsrDWh^p#XfwQp>PBH=t zih}_{mjOZnE==|B`fDOUh^omzYo!p$56}l_XlPvDuLZ9IfT}41@V^Rr+Lul@rhyrk zZ+n)OQ3s%bfR}q+1%Rmqo{$YVYpuo)$pfEkS{;C=t>u?-LID~oj}(BJsQ`}#P=9Nd zR(GVK&;(q)w+ik60+*Sk@C>E$$w1HssR5I!uRoW!LIGg`_Lmt$0b~JmmwrP58!Hft zkBfbQ$*|A`sd-HBipfII2O?Ol22Avd$u!Ufso6~Mipe$51*zIh@R!6x0XP94m;FNl zJ^>$>JVXIp0YI0TL;*%MD-!~Mg(Cr<6b%A^(W3*Z=)C~DNa(!)yYL0dXiU6}%4nOS zg_4Y;gvFMO$u!Ugso5Azl9v=k0cIXkBDt4bZIQEdGp3PD8;$z#mw9snfQd{SjbDHS zbeEV#0WJd{m2sEJMFBPf5J#5*Mgiss8gK)Eflr@Ox9>&)9RmplD;1ugtNd8 zPBH@giGvVAmk>e$E<%a#`fHP6bAXdl5Z<|5->(I)1AwY20`MrD#t)Ye8I6`v2cUs~ zmV94?p$Mr1jm8h2&<(p6JC2t^H>Rg=H>MGQmx@9G8Z086=>n+$j|NbGYnDzHpP|qM zT)npn?f?RpVmPLy@C>E$$w1HssR5I!uOpYvLIGg`=$9}<0c1yJi&~qnLGpen0!YvW zsT++i0j~hOs_>UPZjXfmi;s(afyuDY1*v&V;)=;a&<7$|tp-fyipez41*zFg;)=;N z&;_a5OyZZ@LjgDe5tkH10X_i{ms3OmTmdAPutWhy3J((kfQ2IgpLh(GMFA8Su8hiP zo1%q;jH86bh>XcJ&<3g57)*qhD@6fj9ycPnmt1X;vsg2xkxUJZ`tX-sa{_>gObv}+ zfM|1 Date: Mon, 2 Oct 2023 21:52:34 -0500 Subject: [PATCH 044/123] Some fixes to OWR district shuffle yamls --- presets/world/owr_districtshuffle-full.yaml | 2 -- presets/world/owr_districtshuffle-vanillaborders.yaml | 2 -- 2 files changed, 4 deletions(-) diff --git a/presets/world/owr_districtshuffle-full.yaml b/presets/world/owr_districtshuffle-full.yaml index 6ce0ac7a..e9e62916 100644 --- a/presets/world/owr_districtshuffle-full.yaml +++ b/presets/world/owr_districtshuffle-full.yaml @@ -121,8 +121,6 @@ ow-edges: - Lake Hylia NW* - Tree Line SC* - Lake Hylia NC* - - Lake Hylia WS* - - South Pass ES* - Lake Hylia EC* - Octoballoon WC* - Lake Hylia ES* diff --git a/presets/world/owr_districtshuffle-vanillaborders.yaml b/presets/world/owr_districtshuffle-vanillaborders.yaml index 6e622648..016dd9da 100644 --- a/presets/world/owr_districtshuffle-vanillaborders.yaml +++ b/presets/world/owr_districtshuffle-vanillaborders.yaml @@ -138,8 +138,6 @@ ow-edges: - Lake Hylia NW* - Tree Line SC* - Lake Hylia NC* - - Lake Hylia WS* - - South Pass ES* - Lake Hylia EC* - Octoballoon WC* - Lake Hylia ES* From 346d91fe375e0c87bbcc119409bba0f5861def07 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 5 Oct 2023 00:43:12 -0500 Subject: [PATCH 045/123] More OWR yamls and some adjustments to existing ones --- presets/world/owr_flute-nearvanilla.yaml | 31 + .../world/owr_quadrantshuffle-diagonal.yaml | 308 ++++++++++ presets/world/owr_quadrantshuffle-full.yaml | 573 +++++++++--------- .../owr_quadrantshuffle-vanillaborders.yaml | 492 +++++++-------- presets/world/owr_ringshuffle-borders.yaml | 214 +++++++ presets/world/owr_ringshuffle-full.yaml | 313 ++++++++++ presets/world/owr_ringshuffle-interiors.yaml | 258 ++++++++ presets/world/owr_shuffle-largescreens.yaml | 4 +- .../world/owr_shuffle-separatemountain.yaml | 24 +- presets/world/owr_shuffle-splitsimilar.yaml | 123 ++++ .../owr_shuffle-splitsimilarterrain.yaml | 175 ++++++ presets/world/owr_shuffle-vertical.yaml | 4 +- presets/world/owr_shuffle-verticalbyrows.yaml | 4 +- 13 files changed, 1969 insertions(+), 554 deletions(-) create mode 100644 presets/world/owr_flute-nearvanilla.yaml create mode 100644 presets/world/owr_quadrantshuffle-diagonal.yaml create mode 100644 presets/world/owr_ringshuffle-borders.yaml create mode 100644 presets/world/owr_ringshuffle-full.yaml create mode 100644 presets/world/owr_ringshuffle-interiors.yaml create mode 100644 presets/world/owr_shuffle-splitsimilar.yaml create mode 100644 presets/world/owr_shuffle-splitsimilarterrain.yaml diff --git a/presets/world/owr_flute-nearvanilla.yaml b/presets/world/owr_flute-nearvanilla.yaml new file mode 100644 index 00000000..5941d344 --- /dev/null +++ b/presets/world/owr_flute-nearvanilla.yaml @@ -0,0 +1,31 @@ +settings: + 1: + ow_fluteshuffle: balanced +ow-flutespots: + 1: + forbid: + - 0x00 # Lost Woods + - 0x02 # Lumberjack + - 0x07 # Death Mountain TR Pegs + - 0x0A # Mountain Pass + - 0x0F # Zora Waterfall + - 0x10 # Lost Woods Pass + - 0x12 # Kakariko Pond + - 0x13 # Sanctuary + - 0x14 # Graveyard + - 0x17 # Zora Approach + - 0x1A # Forgotten Forest + - 0x1D # Wooden Bridge + - 0x22 # Blacksmith + - 0x25 # Sand Dunes + - 0x28 # Maze Race + - 0x29 # Kakariko Suburb + - 0x2A # Flute Boy + - 0x2B # Central Bonk Rocks + - 0x2D # Stone Bridge + - 0x2E # Tree Line + - 0x32 # Flute Boy Approach + - 0x34 # Statues + - 0x37 # Ice Cave + - 0x3A # Desert Pass + - 0x3C # South Pass diff --git a/presets/world/owr_quadrantshuffle-diagonal.yaml b/presets/world/owr_quadrantshuffle-diagonal.yaml new file mode 100644 index 00000000..010d9e3c --- /dev/null +++ b/presets/world/owr_quadrantshuffle-diagonal.yaml @@ -0,0 +1,308 @@ +ow-edges: + 1: + groups: + north: + - Lost Woods NW + - Lost Woods EN + - Skull Woods EN + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Kakariko Pond NE + - Kakariko Pond EN + - Kakariko Pond ES + - Outcast Pond NE + - Outcast Pond EN + - Outcast Pond ES + - Sanctuary WN + - Sanctuary WS + - Sanctuary EC + - Dark Chapel WN + - Dark Chapel WS + - Dark Chapel EC + - Graveyard WC + - Graveyard EC + - Dark Graveyard WC + - Dark Graveyard EC + - River Bend WC + - Qirn Jump WC + - Master Sword Meadow SC + east: + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Hyrule Castle ES + - Pyramid ES + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Wooden Bridge SW + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Broken Bridge SW + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes NW + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes NW + - Dark Dunes WN + - Dark Dunes SC + - Stone Bridge NC + - Stone Bridge EN + - Stone Bridge EC + - Hammer Bridge NC + - Hammer Bridge EN + - Hammer Bridge EC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Lake Hylia NC + - Lake Hylia NE + - Ice Lake NC + - Ice Lake NE + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - Octoballoon NW + - Octoballoon NE + - Bomber Corner NW + - Bomber Corner NE + - Zoras Domain SW + south: + - Hyrule Castle SW + - Hyrule Castle SE + - Pyramid SW + - Pyramid SE + - Flute Boy SW + - Flute Boy SC + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Dark Bonk Rocks SW + - Links House NE + - Links House WN + - Links House WC + - Links House WS + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge SC + - Hammer Bridge WS + - Hammer Bridge SC + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Dark C Whirlpool SC + - Statues NC + - Statues WN + - Statues WC + - Statues WS + - Statues SC + - Hype Cave NC + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Dam EC + - Swamp NC + - Swamp WC + - Swamp WS + - Swamp EC + - South Pass NC + - South Pass WC + - South Pass ES + - Dark South Pass NC + - Dark South Pass WC + - Dark South Pass ES + - Octoballoon WC + - Octoballoon WS + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC + west: + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond SW + - Outcast Pond SE + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Kakariko ES + - Kakariko SE + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Village of Outcasts ES + - Village of Outcasts SE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Stumpy WS +ow-tileflips: + 1: + force_together: + PedHobo: + - 0x00 + - 0x2d + - 0x80 + Zora: + - 0x0f + - 0x81 + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_quadrantshuffle-full.yaml b/presets/world/owr_quadrantshuffle-full.yaml index 8538f709..6dabad7c 100644 --- a/presets/world/owr_quadrantshuffle-full.yaml +++ b/presets/world/owr_quadrantshuffle-full.yaml @@ -2,297 +2,290 @@ ow-edges: 1: groups: northwest: - - Lost Woods NW - - Lost Woods EN - - Lost Woods SW - - Lost Woods SC - - Lost Woods SE - - Skull Woods EN - - Skull Woods SW - - Skull Woods SC - - Skull Woods SE - - Lumberjack WN - - Lumberjack SW - - Dark Lumberjack WN - - Dark Lumberjack SW - - Mountain Pass NW - - Mountain Pass SE - - Bumper Cave NW - - Bumper Cave SE - - Lost Woods Pass NW - - Lost Woods Pass NE - - Lost Woods Pass SW - - Lost Woods Pass SE - - Skull Woods Pass NW - - Skull Woods Pass NE - - Skull Woods Pass SW - - Skull Woods Pass SE - - Kakariko Fortune NE - - Kakariko Fortune EN - - Kakariko Fortune ES - - Kakariko Fortune SC - - Dark Fortune NE - - Dark Fortune EN - - Dark Fortune ES - - Dark Fortune SC - - Kakariko Pond NE - - Kakariko Pond WN - - Kakariko Pond WS - - Kakariko Pond EN - - Kakariko Pond ES - - Kakariko Pond SW - - Kakariko Pond SE - - Outcast Pond NE - - Outcast Pond WN - - Outcast Pond WS - - Outcast Pond EN - - Outcast Pond ES - - Outcast Pond SW - - Outcast Pond SE - - Sanctuary WN - - Sanctuary WS - - Kakariko NW - - Kakariko NC - - Kakariko NE - - Dark Chapel WN - - Dark Chapel WS - - Village of Outcasts NW - - Village of Outcasts NC - - Village of Outcasts NE - - Forgotten Forest NW - - Forgotten Forest NE - - Forgotten Forest ES - - Shield Shop NW - - Shield Shop NE - - Hyrule Castle WN - - Master Sword Meadow SC + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Dark Chapel WN + - Dark Chapel WS + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Master Sword Meadow SC northeast: - - West Death Mountain EN - - West Death Mountain ES - - West Dark Death Mountain EN - - West Dark Death Mountain ES - - East Death Mountain WN - - East Death Mountain WS - - East Death Mountain EN - - East Dark Death Mountain WN - - East Dark Death Mountain WS - - East Dark Death Mountain EN - - Death Mountain TR Pegs WN - - Turtle Rock WN - - Zora Waterfall NE - - Zora Waterfall SE - - Catfish SE - - Graveyard EC - - Dark Graveyard EC - - River Bend WC - - River Bend EN - - River Bend EC - - River Bend ES - - River Bend SW - - River Bend SC - - River Bend SE - - Qirn Jump WC - - Qirn Jump EN - - Qirn Jump EC - - Qirn Jump ES - - Qirn Jump SW - - Qirn Jump SC - - Qirn Jump SE - - Potion Shop WN - - Potion Shop WC - - Potion Shop WS - - Potion Shop EN - - Potion Shop EC - - Dark Witch WN - - Dark Witch WC - - Dark Witch WS - - Dark Witch EN - - Dark Witch EC - - Zora Approach NE - - Zora Approach WN - - Zora Approach WC - - Catfish Approach NE - - Catfish Approach WN - - Catfish Approach WC - - Wooden Bridge NW - - Wooden Bridge NC - - Wooden Bridge NE - - Broken Bridge NW - - Broken Bridge NC - - Broken Bridge NE - - Zoras Domain SW + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Graveyard EC + - Dark Graveyard EC + - River Bend WC + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Zoras Domain SW southwest: - - Kakariko ES - - Kakariko SE - - Village of Outcasts ES - - Village of Outcasts SE - - Hyrule Castle SW - - Pyramid SW - - Blacksmith WS - - Hammer Pegs WS - - Maze Race ES - - Dig Game EC - - Dig Game ES - - Kakariko Suburb NE - - Kakariko Suburb WS - - Kakariko Suburb ES - - Frog NE - - Frog WC - - Frog WS - - Frog ES - - Flute Boy WS - - Flute Boy SW - - Flute Boy SC - - Stumpy WS - - Stumpy SW - - Stumpy SC - - Central Bonk Rocks NW - - Central Bonk Rocks SW - - Dark Bonk Rocks NW - - Dark Bonk Rocks SW - - Desert EC - - Desert ES - - Flute Boy Approach NW - - Flute Boy Approach NC - - Flute Boy Approach EC - - Stumpy Approach NW - - Stumpy Approach NC - - Stumpy Approach EC - - C Whirlpool NW - - C Whirlpool WC - - C Whirlpool SC - - Dark C Whirlpool NW - - Dark C Whirlpool WC - - Dark C Whirlpool SC - - Desert Pass WC - - Desert Pass WS - - Desert Pass EC - - Desert Pass ES - - Swamp Nook EC - - Swamp Nook ES - - Dam NC - - Dam WC - - Dam WS - - Swamp NC - - Swamp WC - - Swamp WS + - Kakariko ES + - Kakariko SE + - Village of Outcasts ES + - Village of Outcasts SE + - Hyrule Castle SW + - Pyramid SW + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Flute Boy SW + - Flute Boy SC + - Stumpy WS + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks SW + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool SC + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Swamp NC + - Swamp WC + - Swamp WS southeast: - - Hyrule Castle ES - - Hyrule Castle SE - - Pyramid ES - - Pyramid SE - - Eastern Palace SW - - Eastern Palace SE - - Palace of Darkness SW - - Palace of Darkness SE - - Sand Dunes WN - - Sand Dunes SC - - Dark Dunes WN - - Dark Dunes SC - - Links House NE - - Links House SC - - Links House ES - - Big Bomb Shop NE - - Big Bomb Shop SC - - Big Bomb Shop ES - - Stone Bridge NC - - Stone Bridge WC - - Stone Bridge WS - - Stone Bridge EN - - Stone Bridge EC - - Stone Bridge SC - - Hammer Bridge NC - - Hammer Bridge WS - - Hammer Bridge EN - - Hammer Bridge EC - - Hammer Bridge SC - - Tree Line NW - - Tree Line WN - - Tree Line WC - - Tree Line SC - - Tree Line SE - - Dark Tree Line NW - - Dark Tree Line WN - - Dark Tree Line WC - - Dark Tree Line SC - - Dark Tree Line SE - - Eastern Nook NE - - Palace of Darkness Nook NE - - Statues NC - - Statues SC - - Hype Cave NC - - Hype Cave SC - - Lake Hylia NW - - Lake Hylia NC - - Lake Hylia NE - - Lake Hylia WS - - Lake Hylia EC - - Lake Hylia ES - - Ice Lake NW - - Ice Lake NC - - Ice Lake NE - - Ice Lake WS - - Ice Lake EC - - Ice Lake ES - - Ice Cave SW - - Ice Cave SE - - Shopping Mall SW - - Shopping Mall SE - - South Pass NC - - South Pass ES - - Dark South Pass NC - - Dark South Pass ES - - Octoballoon NW - - Octoballoon NE - - Octoballoon WC - - Octoballoon WS - - Bomber Corner NW - - Bomber Corner NE - - Bomber Corner WC - - Bomber Corner WS - - Hobo EC + - Hyrule Castle ES + - Hyrule Castle SE + - Pyramid ES + - Pyramid SE + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes WN + - Dark Dunes SC + - Links House NE + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge NC + - Hammer Bridge WS + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Statues NC + - Statues SC + - Hype Cave NC + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia NC + - Lake Hylia NE + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake NC + - Ice Lake NE + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - South Pass NC + - South Pass ES + - Dark South Pass NC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC borders: - - Zora Whirlpool - - Kakariko Pond Whirlpool - - Sanctuary EC - - Dark Chapel EC - - Graveyard WC - - Dark Graveyard WC - - River Bend Whirlpool - - Qirn Jump Whirlpool - - Wooden Bridge SW - - Broken Bridge SW - - Sand Dunes NW - - Dark Dunes NW - - Central Bonk Rocks EN - - Central Bonk Rocks EC - - Central Bonk Rocks ES - - Dark Bonk Rocks EN - - Dark Bonk Rocks EC - - Dark Bonk Rocks ES - - Links House WN - - Links House WC - - Links House WS - - Big Bomb Shop WN - - Big Bomb Shop WC - - Big Bomb Shop WS - - C Whirlpool EN - - C Whirlpool EC - - C Whirlpool ES - - C Whirlpool - - Dark C Whirlpool EN - - Dark C Whirlpool EC - - Dark C Whirlpool ES - - Statues WN - - Statues WC - - Statues WS - - Hype Cave WN - - Hype Cave WC - - Hype Cave WS - - Lake Hylia Whirlpool - - Dam EC - - Swamp EC - - South Pass WC - - Dark South Pass WC - - Octoballoon Whirlpool - - Bomber Corner Whirlpool + - Sanctuary EC + - Dark Chapel EC + - Graveyard WC + - Dark Graveyard WC + - Wooden Bridge SW + - Broken Bridge SW + - Sand Dunes NW + - Dark Dunes NW + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - C Whirlpool + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Dam EC + - Swamp EC + - South Pass WC + - Dark South Pass WC \ No newline at end of file diff --git a/presets/world/owr_quadrantshuffle-vanillaborders.yaml b/presets/world/owr_quadrantshuffle-vanillaborders.yaml index f446011c..22f9e8fb 100644 --- a/presets/world/owr_quadrantshuffle-vanillaborders.yaml +++ b/presets/world/owr_quadrantshuffle-vanillaborders.yaml @@ -24,252 +24,252 @@ ow-edges: Swamp EC*: Dark South Pass WC* groups: northwest: - - Lost Woods NW - - Lost Woods EN - - Lost Woods SW - - Lost Woods SC - - Lost Woods SE - - Skull Woods EN - - Skull Woods SW - - Skull Woods SC - - Skull Woods SE - - Lumberjack WN - - Lumberjack SW - - Dark Lumberjack WN - - Dark Lumberjack SW - - Mountain Pass NW - - Mountain Pass SE - - Bumper Cave NW - - Bumper Cave SE - - Lost Woods Pass NW - - Lost Woods Pass NE - - Lost Woods Pass SW - - Lost Woods Pass SE - - Skull Woods Pass NW - - Skull Woods Pass NE - - Skull Woods Pass SW - - Skull Woods Pass SE - - Kakariko Fortune NE - - Kakariko Fortune EN - - Kakariko Fortune ES - - Kakariko Fortune SC - - Dark Fortune NE - - Dark Fortune EN - - Dark Fortune ES - - Dark Fortune SC - - Kakariko Pond NE - - Kakariko Pond WN - - Kakariko Pond WS - - Kakariko Pond EN - - Kakariko Pond ES - - Kakariko Pond SW - - Kakariko Pond SE - - Outcast Pond NE - - Outcast Pond WN - - Outcast Pond WS - - Outcast Pond EN - - Outcast Pond ES - - Outcast Pond SW - - Outcast Pond SE - - Sanctuary WN - - Sanctuary WS - - Kakariko NW - - Kakariko NC - - Kakariko NE - - Dark Chapel WN - - Dark Chapel WS - - Village of Outcasts NW - - Village of Outcasts NC - - Village of Outcasts NE - - Forgotten Forest NW - - Forgotten Forest NE - - Forgotten Forest ES - - Shield Shop NW - - Shield Shop NE - - Hyrule Castle WN - - Master Sword Meadow SC + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Dark Chapel WN + - Dark Chapel WS + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Master Sword Meadow SC northeast: - - West Death Mountain EN - - West Death Mountain ES - - West Dark Death Mountain EN - - West Dark Death Mountain ES - - East Death Mountain WN - - East Death Mountain WS - - East Death Mountain EN - - East Dark Death Mountain WN - - East Dark Death Mountain WS - - East Dark Death Mountain EN - - Death Mountain TR Pegs WN - - Turtle Rock WN - - Zora Waterfall NE - - Zora Waterfall SE - - Catfish SE - - Graveyard EC - - Dark Graveyard EC - - River Bend WC - - River Bend EN - - River Bend EC - - River Bend ES - - River Bend SW - - River Bend SC - - River Bend SE - - Qirn Jump WC - - Qirn Jump EN - - Qirn Jump EC - - Qirn Jump ES - - Qirn Jump SW - - Qirn Jump SC - - Qirn Jump SE - - Potion Shop WN - - Potion Shop WC - - Potion Shop WS - - Potion Shop EN - - Potion Shop EC - - Dark Witch WN - - Dark Witch WC - - Dark Witch WS - - Dark Witch EN - - Dark Witch EC - - Zora Approach NE - - Zora Approach WN - - Zora Approach WC - - Catfish Approach NE - - Catfish Approach WN - - Catfish Approach WC - - Wooden Bridge NW - - Wooden Bridge NC - - Wooden Bridge NE - - Broken Bridge NW - - Broken Bridge NC - - Broken Bridge NE - - Zoras Domain SW + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Graveyard EC + - Dark Graveyard EC + - River Bend WC + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Zoras Domain SW southwest: - - Kakariko ES - - Kakariko SE - - Village of Outcasts ES - - Village of Outcasts SE - - Hyrule Castle SW - - Pyramid SW - - Blacksmith WS - - Hammer Pegs WS - - Maze Race ES - - Dig Game EC - - Dig Game ES - - Kakariko Suburb NE - - Kakariko Suburb WS - - Kakariko Suburb ES - - Frog NE - - Frog WC - - Frog WS - - Frog ES - - Flute Boy WS - - Flute Boy SW - - Flute Boy SC - - Stumpy WS - - Stumpy SW - - Stumpy SC - - Central Bonk Rocks NW - - Central Bonk Rocks SW - - Dark Bonk Rocks NW - - Dark Bonk Rocks SW - - Desert EC - - Desert ES - - Flute Boy Approach NW - - Flute Boy Approach NC - - Flute Boy Approach EC - - Stumpy Approach NW - - Stumpy Approach NC - - Stumpy Approach EC - - C Whirlpool NW - - C Whirlpool WC - - C Whirlpool SC - - Dark C Whirlpool NW - - Dark C Whirlpool WC - - Dark C Whirlpool SC - - Desert Pass WC - - Desert Pass WS - - Desert Pass EC - - Desert Pass ES - - Swamp Nook EC - - Swamp Nook ES - - Dam NC - - Dam WC - - Dam WS - - Swamp NC - - Swamp WC - - Swamp WS + - Kakariko ES + - Kakariko SE + - Village of Outcasts ES + - Village of Outcasts SE + - Hyrule Castle SW + - Pyramid SW + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Flute Boy SW + - Flute Boy SC + - Stumpy WS + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks SW + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool SC + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Swamp NC + - Swamp WC + - Swamp WS southeast: - - Hyrule Castle ES - - Hyrule Castle SE - - Pyramid ES - - Pyramid SE - - Eastern Palace SW - - Eastern Palace SE - - Palace of Darkness SW - - Palace of Darkness SE - - Sand Dunes WN - - Sand Dunes SC - - Dark Dunes WN - - Dark Dunes SC - - Links House NE - - Links House SC - - Links House ES - - Big Bomb Shop NE - - Big Bomb Shop SC - - Big Bomb Shop ES - - Stone Bridge NC - - Stone Bridge WC - - Stone Bridge WS - - Stone Bridge EN - - Stone Bridge EC - - Stone Bridge SC - - Hammer Bridge NC - - Hammer Bridge WS - - Hammer Bridge EN - - Hammer Bridge EC - - Hammer Bridge SC - - Tree Line NW - - Tree Line WN - - Tree Line WC - - Tree Line SC - - Tree Line SE - - Dark Tree Line NW - - Dark Tree Line WN - - Dark Tree Line WC - - Dark Tree Line SC - - Dark Tree Line SE - - Eastern Nook NE - - Palace of Darkness Nook NE - - Statues NC - - Statues SC - - Hype Cave NC - - Hype Cave SC - - Lake Hylia NW - - Lake Hylia NC - - Lake Hylia NE - - Lake Hylia WS - - Lake Hylia EC - - Lake Hylia ES - - Ice Lake NW - - Ice Lake NC - - Ice Lake NE - - Ice Lake WS - - Ice Lake EC - - Ice Lake ES - - Ice Cave SW - - Ice Cave SE - - Shopping Mall SW - - Shopping Mall SE - - South Pass NC - - South Pass ES - - Dark South Pass NC - - Dark South Pass ES - - Octoballoon NW - - Octoballoon NE - - Octoballoon WC - - Octoballoon WS - - Bomber Corner NW - - Bomber Corner NE - - Bomber Corner WC - - Bomber Corner WS - - Hobo EC \ No newline at end of file + - Hyrule Castle ES + - Hyrule Castle SE + - Pyramid ES + - Pyramid SE + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes WN + - Dark Dunes SC + - Links House NE + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge NC + - Hammer Bridge WS + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Statues NC + - Statues SC + - Hype Cave NC + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia NC + - Lake Hylia NE + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake NC + - Ice Lake NE + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - South Pass NC + - South Pass ES + - Dark South Pass NC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC \ No newline at end of file diff --git a/presets/world/owr_ringshuffle-borders.yaml b/presets/world/owr_ringshuffle-borders.yaml new file mode 100644 index 00000000..3cbdb28f --- /dev/null +++ b/presets/world/owr_ringshuffle-borders.yaml @@ -0,0 +1,214 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + West Death Mountain EN*: East Death Mountain WN* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods SE*: Dark Fortune NE* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Eastern Palace SW*: Tree Line NW* + Palace of Darkness SW*: Dark Tree Line NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + groups: + border12_nw: + - Potion Shop EN + - Potion Shop EC + - Dark Witch EN + - Dark Witch EC + - Zora Approach WN + - Zora Approach WC + - Catfish Approach WN + - Catfish Approach WC + - C Whirlpool SC + - Dark C Whirlpool SC + - Statues SC + - Hype Cave SC + - Dam NC + - Swamp NC + - South Pass NC + - Dark South Pass NC + border12_es: + - Lumberjack SW + - Dark Lumberjack SW + - Mountain Pass NW + - Bumper Cave NW + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb WS + - Frog WC + - Frog WS + border23_nw: + - River Bend EN + - River Bend EC + - River Bend ES + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Flute Boy SW + - Flute Boy SC + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks SW + - Dark Bonk Rocks SW + - Links House SC + - Big Bomb Shop SC + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line WN + - Tree Line WC + - Dark Tree Line WN + - Dark Tree Line WC + - Flute Boy Approach NW + - Flute Boy Approach NC + - Stumpy Approach NW + - Stumpy Approach NC + - C Whirlpool NW + - Dark C Whirlpool NW + - Statues NC + - Hype Cave NC + - Lake Hylia NW + - Ice Lake NW + border23_es: + - Mountain Pass SE + - Bumper Cave SE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Dark Fortune EN + - Dark Fortune ES + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Kakariko ES + - Village of Outcasts ES + - Blacksmith WS + - Hammer Pegs WS + - Kakariko Suburb ES + - Frog ES + - Flute Boy WS + - Stumpy WS + border34_nw: + - Hyrule Castle ES + - Hyrule Castle SW + - Hyrule Castle SE + - Pyramid ES + - Pyramid SW + - Pyramid SE + - Sand Dunes WN + - Dark Dunes WN + - Central Bonk Rocks NW + - Dark Bonk Rocks NW + - Links House NE + - Big Bomb Shop NE + border34_es: + - Forgotten Forest ES + - Hyrule Castle WN +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool +ow-tileflips: + 1: + force_together: + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 \ No newline at end of file diff --git a/presets/world/owr_ringshuffle-full.yaml b/presets/world/owr_ringshuffle-full.yaml new file mode 100644 index 00000000..c271befe --- /dev/null +++ b/presets/world/owr_ringshuffle-full.yaml @@ -0,0 +1,313 @@ +ow-edges: + 1: + groups: + ring1: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Lumberjack WN + - Dark Lumberjack WN + - West Death Mountain EN + - West Dark Death Mountain EN + - East Death Mountain WN + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Zora Approach NE + - Catfish Approach NE + - Kakariko NW + - Kakariko NC + - Village of Outcasts NW + - Village of Outcasts NC + - Eastern Palace SE + - Palace of Darkness SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Desert EC + - Desert ES + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam WC + - Dam WS + - Dam EC + - Swamp WC + - Swamp WS + - Swamp EC + - South Pass WC + - South Pass ES + - Dark South Pass WC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Master Sword Meadow SC + - Zoras Domain SW + ring2: + - Lost Woods SE + - Skull Woods SE + - West Death Mountain ES + - West Dark Death Mountain ES + - East Death Mountain WS + - East Dark Death Mountain WS + - Kakariko Fortune NE + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune SC + - Kakariko NE + - Kakariko SE + - Village of Outcasts NE + - Village of Outcasts SE + - Eastern Palace SW + - Palace of Darkness SW + - Kakariko Suburb NE + - Frog NE + - Tree Line NW + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line SC + - Dark Tree Line SE + - Flute Boy Approach EC + - Stumpy Approach EC + - C Whirlpool WC + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - Dark C Whirlpool WC + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Lake Hylia NC + - Lake Hylia NE + - Ice Lake NC + - Ice Lake NE + ring3: + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Sanctuary EC + - Dark Chapel WN + - Dark Chapel WS + - Dark Chapel EC + - Graveyard WC + - Graveyard EC + - Dark Graveyard WC + - Dark Graveyard EC + - River Bend WC + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Forgotten Forest NW + - Forgotten Forest NE + - Shield Shop NW + - Shield Shop NE + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Wooden Bridge SW + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Broken Bridge SW + - Sand Dunes NW + - Sand Dunes SC + - Dark Dunes NW + - Dark Dunes SC + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Links House ES + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Hammer Bridge NC + - Hammer Bridge WS + - Hobo EC + border12_nw: + - Potion Shop EN + - Potion Shop EC + - Dark Witch EN + - Dark Witch EC + - Zora Approach WN + - Zora Approach WC + - Catfish Approach WN + - Catfish Approach WC + - C Whirlpool SC + - Dark C Whirlpool SC + - Statues SC + - Hype Cave SC + - Dam NC + - Swamp NC + - South Pass NC + - Dark South Pass NC + border12_es: + - Lumberjack SW + - Dark Lumberjack SW + - Mountain Pass NW + - Bumper Cave NW + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb WS + - Frog WC + - Frog WS + border23_nw: + - River Bend EN + - River Bend EC + - River Bend ES + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Flute Boy SW + - Flute Boy SC + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks SW + - Dark Bonk Rocks SW + - Links House SC + - Big Bomb Shop SC + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line WN + - Tree Line WC + - Dark Tree Line WN + - Dark Tree Line WC + - Flute Boy Approach NW + - Flute Boy Approach NC + - Stumpy Approach NW + - Stumpy Approach NC + - C Whirlpool NW + - Dark C Whirlpool NW + - Statues NC + - Hype Cave NC + - Lake Hylia NW + - Ice Lake NW + border23_es: + - Mountain Pass SE + - Bumper Cave SE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Dark Fortune EN + - Dark Fortune ES + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Kakariko ES + - Village of Outcasts ES + - Blacksmith WS + - Hammer Pegs WS + - Kakariko Suburb ES + - Frog ES + - Flute Boy WS + - Stumpy WS + border34_nw: + - Hyrule Castle ES + - Hyrule Castle SW + - Hyrule Castle SE + - Pyramid ES + - Pyramid SW + - Pyramid SE + - Sand Dunes WN + - Dark Dunes WN + - Central Bonk Rocks NW + - Dark Bonk Rocks NW + - Links House NE + - Big Bomb Shop NE + border34_es: + - Forgotten Forest ES + - Hyrule Castle WN +ow-tileflips: + 1: + force_together: + PedHobo: + - 0x00 + - 0x2d + - 0x80 + Zora: + - 0x0f + - 0x81 + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_ringshuffle-interiors.yaml b/presets/world/owr_ringshuffle-interiors.yaml new file mode 100644 index 00000000..0dcfbaa7 --- /dev/null +++ b/presets/world/owr_ringshuffle-interiors.yaml @@ -0,0 +1,258 @@ +ow-edges: + 1: + two-way: + Lumberjack SW: Mountain Pass NW + Dark Lumberjack SW: Bumper Cave NW + Potion Shop EN: Zora Approach WN + Potion Shop EC: Zora Approach WC + Dark Witch EN: Catfish Approach WN + Dark Witch EC: Catfish Approach WC + Maze Race ES: Kakariko Suburb WS + Dig Game EC: Frog WC + Dig Game ES: Frog WS + C Whirlpool SC: Dam NC + Dark C Whirlpool SC: Swamp NC + Statues SC: South Pass NC + Hype Cave SC: Dark South Pass NC + Mountain Pass SE: Kakariko Pond NE + Bumper Cave SE: Outcast Pond NE + Kakariko Fortune EN: Kakariko Pond WN + Kakariko Fortune ES: Kakariko Pond WS + Dark Fortune EN: Outcast Pond WN + Dark Fortune ES: Outcast Pond WS + River Bend EN: Potion Shop WN + River Bend EC: Potion Shop WC + River Bend ES: Potion Shop WS + Qirn Jump EN: Dark Witch WN + Qirn Jump EC: Dark Witch WC + Qirn Jump ES: Dark Witch WS + Kakariko ES: Blacksmith WS + Village of Outcasts ES: Hammer Pegs WS + Kakariko Suburb ES: Flute Boy WS + Frog ES: Stumpy WS + Flute Boy SW: Flute Boy Approach NW + Flute Boy SC: Flute Boy Approach NC + Stumpy SW: Stumpy Approach NW + Stumpy SC: Stumpy Approach NC + Central Bonk Rocks SW: C Whirlpool NW + Dark Bonk Rocks SW: Dark C Whirlpool NW + Links House SC: Statues NC + Big Bomb Shop SC: Hype Cave NC + Stone Bridge EN: Tree Line WN + Stone Bridge EC: Tree Line WC + Stone Bridge SC: Lake Hylia NW + Hammer Bridge EN: Dark Tree Line WN + Hammer Bridge EC: Dark Tree Line WC + Hammer Bridge SC: Ice Lake NW + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES: Sand Dunes WN + Hyrule Castle SW: Central Bonk Rocks NW + Hyrule Castle SE: Links House NE + Pyramid ES: Dark Dunes WN + Pyramid SW: Dark Bonk Rocks NW + Pyramid SE: Big Bomb Shop NE + groups: + ring1: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Lumberjack WN + - Dark Lumberjack WN + - West Death Mountain EN + - West Dark Death Mountain EN + - East Death Mountain WN + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Zora Approach NE + - Catfish Approach NE + - Kakariko NW + - Kakariko NC + - Village of Outcasts NW + - Village of Outcasts NC + - Eastern Palace SE + - Palace of Darkness SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Desert EC + - Desert ES + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam WC + - Dam WS + - Dam EC + - Swamp WC + - Swamp WS + - Swamp EC + - South Pass WC + - South Pass ES + - Dark South Pass WC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Master Sword Meadow SC + - Zoras Domain SW + ring2: + - Lost Woods SE + - Skull Woods SE + - West Death Mountain ES + - West Dark Death Mountain ES + - East Death Mountain WS + - East Dark Death Mountain WS + - Kakariko Fortune NE + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune SC + - Kakariko NE + - Kakariko SE + - Village of Outcasts NE + - Village of Outcasts SE + - Eastern Palace SW + - Palace of Darkness SW + - Kakariko Suburb NE + - Frog NE + - Tree Line NW + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line SC + - Dark Tree Line SE + - Flute Boy Approach EC + - Stumpy Approach EC + - C Whirlpool WC + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - Dark C Whirlpool WC + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Lake Hylia NC + - Lake Hylia NE + - Ice Lake NC + - Ice Lake NE + ring3: + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Sanctuary EC + - Dark Chapel WN + - Dark Chapel WS + - Dark Chapel EC + - Graveyard WC + - Graveyard EC + - Dark Graveyard WC + - Dark Graveyard EC + - River Bend WC + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Forgotten Forest NW + - Forgotten Forest NE + - Shield Shop NW + - Shield Shop NE + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Wooden Bridge SW + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Broken Bridge SW + - Sand Dunes NW + - Sand Dunes SC + - Dark Dunes NW + - Dark Dunes SC + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Links House ES + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Hammer Bridge NC + - Hammer Bridge WS + - Hobo EC +ow-whirlpools: + 1: + two-way: + Kakariko Pond Whirlpool: Octoballoon Whirlpool + Qirn Jump Whirlpool: Bomber Corner Whirlpool + River Bend Whirlpool: C Whirlpool +ow-tileflips: + 1: + force_together: + PedHobo: + - 0x00 + - 0x2d + - 0x80 + Zora: + - 0x0f + - 0x81 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_shuffle-largescreens.yaml b/presets/world/owr_shuffle-largescreens.yaml index 6c54658c..47798cfa 100644 --- a/presets/world/owr_shuffle-largescreens.yaml +++ b/presets/world/owr_shuffle-largescreens.yaml @@ -202,9 +202,9 @@ ow-edges: ow-tileflips: 1: force_together: - 1: + Pedestal: - 0x00 - 0x80 - 2: + Zora: - 0x0f - 0x81 \ No newline at end of file diff --git a/presets/world/owr_shuffle-separatemountain.yaml b/presets/world/owr_shuffle-separatemountain.yaml index b1078849..02a53d9c 100644 --- a/presets/world/owr_shuffle-separatemountain.yaml +++ b/presets/world/owr_shuffle-separatemountain.yaml @@ -2,15 +2,15 @@ ow-edges: 1: groups: mountain: - - West Death Mountain EN - - West Death Mountain ES - - West Dark Death Mountain EN - - West Dark Death Mountain ES - - East Death Mountain WN - - East Death Mountain WS - - East Death Mountain EN - - East Dark Death Mountain WN - - East Dark Death Mountain WS - - East Dark Death Mountain EN - - Death Mountain TR Pegs WN - - Turtle Rock WN + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN diff --git a/presets/world/owr_shuffle-splitsimilar.yaml b/presets/world/owr_shuffle-splitsimilar.yaml new file mode 100644 index 00000000..bf1dde3d --- /dev/null +++ b/presets/world/owr_shuffle-splitsimilar.yaml @@ -0,0 +1,123 @@ +settings: + 1: + ow_terrain: false +ow-edges: + 1: + groups: + similar_2_left_top: + - Lost Woods SW + - Skull Woods SW + - Lost Woods Pass NW + - Lost Woods Pass SW + - Skull Woods Pass NW + - Skull Woods Pass SW + - Kakariko Fortune EN + - Dark Fortune EN + - Kakariko Pond WN + - Kakariko Pond EN + - Kakariko Pond SW + - Outcast Pond WN + - Outcast Pond EN + - Outcast Pond SW + - Sanctuary WN + - Dark Chapel WN + - River Bend EC + - River Bend SW + - Qirn Jump EC + - Qirn Jump SW + - Potion Shop WC + - Dark Witch WC + - Kakariko NW + - Village of Outcasts NW + - Forgotten Forest NW + - Shield Shop NW + - Wooden Bridge NW + - Broken Bridge NW + - Dig Game EC + - Frog WC + - Flute Boy SW + - Stumpy SW + - Desert EC + - Flute Boy Approach NW + - Stumpy Approach NW + - C Whirlpool EN + - Dark C Whirlpool EN + - Statues WN + - Hype Cave WN + - Desert Pass WC + - Desert Pass EC + - Swamp Nook EC + - Dam WC + - Swamp WC + similar_2_right_bottom: + - Lost Woods SC + - Skull Woods SC + - Lost Woods Pass NE + - Lost Woods Pass SE + - Skull Woods Pass NE + - Skull Woods Pass SE + - Kakariko Fortune ES + - Dark Fortune ES + - Kakariko Pond WS + - Kakariko Pond ES + - Kakariko Pond SE + - Outcast Pond WS + - Outcast Pond ES + - Outcast Pond SE + - Sanctuary WS + - Dark Chapel WS + - River Bend ES + - River Bend SE + - Qirn Jump ES + - Qirn Jump SE + - Potion Shop WS + - Dark Witch WS + - Kakariko NC + - Village of Outcasts NC + - Forgotten Forest NE + - Shield Shop NE + - Wooden Bridge NE + - Broken Bridge NE + - Dig Game ES + - Frog WS + - Flute Boy SC + - Stumpy SC + - Desert ES + - Flute Boy Approach NC + - Stumpy Approach NC + - C Whirlpool ES + - Dark C Whirlpool ES + - Statues WS + - Hype Cave WS + - Desert Pass WS + - Desert Pass ES + - Swamp Nook ES + - Dam WS + - Swamp WS + similar_3_left_top: + - Central Bonk Rocks EN + - Dark Bonk Rocks EN + - Links House WN + - Big Bomb Shop WN + similar_3_middle: + - Central Bonk Rocks EC + - Dark Bonk Rocks EC + - Links House WC + - Big Bomb Shop WC + similar_3_right_bottom: + - Central Bonk Rocks ES + - Dark Bonk Rocks ES + - Links House WS + - Big Bomb Shop WS +ow-tileflips: + 1: + force_together: + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_shuffle-splitsimilarterrain.yaml b/presets/world/owr_shuffle-splitsimilarterrain.yaml new file mode 100644 index 00000000..2ade18e4 --- /dev/null +++ b/presets/world/owr_shuffle-splitsimilarterrain.yaml @@ -0,0 +1,175 @@ +settings: + 1: + ow_terrain: true +ow-edges: + 1: + groups: + similar_2_left_top: + - Lost Woods SW + - Skull Woods SW + - Lost Woods Pass NW + - Lost Woods Pass SW + - Skull Woods Pass NW + - Skull Woods Pass SW + - Kakariko Fortune EN + - Dark Fortune EN + - Kakariko Pond WN + - Kakariko Pond EN + - Kakariko Pond SW + - Outcast Pond WN + - Outcast Pond EN + - Outcast Pond SW + - Sanctuary WN + - Dark Chapel WN + - Potion Shop EN + - Dark Witch EN + - Zora Approach WN + - Catfish Approach WN + - Kakariko NW + - Village of Outcasts NW + - Forgotten Forest NW + - Shield Shop NW + - Dig Game EC + - Frog WC + - Flute Boy SW + - Stumpy SW + - Stone Bridge EN + - Hammer Bridge EN + - Tree Line WN + - Tree Line SC + - Dark Tree Line WN + - Dark Tree Line SC + - Desert EC + - Flute Boy Approach NW + - Stumpy Approach NW + - Lake Hylia NC + - Lake Hylia EC + - Ice Lake NC + - Ice Lake EC + - Ice Cave SW + - Shopping Mall SW + - Desert Pass WC + - Desert Pass EC + - Swamp Nook EC + - Dam WC + - Swamp WC + - Octoballoon NW + - Octoballoon WC + - Bomber Corner NW + - Bomber Corner WC + similar_2_right_bottom: + - Lost Woods SC + - Skull Woods SC + - Lost Woods Pass NE + - Lost Woods Pass SE + - Skull Woods Pass NE + - Skull Woods Pass SE + - Kakariko Fortune ES + - Dark Fortune ES + - Kakariko Pond WS + - Kakariko Pond ES + - Kakariko Pond SE + - Outcast Pond WS + - Outcast Pond ES + - Outcast Pond SE + - Sanctuary WS + - Dark Chapel WS + - Potion Shop EC + - Dark Witch EC + - Zora Approach WC + - Catfish Approach WC + - Kakariko NC + - Village of Outcasts NC + - Forgotten Forest NE + - Shield Shop NE + - Dig Game ES + - Frog WS + - Flute Boy SC + - Stumpy SC + - Stone Bridge EC + - Hammer Bridge EC + - Tree Line WC + - Tree Line SE + - Dark Tree Line WC + - Dark Tree Line SE + - Desert ES + - Flute Boy Approach NC + - Stumpy Approach NC + - Lake Hylia NE + - Lake Hylia ES + - Ice Lake NE + - Ice Lake ES + - Ice Cave SE + - Shopping Mall SE + - Desert Pass WS + - Desert Pass ES + - Swamp Nook ES + - Dam WS + - Swamp WS + - Octoballoon NE + - Octoballoon WS + - Bomber Corner NE + - Bomber Corner WS + similar_3_left_top: + - River Bend EN + - River Bend SW + - Qirn Jump EN + - Qirn Jump SW + - Potion Shop WN + - Dark Witch WN + - Wooden Bridge NW + - Broken Bridge NW + - Central Bonk Rocks EN + - Dark Bonk Rocks EN + - Links House WN + - Big Bomb Shop WN + - C Whirlpool EN + - Dark C Whirlpool EN + - Statues WN + - Hype Cave WN + similar_3_middle: + - River Bend EC + - River Bend SC + - Qirn Jump EC + - Qirn Jump SC + - Potion Shop WC + - Dark Witch WC + - Wooden Bridge NC + - Broken Bridge NC + - Central Bonk Rocks EC + - Dark Bonk Rocks EC + - Links House WC + - Big Bomb Shop WC + - C Whirlpool EC + - Dark C Whirlpool EC + - Statues WC + - Hype Cave WC + similar_3_right_bottom: + - River Bend ES + - River Bend SE + - Qirn Jump ES + - Qirn Jump SE + - Potion Shop WS + - Dark Witch WS + - Wooden Bridge NE + - Broken Bridge NE + - Central Bonk Rocks ES + - Dark Bonk Rocks ES + - Links House WS + - Big Bomb Shop WS + - C Whirlpool ES + - Dark C Whirlpool ES + - Statues WS + - Hype Cave WS +ow-tileflips: + 1: + force_together: + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_shuffle-vertical.yaml b/presets/world/owr_shuffle-vertical.yaml index 222834c9..d037f0c7 100644 --- a/presets/world/owr_shuffle-vertical.yaml +++ b/presets/world/owr_shuffle-vertical.yaml @@ -79,9 +79,9 @@ ow-edges: ow-tileflips: 1: force_together: - 1: + Pedestal: - 0x00 - 0x80 - 2: + Zora: - 0x0f - 0x81 \ No newline at end of file diff --git a/presets/world/owr_shuffle-verticalbyrows.yaml b/presets/world/owr_shuffle-verticalbyrows.yaml index 0799a25b..25650d60 100644 --- a/presets/world/owr_shuffle-verticalbyrows.yaml +++ b/presets/world/owr_shuffle-verticalbyrows.yaml @@ -219,9 +219,9 @@ ow-edges: ow-tileflips: 1: force_together: - 1: + Pedestal: - 0x00 - 0x80 - 2: + Zora: - 0x0f - 0x81 \ No newline at end of file From a1f2ee8d916dfa5b9a27845b6f6d4928ce4d9ecb Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 5 Oct 2023 01:05:16 -0500 Subject: [PATCH 046/123] Version bump 0.3.3.1 --- CHANGELOG.md | 8 +++++++- OverworldShuffle.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 041d2816..16a5a53d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Changelog +## 0.3.3.1 +- \~Merged in DR v1.2.0.21~ +- Fixed issue with Old Man death spawning on Pyramid/Castle +- Fixed issue with Mixed OWR + Flute Shuffle placing spots on large screens +- Fixed issue with mirror portals disappearing in Crossed OWR +- Added more OWR preset yamls + some fixes to the existing ones + ## 0.3.3.0 - Added Customizer support for all remaining OWR options - Added several OWR preset yamls (many ideas are thanks to Catobat) -- Fixed issue with Mixed OWR + Flute Shuffle placing spots on large screens - Removed Limited Crossed OWR and renamed Chaos Crossed to Unrestricted Crossed ## 0.3.2.2 diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 76b2e1e9..cd41b663 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.3.0' +version_number = '0.3.3.1' # branch indicator is intentionally different across branches version_branch = '-u' From fd99620ac979e52c934b0cfb7de9d02cb590dd28 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 6 Oct 2023 12:56:29 -0600 Subject: [PATCH 047/123] Standard: generation error fixed --- Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rules.py b/Rules.py index 4140469c..9b6ed1a9 100644 --- a/Rules.py +++ b/Rules.py @@ -1375,12 +1375,12 @@ def find_rules_for_zelda_delivery(world, player): if not rule(blank_state): rule_list.append(rule) next_path.append(ext.name) - if connect.name == 'Sanctuary': + if connect.name == 'Hyrule Castle Throne Room': return rule_list, next_path else: visited.add(connect) queue.append((connect, rule_list, next_path)) - raise Exception('No path to Sanctuary found') + raise Exception('No path to Throne Room found') def set_big_bomb_rules(world, player): From 8b5c5ce2547b8150afb52e1b686d294f0c4df535 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 10 Oct 2023 02:08:36 -0500 Subject: [PATCH 048/123] ROM Fixes - Fix standing item dupe respawns - Fix Hera boss room falling fade out --- Rom.py | 2 +- asm/owrando.asm | 16 ---------------- data/base2current.bps | Bin 107671 -> 107686 bytes 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/Rom.py b/Rom.py index a99e98c7..97edcdce 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '52317b2dd4fb303887f26ecc40a4cae3' +RANDOMIZERBASEHASH = 'de030c9fec5157af4ad1cf1948428210' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index adc7635e..16c60aee 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -169,11 +169,6 @@ plb : rtl nop #3 + -; follower hooks -;org $8689D9 -;SpritePrep_BombShoppe: -;JML BombShoppe_ConditionalSpawn : NOP - ;Code org $aa8800 OWTransitionDirection: @@ -541,7 +536,6 @@ OWBonkDrops: INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 1,S : PHA : BNE + ; S = Collected, FlagBitmask, X (row + 2) LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - ; JSLSpriteSFX_QueueSFX3WithPan + LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + @@ -873,7 +867,6 @@ OWNewDestination: ++ lda $84 : !add 1,s : sta $84 : pla : pla .adjustMainAxis - ;LDA $84 : SEC : SBC #$0400 : AND #$0F80 : ASL : XBA : STA $88 ; vram LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 @@ -936,7 +929,6 @@ OWNewDestination: sep #$30 : lda $04 : and #$3f : !add OWOppSlotOffset,y : asl : sta $700 ; crossed OW shuffle and terrain - ;lda $8a : JSR OWDetermineScreensPaletteSet : STX $04 ldx $05 : ldy $08 : jsr OWWorldTerrainUpdate ldx $8a : lda $05 : sta $8a : stx $05 ; $05 is prev screen id, $8a is dest screen @@ -1161,14 +1153,6 @@ OWEndScrollTransition: RTL } -; BombShoppe_ConditionalSpawn: -; { -; nop -; INC.w $0BA0,X : LDA.b #$B5 ; what we wrote over -; JML SpritePrep_BombShoppe+5 -; nop#20 -; } - ;Data org $aaa000 OWEdgeOffsets: diff --git a/data/base2current.bps b/data/base2current.bps index 3c56274a00f1b9d319f2082083e17de09264ec86..930280e65cf3437d9796b2f5d3dacd74d95a28b7 100644 GIT binary patch delta 6808 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 dP2h)MUVO=KXjJyZ4*V&gI1Bd?${|}+Z(;@%> delta 6668 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 Date: Sun, 22 Oct 2023 16:24:11 -0500 Subject: [PATCH 049/123] Fix for Hera boss music fadeout when falling --- Rom.py | 2 +- data/base2current.bps | Bin 107686 -> 107687 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 97edcdce..d253271d 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'de030c9fec5157af4ad1cf1948428210' +RANDOMIZERBASEHASH = '2a89df65883fba75578061ebfb4b1003' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 930280e65cf3437d9796b2f5d3dacd74d95a28b7..e46b012a02d7c37315fd2bbf44178e69041ea6e9 100644 GIT binary patch delta 6193 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 g=U6`~09So@kww3wu~s( Date: Tue, 24 Oct 2023 05:29:52 -0500 Subject: [PATCH 050/123] Allow user to change and save output directory within the Gui --- resources/app/gui/lang/en.json | 2 +- source/gui/bottom.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 66aefd98..8a6a0212 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -391,7 +391,7 @@ "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", - "bottom.content.outputdir": "Open Output Directory", + "bottom.content.outputdir": "Select Destination", "bottom.content.docs": "Open Documentation" } } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index beb77a1f..4c21621e 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -154,6 +154,12 @@ def bottom_frame(self, parent, args=None): open_file(output_path('.')) + def select_output(): + from tkinter import filedialog + folder_selected = filedialog.askdirectory() + if folder_selected is not None: + args.outputpath = parent.settings["outputpath"] = folder_selected + ## Output Button # widget ID widget = "outputdir" @@ -168,7 +174,7 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=open_output) + self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=select_output) # button: pack self.widgets[widget].pieces["button"].pack(side=RIGHT) From 42c51d28155634f6b98ec176368dc1fed5b04628 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 24 Oct 2023 07:38:45 -0500 Subject: [PATCH 051/123] Fix Hera music to play correct dungeon track following a floor change --- Rom.py | 2 +- data/base2current.bps | Bin 107687 -> 107693 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index d253271d..e74a80bb 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '2a89df65883fba75578061ebfb4b1003' +RANDOMIZERBASEHASH = '52bd727d617de6491fdbb74fd047852d' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index e46b012a02d7c37315fd2bbf44178e69041ea6e9..0b9e4f9d64f7a216bd5c188920d1b726fe5b0c05 100644 GIT binary patch delta 1042 zcmW+!drVtp9KFBNQc7R6<qplXH@Da*}hVm%=|? z3csL%HtNuuKv83VvTwhaVyCUJ0uO!1So_)V9DO>RfhWE!F2&+X{&rWKBa`VHykDTq zHx^mQf-CfI0yW$j6slO*LmC%$LJd7GtN}y^g_ZD#E)<;sqwjMO69Mng@5ULsOM>p? ze){F(gBph&Z&`P-KcevGgYJ73$sie`(5ehn@FLokf$q>T`_GPaz;yTv>+a=-Tct@@V9zw)%cCpl(IegjYj&Vw zs>4R+I<4eEr?n4XQz4~|T%hHsBO1y@g#%=a(@F`~R34+V0WC*Eu{w3by%qj_)Q*j~ zIYZc!lCEW=Vj+*%osEpNU>EJu@;a9aTT}USQBDj@=IoFBk$JrNZJ!_iaM{wtC78@c zE`d0wrWlUmldCi4nf5@JCtBNGRM%X;{K4M+lZYJ)nNZ8*#lkHR7>X|pZ@Ttj-e2jr zkVp(3y6Ud1vhev8SI5+TbV&|f(2DjcAS-%g z+pPvHkhu3pZrc}+P1pVr`Nz;33Qz!!&L|)i@{mgby}(E6csL6xv=ARE(T48C!$~MX zRS8fc5EohJ+Ka8+zN6=q%uoUhhQ+`7%)b+1o{dI7{v9;ZX8Z7#s39QGIS&wwRtX1q zTq%s8YHWnDqydWqxu_{4G<`dRGGKwjHJC4xEL6=H@?e4$5th_Pj>-rNv-2$YSR6sA zb^7?UR|9OmGO?FxINa!RdGnaBieQ_?YZ2QomdUUCBn+d4RUY4PYJk!aiE1$_V8)KZ zVwm)><;id=hJ~JRskQ9#^f1z*Ry`zes&j^D9isGb0`zECA8I2($p#qU$7qJAs*s@! n#WCL)z@m(j)XaIR%^3GTEF{-BUttQG!BP-G`2Bp;pj`VO6%vl} delta 1070 zcmW+!YfM`O7`@-+Q3|vSTA;k7y*#AI`(=^G){SY0@=#nh9qMF6=$N87oR1Cey&D6{ zF5Tr)zp*+gv>ibdW*WRR#z3|yLtNIWGyHJ6Oo9dppaqv;w%P2*IVb1*I5|JgXS4nz zv;L=b(83q)hhF~dg75I*mwE&iZo-mlFZqZsug{V38TW#!CLsGPo^gCEen+FC{zoBvKr_YJhG=?MJSTL2NkYKxflR-!t0UM?L{6( zVvkU@uTN*@sOCkR?{S{m?r|)YF%NAu{ZMYZo_V`(o}*yh@#lo?>Q^iCtG8-TaJ6F| z$Ghy{6-$S_Hh0~T(!ia*yLEBP5-?DD-@9Ejgp6?FGYW zCwQuT(dH^vRcImEWzyd@z=Vr6HH5%^*V4d|R+tm(LLh|Hekpej&q@e)`fx;V8qmpD zp2eS&k7vQ{zuhVdKToA&rT?sqn`MpfImE~o3)Xr*a9lDiJqD)wEtoeVp<*tdoI!bdI z&7~}S95vyfgiI_5hhdc}@S3=>I^8`*eK2?6kc1Xfnj|!~H{)73sGuID5s(2@qAmii zL$IObv}M&6OHBMI>_deLk{|~QRghWaBjcGAG}O>SS*e+MVTNTIy)X{4-1NaUKjGRC zwPCR}o#7TptU6?N9((cAd6F8xX?x$ULc0o%K@)1CATz_V?$83^j@$e-r}c}C+Dre+ zr*Pe|sWK8Dbr4<#!)PwiK{j^yjOJQnu`LRcAq$;R&;@>&5e+9H8YiQ@i<@yF8V-UE zt74!uSX#`EwUn@u?p>Xai32gv=M$x?^f-N-D@{|>WoR;B57+1pHdtRXc8WXWAweoB zSDI0(G>gqCK>9%%wxoM+&?cTshYNlZL;ZP%#HYpQb76=KkZQV_0R??Pyq6Cy($9Yz zJFgAO Date: Sun, 5 Nov 2023 08:46:58 -0600 Subject: [PATCH 052/123] Various MSU corrections/fixes - GT2 track now plays if DR and GTBK is collected --- Rom.py | 2 +- data/base2current.bps | Bin 107693 -> 107715 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index e74a80bb..f11ced3b 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '52bd727d617de6491fdbb74fd047852d' +RANDOMIZERBASEHASH = 'd1f78b0b0a4aad1523ad0068dd24aa50' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 0b9e4f9d64f7a216bd5c188920d1b726fe5b0c05..ec152a61ce211a64429ba9a4a4c12b4aa3eb0a40 100644 GIT binary patch delta 15584 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 delta 15386 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 Date: Wed, 8 Nov 2023 23:10:34 -0600 Subject: [PATCH 053/123] Marking OW screens as visited --- Rom.py | 2 +- asm/owrando.asm | 13 +++++++++++++ data/base2current.bps | Bin 107715 -> 107745 bytes 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index f11ced3b..3a82aa7c 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'd1f78b0b0a4aad1523ad0068dd24aa50' +RANDOMIZERBASEHASH = 'fe9e7870071daa40829c1072829bf30b' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 16c60aee..485bab50 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -29,6 +29,9 @@ BCS OWDetectTransitionReturn org $02a999 jsl OWEdgeTransition : nop #4 ;LDA $02A4E3,X : ORA $7EF3CA +org $02aa07 +JSL OWMarkVisited : NOP + org $04e8ae JSL OWDetectSpecialTransition RTL : NOP @@ -340,6 +343,16 @@ OWOldManSpeed: lda #$0c : sta $5e ; what we wrote over rtl } +OWMarkVisited: +{ + LDX.b $8A : STZ.w $0412 ; what we wrote over + LDA.b $10 : CMP.b #$14 : BCS .return + LDA.l OverworldEventDataWRAM,X + ORA.b #$80 : STA.l OverworldEventDataWRAM,X + + .return + RTL +} LoadMapDarkOrMixed: { diff --git a/data/base2current.bps b/data/base2current.bps index ec152a61ce211a64429ba9a4a4c12b4aa3eb0a40..a21ad3abf7406231397ccf4c057391d8ef5b7d7a 100644 GIT binary patch delta 395 zcmV;60d)Su$p+!c2C#ks1R-1Aq_ctn#{vonfRQ3`iK^-(fU`>kAO!)9vup|L1p$Y% zSP#N70U(1LLYEst0q0I3fba^Xpeg>z@bD0T2BnJ01Mmo?Udaq=WL|5j42526riz>r z1f>wk6tI_UG>?UVpO1{K$ppeM@DQT@1HC!dmT(n`jrM|Q0GD4gkC!_`0U`nnikDwQ z0b@yui&~qnLGpen0!YvWsT++i0j~hOs_>U%ZjTj$CX28CyQ;~s&;_Y^Oa_a|LeK{y zSgi(33X91!&;_a4Oa_a|HP8jA+DrzEmkLAyHw86~s*?kkMnnNR0X3I_L;;sU7K)vK z*yz0gyV^*XR}%t&g(CrHrI7#t delta 302 zcmaEOlI`$GwheWREK13D=WK3bJkG@A(zrR8MS+#Ed~*rsYgWd>%?bR6j2O9^m7S(5 zJ25_I)SS-k%&0Kk-kDL1Nv~mggfpWO)3?s)HO`F5j3v__I5SG=whDCjch@$a+;D+) zWu?!Du9Hp|*p(vIviW@JI%xtFy9p9A1u|{{8Phdf7|mJbdsj_noSx#sXvHW$eZC9h zbjPgDxeYg7>}A+}%VWB>FjGUTGUI#^UZ#eNi Date: Mon, 13 Nov 2023 15:43:27 -0700 Subject: [PATCH 054/123] fix(key logic): typo fix(bunny logic): multiple paths considered --- KeyDoorShuffle.py | 2 +- Main.py | 2 +- RELEASENOTES.md | 4 ++ Rules.py | 177 ++++++++++++++++++++++++---------------------- 4 files changed, 100 insertions(+), 85 deletions(-) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 7284c0cf..6883fb94 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -1800,7 +1800,7 @@ def imp_locations_factory(world, player): imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] if world.mode[player] == 'standard': imp_locations.append('Zelda Pickup') - imp_locations.append('Zelda Dropoff') + imp_locations.append('Zelda Drop Off') return imp_locations diff --git a/Main.py b/Main.py index 24fa0080..d5b39084 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.21' +version_number = '1.2.0.22' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cae2bc56..5abff4a8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,10 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* * 1.2.0.22u + * Fixed logic issues: + * Self-locking key not allowed in Sanctuary in standard (typo fixed) + * More advanced bunny-walking logic in dungeons (multiple paths considred) * 1.2.0.21u * Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory * Small fix for Tavern Shuffle (thanks Catobat) diff --git a/Rules.py b/Rules.py index 9b6ed1a9..1560dba2 100644 --- a/Rules.py +++ b/Rules.py @@ -101,16 +101,20 @@ def mirrorless_path_to_castle_courtyard(world, player): else: queue.append((entrance.connected_region, new_path)) + def set_rule(spot, rule): spot.access_rule = rule + def set_defeat_dungeon_boss_rule(location): # Lambda required to defer evaluation of dungeon.boss since it will change later if boos shuffle is used set_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state)) + def set_always_allow(spot, rule): spot.always_allow = rule + def add_rule(spot, rule, combine='and'): old_rule = spot.access_rule if combine == 'or': @@ -140,22 +144,26 @@ def forbid_item(location, item, player): old_rule = location.item_rule location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i) + def add_item_rule(location, rule): old_rule = location.item_rule location.item_rule = lambda item: rule(item) and old_rule(item) + def item_in_locations(state, item, player, locations): for location in locations: if item_name(state, location[0], location[1]) == (item, player): return True return False + def item_name(state, location, player): location = state.world.get_location(location, player) if location.item is None: return None return (location.item.name, location.item.player) + def global_rules(world, player): # ganon can only carry triforce add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) @@ -187,7 +195,7 @@ def global_rules(world, player): set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) - set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith + set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest @@ -625,7 +633,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Swamp Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier', player), player)) set_rule(world.get_entrance('Swamp Crystal Switch Inner to Crystal', player), lambda state: state.can_hit_crystal(player)) - set_rule(world.get_entrance('Swamp Crystal Switch Outer to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_beam_sword(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('Swamp Crystal Switch Outer', player), player))) # It is the length of the sword, not the beam itself that allows this + set_rule(world.get_entrance('Swamp Crystal Switch Outer to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_beam_sword(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('Swamp Crystal Switch Outer', player), player))) # It is the length of the sword, not the beam itself that allows this set_rule(world.get_entrance('Swamp Crystal Switch Outer to Inner Bypass', player), lambda state: state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) set_rule(world.get_entrance('Swamp Crystal Switch Inner to Outer Bypass', player), lambda state: state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) @@ -668,8 +676,8 @@ def global_rules(world, player): set_rule(world.get_entrance('Mire Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Left', player), player)) set_rule(world.get_entrance('Mire Conveyor to Crystal', player), lambda state: state.can_hit_crystal(player)) - set_rule(world.get_entrance('Mire Tall Dark and Roomy to Ranged Crystal', player), lambda state: True) # Can always throw pots - set_rule(world.get_entrance('Mire Fishbone Blue Barrier Bypass', player), lambda state: False) # (state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) and state.can_tastate.can_use_bombs(player) // Easy to do but obscure. Should it be in logic? + set_rule(world.get_entrance('Mire Tall Dark and Roomy to Ranged Crystal', player), lambda state: True) # Can always throw pots + set_rule(world.get_entrance('Mire Fishbone Blue Barrier Bypass', player), lambda state: False) # (state.world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player)) and state.can_tastate.can_use_bombs(player) // Easy to do but obscure. Should it be in logic? set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: state.can_reach('TR Chain Chomps Top', 'Region', player) and state.can_hit_crystal_through_barrier(player)) set_rule(world.get_entrance('TR Chain Chomps Top to Bottom Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Chain Chomps Top', player), player)) @@ -691,14 +699,14 @@ def global_rules(world, player): set_rule(world.get_entrance('TR Pokey 2 Top to Crystal', player), lambda state: state.can_hit_crystal(player)) set_rule(world.get_entrance('TR Crystaroller Top to Crystal', player), lambda state: state.can_hit_crystal(player)) set_rule(world.get_entrance('TR Crystal Maze Start to Crystal', player), lambda state: state.can_hit_crystal(player)) - set_rule(world.get_entrance('TR Chain Chomps Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Chain Chomps Bottom', player), player))) # or state.has_beam_sword(player) - set_rule(world.get_entrance('TR Pokey 2 Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('TR Pokey 2 Bottom', player), player))) # or state.has_beam_sword(player) - set_rule(world.get_entrance('TR Crystaroller Bottom to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player))) # or state.has_beam_sword(player) - set_rule(world.get_entrance('TR Crystaroller Middle to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Chain Chomps Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Chain Chomps Bottom', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Pokey 2 Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('TR Pokey 2 Bottom', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Crystaroller Bottom to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player))) # or state.has_beam_sword(player) + set_rule(world.get_entrance('TR Crystaroller Middle to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))) # or state.has_beam_sword(player) set_rule(world.get_entrance('TR Crystaroller Middle to Bottom Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Blue Boomerang', player)) - set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes. - set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # Beam sword does NOT work - set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True) # Can always grab a pot from the interior and walk it to the start region and throw it there + set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes. + set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # Beam sword does NOT work + set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True) # Can always grab a pot from the interior and walk it to the start region and throw it there set_rule(world.get_entrance('GT Hookshot Platform Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Platform', player), player)) set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player)) @@ -811,7 +819,7 @@ def bomb_rules(world, player): ('TR Tongue Pull WS', True), ('TR Twin Pokeys NW', False), ] - for killdoor,bombable in easy_kill_rooms: + for killdoor, bombable in easy_kill_rooms: if bombable: add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player))) else: @@ -828,47 +836,47 @@ def bomb_rules(world, player): if world.get_entrance('Mire Cross SW', player).door.trapped: add_rule(world.get_entrance('Mire Cross SW', player), lambda state: state.can_kill_most_things(player)) - enemy_kill_drops = [ # Location, bool-bombable + enemy_kill_drops = [ # Location, bool-bombable ('Hyrule Castle - Map Guard Key Drop', True), ('Hyrule Castle - Boomerang Guard Key Drop', True), ('Hyrule Castle - Key Rat Key Drop', True), -# ('Hyrule Castle - Big Key Drop', True), # Pots are available -# ('Eastern Palace - Dark Eyegore Key Drop', True), # Pots are available + # ('Hyrule Castle - Big Key Drop', True), # Pots are available + # ('Eastern Palace - Dark Eyegore Key Drop', True), # Pots are available ('Castle Tower - Dark Archer Key Drop', True), -# ('Castle Tower - Circle of Pots Key Drop', True), # Pots are available -# ('Skull Woods - Spike Corner Key Drop', True), # Pots are available + # ('Castle Tower - Circle of Pots Key Drop', True), # Pots are available + # ('Skull Woods - Spike Corner Key Drop', True), # Pots are available ('Ice Palace - Jelly Key Drop', True), ('Ice Palace - Conveyor Key Drop', True), ('Misery Mire - Conveyor Crystal Key Drop', True), ('Turtle Rock - Pokey 1 Key Drop', True), ('Turtle Rock - Pokey 2 Key Drop', True), -# ('Ganons Tower - Mini Helmasaur Key Drop', True) # Pots are available - ('Castle Tower - Room 03', True), # Two spring soliders - ('Ice Palace - Compass Chest', True) # Pengators + # ('Ganons Tower - Mini Helmasaur Key Drop', True) # Pots are available + ('Castle Tower - Room 03', True), # Two spring soliders + ('Ice Palace - Compass Chest', True) # Pengators ] - for location,bombable in enemy_kill_drops: + for location, bombable in enemy_kill_drops: if bombable: - add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.can_kill_most_things(player)) + add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.can_kill_most_things(player)) else: add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player)) - add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player)) bombable_floors = ['PoD Pit Room Bomb Hole', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', 'GT Bob\'s Room Hole'] for entrance in bombable_floors: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) if world.doorShuffle[player] == 'vanilla': - add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. - add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? + add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. + add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? dungeon_bonkable = ['Sewers Rat Path WS', 'Sewers Rat Path WN', 'PoD Warp Hint SE', 'PoD Jelly Hall NW', 'PoD Jelly Hall NE', 'PoD Mimics 1 SW', 'Thieves Ambush E', 'Thieves Rail Ledge W', 'TR Dash Room NW', 'TR Crystaroller SW', 'TR Dash Room ES', - 'GT Four Torches NW','GT Fairy Abyss SW' + 'GT Four Torches NW', 'GT Fairy Abyss SW' ] dungeon_bombable = ['PoD Map Balcony WS', 'PoD Arena Ledge ES', 'PoD Dark Maze E', 'PoD Big Chest Balcony W', - 'Swamp Pot Row WN','Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S', + 'Swamp Pot Row WN', 'Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S', 'Skull Pot Circle WN', 'Skull Pull Switch EN', 'Skull Big Key EN', 'Skull Lone Pot WN', 'Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', @@ -876,9 +884,9 @@ def bomb_rules(world, player): 'GT Warp Maze (Rails) WS', 'GT Bob\'s Room Hole', 'GT Randomizer Room ES', 'GT Bomb Conveyor SW', 'GT Crystal Circles NW', 'GT Cannonball Bridge SE', 'GT Refill NE' ] for entrance in dungeon_bonkable: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) for entrance in dungeon_bombable: - add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) + add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) else: doors_to_bomb_check = [x for x in world.doors if x.player == player and x.type in [DoorType.Normal, DoorType.Interior]] for door in doors_to_bomb_check: @@ -984,13 +992,13 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('20 Rupee Cave', player), player) add_bunny_rule(world.get_entrance('50 Rupee Cave', player), player) - add_bunny_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), player) # bunny cannot lift bush - add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player) # bunny cannot lift bush - add_bunny_rule(world.get_entrance('Skull Woods Final Section', player), player) # bunny cannot use fire rod + add_bunny_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), player) # bunny cannot lift bush + add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player) # bunny cannot lift bush + add_bunny_rule(world.get_entrance('Skull Woods Final Section', player), player) # bunny cannot use fire rod add_bunny_rule(world.get_entrance('Hookshot Cave', player), player) - add_bunny_rule(world.get_entrance('Thieves Town', player), player) # bunny cannot pull + add_bunny_rule(world.get_entrance('Thieves Town', player), player) # bunny cannot pull add_bunny_rule(world.get_entrance('Turtle Rock', player), player) - add_bunny_rule(world.get_entrance('Palace of Darkness', player), player) # kiki needs pearl + add_bunny_rule(world.get_entrance('Palace of Darkness', player), player) # kiki needs pearl add_bunny_rule(world.get_entrance('Hammer Peg Cave', player), player) add_bunny_rule(world.get_entrance('Bonk Fairy (Dark)', player), player) add_bunny_rule(world.get_entrance('Misery Mire', player), player) @@ -1109,17 +1117,20 @@ def forbid_bomb_jump_requirements(world, player): set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) set_rule(world.get_entrance('Ice Island To East Pier', player), lambda state: False) + # Light cones in standard depend on which world we actually are in, not which one the location would normally be # We add Lamp requirements only to those locations which lie in the dark world (or everything if open DW_Entrances = ['Bumper Cave (Bottom)', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)', 'Hookshot Cave', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Thieves Town', 'Skull Woods Final Section', 'Ice Palace', 'Misery Mire', 'Palace of Darkness', 'Swamp Palace', 'Turtle Rock', 'Dark Death Mountain Ledge (West)'] + def check_is_dark_world(region): for entrance in region.entrances: if entrance.name in DW_Entrances: return True return False + def add_conditional_lamps(world, player): def add_conditional_lamp(spot, region, spottype='Location'): if spottype == 'Location': @@ -1221,33 +1232,33 @@ def swordless_rules(world, player): # todo: new traps std_kill_rooms = { - 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard - 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard - 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots - 'Desert Compass Room': ['Desert Compass NE'], # Three popos - 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos + 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard + 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard + 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots + 'Desert Compass Room': ['Desert Compass NE'], # Three popos + 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos 'Hera Beetles': ['Hera Beetles WS'], # Three blue beetles and only two pots, and bombs don't work. - 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain + 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain 'Tower Dark Archers': ['Tower Dark Archers WN'], # Not a kill room 'Tower Red Spears': ['Tower Red Spears WN'], # Two spear soldiers - 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts - 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. + 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts + 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. 'PoD Turtle Party': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], # Lots of turtles. - 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. - 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights - 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? - 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. - 'Mire Cross': ['Mire Cross ES'], # 4 Sluggulas. Bombs don't work - 'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], # Two pokeys - 'GT Petting Zoo': ['GT Petting Zoo SE'], # Dont make anyone do this room with bombs. - 'GT DMs Room': ['GT DMs Room SW'], # Four red stalfos - 'GT Gauntlet 1': ['GT Gauntlet 1 WN'], # Stalfos/zazaks - 'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], # Red stalfos - 'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], # Blue zazaks - 'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], # Red zazaks - 'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], # Stalfos and zazak - 'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'], # Wizzrobes. Bombs don't work - 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work + 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. + 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights + 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? + 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. + 'Mire Cross': ['Mire Cross ES'], # 4 Sluggulas. Bombs don't work + 'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], # Two pokeys + 'GT Petting Zoo': ['GT Petting Zoo SE'], # Dont make anyone do this room with bombs. + 'GT DMs Room': ['GT DMs Room SW'], # Four red stalfos + 'GT Gauntlet 1': ['GT Gauntlet 1 WN'], # Stalfos/zazaks + 'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], # Red stalfos + 'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], # Blue zazaks + 'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], # Red zazaks + 'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], # Stalfos and zazak + 'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'], # Wizzrobes. Bombs don't work + 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work } # all trap rooms? std_kill_doors_if_trapped = { @@ -1262,6 +1273,7 @@ std_kill_doors_if_trapped = { # 'Ice Lobby S' # can melt rule is sufficient } + def add_connection(parent_name, target_name, entrance_name, world, player): parent = world.get_region(parent_name, player) target = world.get_region(target_name, player) @@ -1299,7 +1311,7 @@ def standard_rules(world, player): return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)'] def standard_escape_rule(state): - return state.can_kill_most_things(player) or bomb_escape_rule() + return state.can_kill_most_things(player) or bomb_escape_rule() add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule) @@ -1326,6 +1338,7 @@ def standard_rules(world, player): def check_rule_list(state, r_list): return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) + rule_list, debug_path = find_rules_for_zelda_delivery(world, player) set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) @@ -1502,7 +1515,7 @@ def set_big_bomb_rules(world, player): set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) - #crossing peg bridge starting from the southern dark world + # crossing peg bridge starting from the southern dark world def cross_peg_bridge(state): return state.has('Hammer', player) and state.has_Pearl(player) @@ -1524,28 +1537,28 @@ def set_big_bomb_rules(world, player): # G = Glove if bombshop_entrance.name in Normal_LW_entrances: - #1. basic routes - #2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror + # 1. basic routes + # 2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror # -> M or BR add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: basic_routes(state) or state.has_Mirror(player)) elif bombshop_entrance.name in LW_walkable_entrances: - #1. Mirror then basic routes + # 1. Mirror then basic routes # -> M and BR add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and basic_routes(state)) elif bombshop_entrance.name in Northern_DW_entrances: - #1. Mirror and basic routes - #2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl + # 1. Mirror and basic routes + # 2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (M and BR) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and basic_routes(state))) elif bombshop_entrance.name == 'Bumper Cave (Bottom)': - #1. Mirror and Lift rock and basic_routes - #2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) - #3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl + # 1. Mirror and Lift rock and basic_routes + # 2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) + # 3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (((G or Flute) and M) and BR)) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.can_flute(player)) and state.has_Mirror(player)) and basic_routes(state))) elif bombshop_entrance.name in Southern_DW_entrances: - #1. Mirror and enter via gate: Need mirror and Aga1 - #2. cross peg bridge: Need hammer and moon pearl + # 1. Mirror and enter via gate: Need mirror and Aga1 + # 2. cross peg bridge: Need hammer and moon pearl # -> CPB or (M and A) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player))) elif bombshop_entrance.name in Isolated_DW_entrances: @@ -1809,7 +1822,6 @@ def set_inverted_big_bomb_rules(world, player): def set_bunny_rules(world, player, inverted): - # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', @@ -1848,13 +1860,14 @@ def set_bunny_rules(world, player, inverted): return region.is_light_world else: return region.is_dark_world + def is_link(region): if inverted: return region.is_dark_world else: return region.is_light_world - def get_rule_to_add(region, location = None, connecting_entrance = None): + def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. if world.logic[player] == 'owglitches': @@ -1877,16 +1890,15 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - seen = {region} - queue = deque([(region, [])]) + queue = deque([(region, [], {region})]) while queue: - (current, path) = queue.popleft() + (current, path, seen) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: continue new_path = path + [entrance.access_rule] - seen.add(new_region) + new_seen = seen.union({new_region}) if not is_link(new_region): if world.logic[player] == 'owglitches': if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: @@ -1923,7 +1935,7 @@ def set_bunny_rules(world, player, inverted): else: continue if is_bunny(new_region): - queue.append((new_region, new_path)) + queue.append((new_region, new_path, new_seen)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) @@ -1975,7 +1987,6 @@ drop_dungeon_entrances = { "Skull Back Drop" } - bunny_revivable_entrances = { "Sewers Pull Switch", "TR Dash Room", "Swamp Boss", "Hera Boss", "Tower Agahnim 1", "Ice Lobby", "Sewers Rat Path", "PoD Falling Bridge", @@ -2035,14 +2046,14 @@ bunny_impassible_doors = { 'PoD Arena Landing Bonk Path', 'PoD Sexy Statue NW', 'PoD Map Balcony Drop Down', 'PoD Mimics 1 NW', 'PoD Falling Bridge Path N', 'PoD Falling Bridge Path S', 'PoD Mimics 2 NW', 'PoD Bow Statue Down Ladder', 'PoD Dark Pegs Landing to Right', - 'PoD Dark Pegs Left to Middle Barrier - Blue', 'PoD Dark Pegs Left to Ranged Crystal', + 'PoD Dark Pegs Left to Middle Barrier - Blue', 'PoD Dark Pegs Left to Ranged Crystal', 'PoD Turtle Party ES', 'PoD Turtle Party NW', 'PoD Callback Warp', 'Swamp Lobby Moat', 'Swamp Entrance Moat', 'Swamp Trench 1 Approach Swim Depart', 'Swamp Trench 1 Approach Key', 'Swamp Trench 1 Key Approach', 'Swamp Trench 1 Key Ledge Depart', 'Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Departure Key', 'Swamp Hub Hook Path', 'Swamp Shortcut Blue Barrier', 'Swamp Trench 2 Pots Blue Barrier', 'Swamp Trench 2 Pots Wet', 'Swamp Trench 2 Departure Wet', 'Swamp West Ledge Hook Path', 'Swamp Barrier Ledge Hook Path', 'Swamp Attic Left Pit', 'Swamp Attic Right Pit', 'Swamp Push Statue NW', 'Swamp Push Statue NE', - 'Swamp Drain Right Switch', 'Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW', + 'Swamp Drain Right Switch', 'Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW', 'Skull Pot Circle WN', 'Skull Pot Circle Star Path', 'Skull Pull Switch S', 'Skull Big Chest N', 'Skull Big Chest Hookpath', 'Skull 2 East Lobby NW', 'Skull Back Drop Star Path', 'Skull 2 West Lobby NW', 'Skull 3 Lobby EN', 'Skull Star Pits SW', 'Skull Star Pits ES', 'Skull Torch Room WN', 'Skull Vines NW', @@ -2077,7 +2088,7 @@ bunny_impassible_doors = { 'GT Double Switch Exit to Blue Barrier', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS', 'GT Mimics 2 NE', 'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN', - 'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', + 'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', 'GT Gauntlet 5 NW', 'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW', 'GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap', 'GT Validation Block Path' @@ -2227,7 +2238,7 @@ def create_key_rule(small_key_name, player, keys): def create_key_rule_allow_small(small_key_name, player, keys, location): loc = location.name - return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) + return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs): @@ -2238,7 +2249,7 @@ def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_ def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs): loc = location.name chest_names = [x.name for x in bk_locs] - return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys)) + return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys)) def create_advanced_key_rule(key_logic, player, rule): From 7030ff01be58dc914c2e8f4928d9dce74a5cd806 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 15 Nov 2023 13:25:13 -0700 Subject: [PATCH 055/123] fix(ER): Links house on Dm won't allow a cave to be used twice anymore --- RELEASENOTES.md | 4 +++- source/overworld/EntranceShuffle2.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5abff4a8..a2486806 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,7 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes -* * 1.2.0.22u +* 1.2.0.22u + * ? + * ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used) * Fixed logic issues: * Self-locking key not allowed in Sanctuary in standard (typo fixed) * More advanced bunny-walking logic in dungeons (multiple paths considred) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index da71c661..9f7ee90a 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -497,10 +497,14 @@ def do_links_house(entrances, exits, avail, cross_world): entrances.remove(chosen_dm_escape) entrances.remove(chosen_landing) else: - connect_entrance(chosen_dm_escape, chosen_cave.pop(0), avail) + chosen_cave_first = chosen_cave.pop(0) + connect_entrance(chosen_dm_escape, chosen_cave_first, avail) connect_exit(chosen_cave.pop(), chosen_landing, avail) entrances.remove(chosen_dm_escape) + avail.decoupled_exits.remove(chosen_cave_first) avail.decoupled_entrances.remove(chosen_landing) + # chosen cave has already been removed from exits + exits.add(chosen_cave_first) # this needs to be added back in if len(chosen_cave): exits.update([x for x in chosen_cave]) exits.update([x for item in multi_exit_caves for x in item]) @@ -1094,7 +1098,7 @@ def connect_entrance(entrancename, exit_name, avail): def connect_exit(exit_name, entrancename, avail): - world, player = avail.world, avail. player + world, player = avail.world, avail.player entrance = world.get_entrance(entrancename, player) exit = world.get_entrance(exit_name, player) From a50170c54f0ad5694888f4635ef456d015ee3e32 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 15 Nov 2023 14:33:11 -0700 Subject: [PATCH 056/123] fix: Flute doesn't work in rain state (even if pre-activated) except for glitched modes fix(msu): GTBK Music in DR --- RELEASENOTES.md | 5 +++-- Rom.py | 2 +- data/base2current.bps | Bin 94199 -> 90613 bytes 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a2486806..0c754ac6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,11 +110,12 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes * 1.2.0.22u - * ? + * Flute can't be activated in rain state (except glitched modes) (Thanks codemann!) * ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used) - * Fixed logic issues: + * Logic issues: * Self-locking key not allowed in Sanctuary in standard (typo fixed) * More advanced bunny-walking logic in dungeons (multiple paths considred) + * MSU: GTBK song fix for DR (Thanks codemann!) * 1.2.0.21u * Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory * Small fix for Tavern Shuffle (thanks Catobat) diff --git a/Rom.py b/Rom.py index 9351970c..637cac4b 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9b6e57f6e9d92934ce14276afd715849' +RANDOMIZERBASEHASH = '45778dbb52eca64611fbdb43a3a11ec8' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 643e68f74b79b1aa1e7712e5e48e71831c6eedc5..f5e4d204be7572897f98e8a1cfc4ad9c9c9f9dd6 100644 GIT binary patch delta 4867 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 delta 8559 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 Date: Wed, 15 Nov 2023 16:47:17 -0700 Subject: [PATCH 057/123] build: forgot to include lower case --- Rom.py | 2 +- data/base2current.bps | Bin 90613 -> 94262 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 637cac4b..11bf35ce 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '45778dbb52eca64611fbdb43a3a11ec8' +RANDOMIZERBASEHASH = '7ba119d84e56617f8da16669f8b519c1' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index f5e4d204be7572897f98e8a1cfc4ad9c9c9f9dd6..14084390990019b05bc8462cea1d18ba84db3938 100644 GIT binary patch delta 5268 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 A`v3p{ delta 1593 zcmW+!3vg3K6y29LP^86vu{70wX!!`(XbYrRCDaP)@JWOU%CB^aqE=c&pkUj#oA^jm zj7#%qwl#!Jg*MPMeGO^y5}Ohz0|lwVh`;f}#~JG|f;giN!wgQ`FgtVa-E+@5_wKwG zxS{;_ta4xz7yo46x2t{CocmG5hC|}ZDRU;*nOeSk^uWim<}H&Cro6-{N`4Y+)8-{; zi$~*++t;T0y3!UYmK^)dS#UsHwce`1mi$w{IIDz*Ne3UM8@}Z`t!tpca6rg5W)p0bZ2vSgak>w!Jo_Wu7<5%3T6pqug@;TVkTf2lyv(NKr?k=) z8*bC`)D9_2chxM`TJ8LlF$(mTXnfFd4>+pLyVaMREI~G&H`BfS-Hl*>L+n^S0F|#C+TM z?Nkq4U%K>0GCx4oMiT0BRNHGD)g#aGzLj3QWY5M70*W)#I!8@8Tw|Degg+-S3FE1F9r*%9LXFbb)Bz zy-1S8GAERZA{%#-yyyhyuQWnYmrqsMYK@&GhTQY>r6OfNPOn}EoGOo zg*&F~X7RZ41dDx24~qjzuaa)GidVlY%E0ei#Rb~2uDIpt-6QJHwDCutD8A{N`u&C3 zX=vNxKX&KdS+?k4R^BJ5!Zv*ok~hV1zLZqk#7W&p;;UTf^yihSh^)@PCl_RJA>|~c z_6wvSiuG1`Jd2}>M^?@K33aBy$LICR_;F5BObX#%L*v)$$dp!;-li0oN$g_?FT~`b`7)j zhnUze?m(SGjL`=M$p?luFI%HuTw!o!8<0%)+wEj05?AS=hsi*1%>N|l6Dy3k&@k+0 z=6#?)28P+*r|i!|95@$b8-!z6fKkJ-abgC<3L_dKqptbrtOA)&gI;ofEmt^09(@+E zkt2+=pEMbds)ab13|)H!8(=n&9e{GX(C%_-_Jt>f!spC{C=}R4dCI3XS5Vq}}Jnqep-ch~# wtNI~I{;A_~^Wv7cJAYNX$1}F=J6gKRP_%D Date: Sun, 19 Nov 2023 14:07:47 -0600 Subject: [PATCH 058/123] Added missing saved user settings in Adjust tab --- source/gui/bottom.py | 4 +++- source/gui/loadcliargs.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 4c21621e..839f2dc9 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -259,7 +259,9 @@ def create_guiargs(parent): "heartbeep": "heartbeep", "menuspeed": "fastmenu", "owpalettes": "ow_palettes", - "uwpalettes": "uw_palettes" + "uwpalettes": "uw_palettes", + "reduce_flashing": "reduce_flashing", + "shuffle_sfx": "shuffle_sfx" } for adjustarg in adjustargs: internal = adjustargs[adjustarg] diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index aec14a72..d99f6c9a 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -206,7 +206,9 @@ def loadadjustargs(gui, settings): "heartbeep": "adjust.heartbeep", "menuspeed": "adjust.menuspeed", "owpalettes": "adjust.owpalettes", - "uwpalettes": "adjust.uwpalettes" + "uwpalettes": "adjust.uwpalettes", + "reduce_flashing": "adjust.reduce_flashing", + "shuffle_sfx": "adjust.shuffle_sfx" } } } From b65c55e1b550aea854738533d67303a74ca1ea1c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 24 Nov 2023 16:38:21 -0600 Subject: [PATCH 059/123] Fix some issues with Smith return --- BaseClasses.py | 2 +- OverworldShuffle.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 08ccdd93..94287939 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1627,7 +1627,7 @@ class Entrance(object): explored_regions[region] = path for exit in region.exits: if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \ - and exit.connected_region.name not in ['Dig Game Area'] \ + and exit.name not in ['Dig Game To Ledge Drop'] \ and exit.access_rule(state): if exit.connected_region == destination: found = True diff --git a/OverworldShuffle.py b/OverworldShuffle.py index cd41b663..7c581fac 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1378,7 +1378,7 @@ def can_reach_smith(world, player): elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state): found = True return - elif exit.connected_region.name not in explored_regions: + elif exit.connected_region.name not in explored_regions and exit.name != "Dig Game To Ledge Drop": if (region.type == RegionType.Dungeon and exit.connected_region.name.endswith(' Portal')) \ or (exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ and exit.access_rule(blank_state)): From 98dc38e3ac12af7677ca55dc25d3c48b8f87ebed Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 24 Nov 2023 16:43:42 -0600 Subject: [PATCH 060/123] Some minor OWR corrections --- OverworldShuffle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 7c581fac..869f43ed 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1173,6 +1173,7 @@ def define_tile_groups(world, do_grouped, player): (lw_regions if id < 0x40 or id >= 0x80 else dw_regions).extend(OWTileRegions.inverse[id]) tile_groups.append((group, lw_regions, dw_regions)) + random.shuffle(tile_groups) return tile_groups, flipped_groups, nonflipped_groups, undefined_chance def remove_reserved(world, groupedlist, connected_edges, player): @@ -1623,7 +1624,7 @@ def validate_layout(world, player): or (entrance.name == 'Big Bomb Shop' and (world.mode[player] != 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ or (entrance.name == 'Ganons Tower' and (world.mode[player] != 'inverted' and not world.shuffle_ganon[player])) \ or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['insanity']) \ - or entrance.name == 'Tavern North': + or (entrance.name == 'Tavern North' and not world.shuffletavern[player]): continue # these are fixed entrances and cannot be used for gaining access to region if entrance.name not in drop_entrances \ and ((entrance.name in dungeon_entrances and world.shuffle[player] not in ['dungeonssimple', 'simple', 'restricted']) \ From d9e515429a24181525402bfbd78d29cc9b84ab30 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Dec 2023 05:29:11 -0600 Subject: [PATCH 061/123] Undo bunny walking change due to infinite loop --- Rules.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Rules.py b/Rules.py index 77677b9e..dc7463ac 100644 --- a/Rules.py +++ b/Rules.py @@ -1707,15 +1707,16 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - queue = deque([(region, [], {region})]) + seen = {region} + queue = deque([(region, [])]) while queue: - (current, path, seen) = queue.popleft() + (current, path) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: continue new_path = path + [entrance.access_rule] - new_seen = seen.union({new_region}) + seen.add(new_region) if not is_link(new_region): if world.logic[player] == 'owglitches': if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: @@ -1752,7 +1753,7 @@ def set_bunny_rules(world, player, inverted): else: continue if is_bunny(new_region): - queue.append((new_region, new_path, new_seen)) + queue.append((new_region, new_path)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) From a1b570da621fa79cfadc8e165762ec7d6b5d1c50 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Dec 2023 05:46:33 -0600 Subject: [PATCH 062/123] Added song instrument shuffle --- Adjuster.py | 3 +- AdjusterMain.py | 11 +- CLI.py | 3 +- Main.py | 5 +- Rom.py | 9 +- mystery_example.yml | 3 + mystery_testsuite.yml | 3 + resources/app/cli/args.json | 4 + resources/app/cli/lang/en.json | 1 + .../app/gui/adjust/overview/widgets.json | 3 +- resources/app/gui/lang/en.json | 2 + .../gui/randomize/gameoptions/widgets.json | 3 +- source/classes/CustomSettings.py | 1 + source/classes/SFX.py | 755 +++++++++++++++++- source/classes/constants.py | 1 + source/gui/adjust/overview.py | 2 + source/gui/bottom.py | 3 +- source/gui/loadcliargs.py | 3 +- source/tools/MysteryUtils.py | 1 + 19 files changed, 804 insertions(+), 12 deletions(-) diff --git a/Adjuster.py b/Adjuster.py index 9e003b3f..45cf37d3 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -36,7 +36,8 @@ def main(): parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true') - parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true') + parser.add_argument('--shuffle_sfx', help='Shuffles sfx instruments', action='store_true') + parser.add_argument('--shuffle_songinstruments', help='Shuffles sound sfx', action='store_true') parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in diff --git a/AdjusterMain.py b/AdjusterMain.py index c6f19680..a3a08924 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -10,6 +10,7 @@ except ImportError: from Utils import output_path from Rom import LocalRom, apply_rom_settings +from source.classes.SFX import output_song_data from source.tools.BPS import bps_read_vlv @@ -33,11 +34,14 @@ def adjust(args): apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, - args.msu_resume) + args.shuffle_songinstruments, args.msu_resume) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) + if args.shuffle_songinstruments: + output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfilebase) + logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) @@ -68,11 +72,14 @@ def patch(args): apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, - args.msu_resume) + args.shuffle_songinstruments, args.msu_resume) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfile_base)) + if args.shuffle_songinstruments: + output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfile_base) + logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) diff --git a/CLI.py b/CLI.py index 72fc7a48..5087eabf 100644 --- a/CLI.py +++ b/CLI.py @@ -140,7 +140,7 @@ def parse_cli(argv, no_defaults=False): 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', - 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', + 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) @@ -258,6 +258,7 @@ def parse_settings(): "uw_palettes": "default", "reduce_flashing": False, "shuffle_sfx": False, + "shuffle_songinstruments": False, "msu_resume": False, "collection_rate": False, diff --git a/Main.py b/Main.py index a8fede2f..0fae0394 100644 --- a/Main.py +++ b/Main.py @@ -35,6 +35,7 @@ from source.item.FillUtil import create_item_pool_config, massage_item_pool, dis from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings +from source.classes.SFX import output_song_data version_number = '1.2.0.22' version_branch = '-u' @@ -393,7 +394,7 @@ def main(args, seed=None, fish=None): apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player], - args.shuffle_sfx[player], args.msu_resume[player]) + args.shuffle_sfx[player], args.shuffle_songinstruments[player], args.msu_resume[player]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches @@ -404,6 +405,8 @@ def main(args, seed=None, fish=None): if world.players > 1 or world.teams > 1: outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else '' + if args.shuffle_songinstruments: + output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfilebase) if args.bps: patchfile = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.bps') patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, rom.buffer) diff --git a/Rom.py b/Rom.py index 3a82aa7c..6095ace0 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from EntranceShuffle import door_addresses, exit_ids, ow_prize_table from OverworldShuffle import default_flute_connections, flute_data from InitialSram import InitialSram -from source.classes.SFX import randomize_sfx +from source.classes.SFX import randomize_sfx, randomize_songinstruments from source.item.FillUtil import valid_pot_items from source.dungeon.RoomList import Room0127 @@ -109,6 +109,9 @@ class LocalRom(object): self.patch_base_rom() self.orig_buffer = self.buffer.copy() + def read_byte(self, address): + return self.buffer[address] + def write_byte(self, address, value): self.buffer[address] = value @@ -1826,7 +1829,7 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, - ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, msu_resume): + ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, shuffle_songinstruments, msu_resume): if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer: dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380], @@ -1932,6 +1935,8 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr if shuffle_sfx: randomize_sfx(rom) + if shuffle_songinstruments: + randomize_songinstruments(rom) if isinstance(rom, LocalRom): rom.write_crc() diff --git a/mystery_example.yml b/mystery_example.yml index dbb455d7..4cf785b0 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -269,3 +269,6 @@ shuffle_sfx: on: 1 off: 1 + shuffle_songinstruments: + on: 1 + off: 1 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index d2355f4f..733adb6e 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -199,3 +199,6 @@ rom: shuffle_sfx: on: 1 off: 1 + shuffle_songinstruments: + on: 1 + off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 52edf0cd..8c21e15a 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -347,6 +347,10 @@ "action": "store_true", "type": "bool" }, + "shuffle_songinstruments": { + "action": "store_true", + "type": "bool" + }, "msu_resume": { "action": "store_true", "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 6d41b23d..7d028961 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -425,6 +425,7 @@ ], "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], + "shuffle_songinstruments": [ "Shuffle song instruments (default: %(default)s)" ], "msu_resume": [ "Enable MSU Resume (default: %(default)s)" ], "create_rom": [ "Create an output rom file. (default: %(default)s)" ], "gui": [ "Launch the GUI. (default: %(default)s)" ], diff --git a/resources/app/gui/adjust/overview/widgets.json b/resources/app/gui/adjust/overview/widgets.json index a24bf7b4..8489bcda 100644 --- a/resources/app/gui/adjust/overview/widgets.json +++ b/resources/app/gui/adjust/overview/widgets.json @@ -4,7 +4,8 @@ "msu_resume": { "type": "checkbox" }, "quickswap": { "type": "checkbox" }, "reduce_flashing": {"type": "checkbox" }, - "shuffle_sfx": {"type": "checkbox" } + "shuffle_sfx": {"type": "checkbox" }, + "shuffle_songinstruments": {"type": "checkbox" } }, "leftAdjustFrame": { "heartcolor": { diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 8a6a0212..36ffe2fb 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -4,6 +4,7 @@ "adjust.quickswap": "L/R Quickswapping", "adjust.reduce_flashing": "Reduce Flashing", "adjust.shuffle_sfx": "Shuffle Sound Effects", + "adjust.shuffle_songinstruments": "Shuffle Song Instruments", "adjust.msu_resume": "MSU Resume", "adjust.heartcolor": "Heart Color", @@ -190,6 +191,7 @@ "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", "randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects", + "randomizer.gameoptions.shuffle_songinstruments": "Shuffle Song Instruments", "randomizer.gameoptions.msu_resume": "MSU Resume", "randomizer.gameoptions.heartcolor": "Heart Color", diff --git a/resources/app/gui/randomize/gameoptions/widgets.json b/resources/app/gui/randomize/gameoptions/widgets.json index 5d8faeba..d6f4e76c 100644 --- a/resources/app/gui/randomize/gameoptions/widgets.json +++ b/resources/app/gui/randomize/gameoptions/widgets.json @@ -4,7 +4,8 @@ "msu_resume": { "type": "checkbox" }, "quickswap": { "type": "checkbox" }, "reduce_flashing": { "type": "checkbox" }, - "shuffle_sfx": { "type": "checkbox" } + "shuffle_sfx": { "type": "checkbox" }, + "shuffle_songinstruments": { "type": "checkbox" } }, "leftRomOptionsFrame": { "heartcolor": { diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 5ec40409..a3402318 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -183,6 +183,7 @@ class CustomSettings(object): args.ow_palettes[p] = get_setting(settings['ow_palettes'], args.ow_palettes[p]) args.uw_palettes[p] = get_setting(settings['uw_palettes'], args.uw_palettes[p]) args.shuffle_sfx[p] = get_setting(settings['shuffle_sfx'], args.shuffle_sfx[p]) + args.shuffle_songinstruments[p] = get_setting(settings['shuffle_songinstruments'], args.shuffle_songinstruments[p]) args.msu_resume[p] = get_setting(settings['msu_resume'], args.msu_resume[p]) def get_item_pool(self): diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 750b284b..e9f41d5d 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -1,9 +1,9 @@ +from aenum import IntEnum import random from Utils import int16_as_bytes, snes_to_pc class SFX(object): - def __init__(self, name, sfx_set, orig_id, addr, chain, accomp=False): self.name = name self.sfx_set = sfx_set @@ -16,6 +16,68 @@ class SFX(object): self.target_id = None self.target_chain = None +class MusicType(IntEnum): + NONE = 0x00, + Ambient = 0x01, + Melody = 0x02, + Rhythm = 0x04, + Beat = 0x08 + +class Instrument(object): + def __init__(self, name, id, srcn, adsr, gain, mult): + self.name = name + self.id = id + self.srcn = srcn + self.adsr = adsr + self.gain = gain + self.mult = mult + + self.target_id = None + +class SFXInstrument(Instrument): + pass + +class SPCInstrument(Instrument): + def __init__(self, name, srcn, adsr, gain, mult, min=9999, max=0): + Instrument.__init__(self, name, srcn, srcn, adsr, gain, mult) + + self.type = MusicType.NONE + self.primary_type = MusicType.NONE + self.replacements = [] + + def add_type(self, type, force_primary): + if force_primary or self.type == MusicType.NONE: + self.primary_type |= type + self.type |= type + + def amb(self, force_primary=False): + self.add_type(MusicType.Ambient, force_primary) + return self + + def mel(self, force_primary=False): + self.add_type(MusicType.Melody, force_primary) + return self + + def bass(self, force_primary=False): + self.add_type(MusicType.Rhythm, force_primary) + return self + + def beat(self, force_primary=False): + self.add_type(MusicType.Beat, force_primary) + return self + +class InstrumentChange(object): + def __init__(self, song_id, segment_id, tracks, orig_instrument, type=MusicType.NONE, ban=[]): + self.song_id = song_id + self.segment_id = segment_id + self.tracks = tracks + self.orig_instrument = orig_instrument + + self.type = type + self.banned_list = ban + + self.target_instrument = None + def init_sfx_data(): sfx_pool = [SFX('Slash1', 0x02, 0x01, 0x2614, []), SFX('Slash2', 0x02, 0x02, 0x2625, []), @@ -178,3 +240,694 @@ def randomize_sfx(rom): last = chained rom.write_byte(ac_base + last - 1, 0) + +def output_song_data(rom, filepath, outfilebase): + with open(filepath, 'w') as outfile: + last_song = 0 + last_segment = -1 + outfile.write(f'{outfilebase}\n') + for change in spc_instrument_changes: + if last_song != change.song_id: + last_song = change.song_id + last_segment = change.segment_id + outfile.write(f'\nSong{change.song_id:02X}.{change.segment_id:02X}.') + elif last_segment != change.segment_id: + last_segment = change.segment_id + outfile.write(f'\n {change.segment_id:02X}.') + else: + outfile.write(f'\n ') + tracks = 8 + for track_id in change.tracks.keys(): + outfile.write(f'{track_id:01X}') + tracks -= 1 + if tracks > 0: + outfile.write(' ' * tracks) + outfile.write(f' = {rom.read_byte(snes_to_pc(next(iter(change.tracks.values()))[0])):02X}') + + +def randomize_songinstruments(rom): + # categorize instruments in pools + ambients, melodies, rhythms, beats = [], [], [], [] + inst_lists = { + MusicType.Ambient: ambients, + MusicType.Melody: melodies, + MusicType.Rhythm: rhythms, + MusicType.Beat: beats + } + for instrument in spc_instruments.values(): + for ins, lst in inst_lists.items(): + if instrument.type & ins > 0: + lst.append(instrument) + for instrument in spc_instruments.values(): + for ins, lst in inst_lists.items(): + if instrument.primary_type & ins > 0: + instrument.replacements += [i for i in lst if i not in instrument.replacements] + + # randomize each instrument change + for change in spc_instrument_changes: + if type(change.type) is list: + candidates = [spc_instruments[i] for i in change.type] + elif change.type != MusicType.NONE: + candidates = [] + for ins, lst in inst_lists.items(): + if change.type & ins > 0: + candidates += [i for i in lst if i not in candidates] + else: + candidates = spc_instruments[change.orig_instrument].replacements + candidates = [i for i in candidates if i.id not in change.banned_list] + change.target_instrument = random.choice(candidates).id + + for change in spc_instrument_changes: + for track_addresses in change.tracks.values(): + for addr in track_addresses: + rom.write_byte(snes_to_pc(addr), change.target_instrument) + + +sfx_instruments = { # table @ $1A9C04 + 0x00: SFXInstrument("Fwoosh", 0x00, 0x00, [0xF6, 0x6A], 0xB8, 0x03), + 0x01: SFXInstrument("Swish", 0x01, 0x01, [0x8E, 0xE0], 0xB8, 0x02), + 0x02: SFXInstrument("Bomp", 0x02, 0x14, [0xFE, 0x6A], 0xB8, 0x02), + 0x03: SFXInstrument("Ting", 0x03, 0x03, [0xFE, 0xF8], 0xB8, 0x0D), + 0x04: SFXInstrument("Rrrrr", 0x04, 0x04, [0xFE, 0x6A], 0x7F, 0x03), + 0x05: SFXInstrument("Clunk", 0x05, 0x02, [0xFE, 0x6A], 0x7F, 0x03), + 0x06: SFXInstrument("Ching", 0x06, 0x05, [0xFE, 0x6A], 0x70, 0x03), + 0x07: SFXInstrument("Fwomp", 0x07, 0x06, [0xFE, 0x6A], 0x70, 0x03), + 0x08: SFXInstrument("Squee", 0x08, 0x08, [0xFA, 0x6A], 0x70, 0x03), + 0x09: SFXInstrument("Unused", 0x09, 0x06, [0xFE, 0x6A], 0x70, 0x01), + 0x0A: SFXInstrument("Bzzzrt", 0x0A, 0x07, [0xFE, 0x6A], 0x70, 0x05), + 0x0B: SFXInstrument("Brrfft", 0x0B, 0x0B, [0xFE, 0x6A], 0xB8, 0x03), + 0x0C: SFXInstrument("Brrwwww", 0x0C, 0x0C, [0xFE, 0xE0], 0xB8, 0x02), + 0x0D: SFXInstrument("Twee", 0x0D, 0x0D, [0xF9, 0x6E], 0xB8, 0x03), + 0x0E: SFXInstrument("Pwing", 0x0E, 0x0E, [0xFE, 0xF5], 0xB8, 0x07), + 0x0F: SFXInstrument("Pling", 0x0F, 0x0F, [0xFE, 0xF5], 0xB8, 0x06), + 0x10: SFXInstrument("Chshtsh", 0x10, 0x01, [0xFE, 0xFC], 0xB8, 0x03), + 0x11: SFXInstrument("Splssh", 0x11, 0x10, [0x8E, 0xE0], 0xB8, 0x03), + 0x12: SFXInstrument("Weewoo", 0x12, 0x08, [0x8E, 0xE0], 0xB8, 0x02), + 0x13: SFXInstrument("Brbrbrb", 0x13, 0x14, [0x8E, 0xE0], 0xB8, 0x02), + 0x14: SFXInstrument("Bwow", 0x14, 0x0A, [0x88, 0xE0], 0xB8, 0x02), + 0x15: SFXInstrument("Uughf", 0x15, 0x17, [0x8E, 0xE0], 0xB8, 0x02), + 0x16: SFXInstrument("Aaaaaa", 0x16, 0x15, [0xFF, 0xE0], 0xB8, 0x04), + 0x17: SFXInstrument("Twing", 0x17, 0x03, [0xDF, 0x11], 0xB8, 0x0F), + 0x18: SFXInstrument("Whooo", 0x18, 0x01, [0x88, 0xE0], 0xB8, 0x01) +} + +spc_instruments = { # table @ $19FB1C + 0x00: SPCInstrument("Noise", 0x00, [0xFF, 0xE0], 0xB8, 0x0470), + 0x01: SPCInstrument("Rain", 0x01, [0xFF, 0xE0], 0xB8, 0x0790).amb(), + 0x02: SPCInstrument("Timpani", 0x02, [0xFF, 0xE0], 0xB8, 0x09C0).beat(), + 0x03: SPCInstrument("Square Wave", 0x03, [0xFF, 0xE0], 0xB8, 0x0400).bass().mel(), + 0x04: SPCInstrument("Saw Wave", 0x04, [0xFF, 0xE0], 0xB8, 0x0400).bass(), + 0x05: SPCInstrument("Clink", 0x05, [0xFF, 0xE0], 0xB8, 0x0470).amb(), + 0x06: SPCInstrument("Wobbly Lead", 0x06, [0xFF, 0xE0], 0xB8, 0x0470).amb(), + 0x07: SPCInstrument("Compound Saw", 0x07, [0xFF, 0xE0], 0xB8, 0x0470), + 0x08: SPCInstrument("Tweet", 0x08, [0xFF, 0xE0], 0xB8, 0x07A0).amb(), + 0x09: SPCInstrument("Strings A", 0x09, [0x8F, 0xE9], 0xB8, 0x01E0).mel().bass(True), + 0x0A: SPCInstrument("Strings B", 0x0A, [0x8A, 0xE9], 0xB8, 0x01E0).mel().bass(True), + 0x0B: SPCInstrument("Trombone", 0x0B, [0xFF, 0xE0], 0xB8, 0x0300).mel().bass(True).beat(), + 0x0C: SPCInstrument("Cymbal", 0x0C, [0xFF, 0xE0], 0xB8, 0x03A0).beat(), + 0x0D: SPCInstrument("Ocarina", 0x0D, [0xFF, 0xE0], 0xB8, 0x0100).bass(), + 0x0E: SPCInstrument("Chimes", 0x0E, [0xFF, 0xEF], 0xB8, 0x0EA0).amb(), + 0x0F: SPCInstrument("Harp", 0x0F, [0xFF, 0xEF], 0xB8, 0x0600).mel().bass(True).beat(), + 0x10: SPCInstrument("Splash", 0x10, [0xFF, 0xE0], 0xB8, 0x03D0).amb().beat(), + 0x11: SPCInstrument("Trumpet", 0x11, [0x8F, 0xE0], 0xB8, 0x0300).mel().bass(True), + 0x12: SPCInstrument("Horn", 0x12, [0x8F, 0xE0], 0xB8, 0x06F0).mel().bass(True), + 0x13: SPCInstrument("Snare A", 0x13, [0xFD, 0xE0], 0xB8, 0x07A0).beat(), + 0x14: SPCInstrument("Snare B", 0x14, [0xFF, 0xE0], 0xB8, 0x07A0).beat(), + 0x15: SPCInstrument("Choir", 0x15, [0xFF, 0xE0], 0xB8, 0x03D0).mel().bass(True), + 0x16: SPCInstrument("Flute", 0x16, [0x8F, 0xE0], 0xB8, 0x0300).mel().bass(True), + 0x17: SPCInstrument("Oof", 0x17, [0xFF, 0xE0], 0xB8, 0x02C0).amb().beat(True), + 0x18: SPCInstrument("Piano", 0x18, [0xFE, 0x8F], 0xB8, 0x06F0).mel().bass(True) +} + +Me = MusicType.Melody +Rh = MusicType.Rhythm +Be = MusicType.Beat +Am = MusicType.Ambient + +spc_instrument_changes = [ + InstrumentChange(0x01, 0x00, {0x00: [0x1A9F5B], + 0x01: [0x1A9F9D], + 0x02: [0x1A9FBB], + 0x03: [0x1A9FDA], + 0x04: [0x1A9FE8]}, 0x0F), + InstrumentChange(0x01, 0x01, {0x00: [0x1ACA1A], + 0x01: [0x1ACA39], + 0x02: [0x1ACA5E], + 0x07: [0x1ACC01]}, 0x0B), + InstrumentChange(0x01, 0x01, {0x03: [0x1ACAA3], + 0x04: [0x1ACAE2]}, 0x11), + InstrumentChange(0x01, 0x01, {0x05: [0x1ACB25, 0x1ACC78]}, 0x02), + InstrumentChange(0x01, 0x01, {0x05: [0x1ACB3A, 0x1ACC51], + 0x06: [0x1ACBA9, 0x1ACC7D]}, 0x13), + InstrumentChange(0x01, 0x01, {0x06: [0x1ACB94, 0x1ACCA3]}, 0x0C), + + InstrumentChange(0x02, 0x00, {0x00: [0x1AA04B], + 0x03: [0x1AA10E], + 0x04: [0x1AA143], + 0x07: [0x1AA1D1]}, 0x0B), + InstrumentChange(0x02, 0x00, {0x01: [0x1AA087], + 0x05: [0x1AA176]}, 0x11), + InstrumentChange(0x02, 0x00, {0x02: [0x1AA0CC], + 0x06: [0x1AA1BF]}, 0x13), + InstrumentChange(0x02, 0x00, {0x06: [0x1AA1C7]}, 0x0C), + InstrumentChange(0x02, 0x01, {0x02: [0x1AA27B]}, 0x13), + InstrumentChange(0x02, 0x01, {0x03: [0x1AA2A2]}, 0x0A), + InstrumentChange(0x02, 0x01, {0x04: [0x1AA2CD]}, 0x02), + InstrumentChange(0x02, 0x01, {0x05: [0x1AA2E0], + 0x07: [0x1AA34D]}, 0x0B), + InstrumentChange(0x02, 0x02, {0x00: [0x1AA5A8], + 0x05: [0x1AA449]}, 0x0B), + InstrumentChange(0x02, 0x02, {0x03: [0x1AA3FF], + 0x04: [0x1AA42A]}, 0x0A), + InstrumentChange(0x02, 0x02, {0x06: [0x1AA49E, 0x1AA4CB]}, 0x13), + InstrumentChange(0x02, 0x02, {0x06: [0x1AA4C0, 0x1AA4EA]}, 0x0C), + InstrumentChange(0x02, 0x02, {0x06: [0x1AA752]}, 0x02), + + InstrumentChange(0x03, 0x00, {0x00: [0x1AA84A], + 0x01: [0x1AA864], + 0x03: [0x1AA885]}, 0x0A), + InstrumentChange(0x03, 0x01, {0x00: [0x1AA89E], + 0x01: [0x1AA8B8], + 0x03: [0x1AA8D9]}, 0x12), + InstrumentChange(0x03, 0x01, {0x02: [0x1AAB86], + 0x04: [0x1AA8F2], + 0x05: [0x1AA93C]}, 0x0A), + + InstrumentChange(0x04, 0x00, {0x00: [0x1AACA3], + 0x01: [0x1AACB1], + 0x02: [0x1AACC7]}, 0x12), + InstrumentChange(0x04, 0x01, {0x00: [0x1AABF5], + 0x01: [0x1AAC0B]}, 0x12), + InstrumentChange(0x04, 0x01, {0x02: [0x1AAC21]}, 0x12), + InstrumentChange(0x04, 0x02, {0x01: [0x1AAC55], + 0x02: [0x1AAC6B]}, 0x12), + InstrumentChange(0x04, 0x03, {0x00: [0x1AAD93], + 0x01: [0x1AACED], + 0x02: [0x1AAD07], + 0x03: [0x1AAD75], + 0x04: [0x1AADB1]}, 0x12), + + InstrumentChange(0x05, 0x00, {0x00: [0x1AAE3F], + 0x02: [0x1AAE72], + 0x03: [0x1AAEAD], + 0x07: [0x1AAF02]}, 0x0A), + InstrumentChange(0x05, 0x00, {0x01: [0x1AAE64]}, 0x09), + InstrumentChange(0x05, 0x00, {0x04: [0x1AAEE8]}, 0x16), + InstrumentChange(0x05, 0x01, {0x00: [0x1AB156], + 0x03: [0x1AAF48], + 0x04: [0x1AAF71], + 0x07: [0x1AB1D3]}, 0x0A), + InstrumentChange(0x05, 0x01, {0x02: [0x1AB186]}, 0x16), + InstrumentChange(0x05, 0x02, {0x00: [0x1AB088], + 0x03: [0x1AB0CF], + 0x04: [0x1AB0F8], + 0x07: [0x1AB11F]}, 0x0A), + InstrumentChange(0x05, 0x02, {0x02: [0x1AB0A7]}, 0x16), + + InstrumentChange(0x06, 0x00, {0x00: [0x1AB338]}, 0x0A), + InstrumentChange(0x06, 0x01, {0x01: [0x1AB68F], + 0x02: [0x1AB69D], + 0x03: [0x1AB6B2], + 0x05: [0x1AB3D7]}, 0x0A), + InstrumentChange(0x06, 0x02, {0x00: [0x1AB622], + 0x01: [0x1AB63A], + 0x02: [0x1AB648], + 0x03: [0x1AB670], + 0x04: [0x1AB6C4], + 0x05: [0x1AB49D]}, 0x0A), + InstrumentChange(0x06, 0x03, {0x05: [0x1AB548]}, 0x0A), + InstrumentChange(0x06, 0x04, {0x00: [0x1AB722], + 0x01: [0x1AB739], + 0x02: [0x1AB745], + 0x03: [0x1AB759], + 0x04: [0x1AB765], + 0x05: [0x1AB5E3]}, 0x0A), + + InstrumentChange(0x07, 0x00, {0x00: [0x1ABB1F]}, 0x0A), + InstrumentChange(0x07, 0x00, {0x01: [0x1ABB31], + 0x02: [0x1ABBE6], + 0x03: [0x1ABC0B]}, 0x09), + InstrumentChange(0x07, 0x00, {0x04: [0x1ABB53, 0x1AB8C9]}, 0x16), + InstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x06]), + InstrumentChange(0x07, 0x01, {0x05: [0x1AB8EB]}, 0x0A), + InstrumentChange(0x07, 0x02, {0x04: [0x1AB981]}, 0x16), + InstrumentChange(0x07, 0x03, {0x02: [0x1ABC37], + 0x03: [0x1ABC66]}, 0x09), + InstrumentChange(0x07, 0x03, {0x04: [0x1ABA09]}, 0x16), + InstrumentChange(0x07, 0x03, {0x05: [0x1ABC8F]}, 0x0A), + InstrumentChange(0x07, 0x05, {0x02: [0x1ABC9D], + 0x03: [0x1ABCBA]}, 0x09), + InstrumentChange(0x07, 0x05, {0x05: [0x1ABCD1]}, 0x0A), + InstrumentChange(0x07, 0x06, {0x00: [0x1ABB72]}, 0x0A), + InstrumentChange(0x07, 0x06, {0x01: [0x1ABB83], + 0x02: [0x1ABB95], + 0x03: [0x1ABBAA]}, 0x09), + InstrumentChange(0x07, 0x06, {0x04: [0x1ABBBD]}, 0x16), + InstrumentChange(0x07, 0x06, {0x05: [0x1ABCE6]}, 0x0A), + + InstrumentChange(0x08, 0x00, {0x00: [0x1ABD3A], + 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am), + InstrumentChange(0x08, 0x00, {0x02: [0x1ABD70], + 0x05: [0x1ABE06]}, 0x0F), + InstrumentChange(0x08, 0x00, {0x03: [0x1ABDAC]}, 0x0A), + InstrumentChange(0x08, 0x00, {0x04: [0x1ABDC8]}, 0x01), + InstrumentChange(0x08, 0x00, {0x06: [0x1ABE3A]}, 0x09), + + InstrumentChange(0x09, 0x00, {0x00: [0x1AC25A], + 0x05: [0x1AC28E]}, 0x0A), + InstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14), + InstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14), + InstrumentChange(0x09, 0x01, {0x06: [0x1ABF43]}, 0x09), + InstrumentChange(0x09, 0x02, {0x00: [0x1AC450], + 0x05: [0x1AC56D], + 0x07: [0x1AC595]}, 0x0A), + InstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14), + InstrumentChange(0x09, 0x02, {0x03: [0x1AC4B3], + 0x04: [0x1AC510]}, 0x11), + InstrumentChange(0x09, 0x02, {0x06: [0x1AC2E9]}, 0x09), + InstrumentChange(0x09, 0x03, {0x00: [0x1ABF63]}, 0x0A), + InstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14), + InstrumentChange(0x09, 0x03, {0x03: [0x1ABFA4]}, 0x11), + InstrumentChange(0x09, 0x03, {0x05: [0x1AC01C]}, 0x16), + InstrumentChange(0x09, 0x04, {0x00: [0x1AC04D]}, 0x0A), + InstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14), + InstrumentChange(0x09, 0x04, {0x02: [0x1AC5CF]}, 0x18), + InstrumentChange(0x09, 0x04, {0x03: [0x1AC085], + 0x04: [0x1AC5ED]}, 0x11), + InstrumentChange(0x09, 0x04, {0x05: [0x1AC137]}, 0x16), + InstrumentChange(0x09, 0x04, {0x06: [0x1AC146]}, 0x12), + InstrumentChange(0x09, 0x05, {0x00: [0x1AC178], + 0x07: [0x1AC229]}, 0x0A), + InstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14), + InstrumentChange(0x09, 0x05, {0x02: [0x1AC19E]}, 0x18), + InstrumentChange(0x09, 0x05, {0x03: [0x1AC1D3], + 0x04: [0x1AC1F4]}, 0x12), + InstrumentChange(0x09, 0x06, {0x00: [0x1AC317], + 0x07: [0x1AC3ED]}, 0x0A), + InstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14), + InstrumentChange(0x09, 0x06, {0x02: [0x1AC33A]}, 0x18), + InstrumentChange(0x09, 0x06, {0x03: [0x1AC36F], + 0x04: [0x1AC3A4], + 0x05: [0x1AC3D9]}, 0x12), + InstrumentChange(0x09, 0x07, {0x00: [0x1AC40A], + 0x05: [0x1AC43C]}, 0x0A), + InstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14), + InstrumentChange(0x09, 0x07, {0x02: [0x1AC492]}, 0x18), + InstrumentChange(0x09, 0x07, {0x03: [0x1AC680], + 0x04: [0x1AC6C1]}, 0x11), + + InstrumentChange(0x0A, 0x00, {0x00: [0x1AC72F], + 0x01: [0x1AC751], + 0x02: [0x1AC772], + 0x03: [0x1AC793]}, 0x0F), + InstrumentChange(0x0A, 0x00, {0x00: [0x1AC740], + 0x01: [0x1AC765], + 0x02: [0x1AC786], + 0x03: [0x1AC7A7], + 0x04: [0x1AC7B4, 0x1AC7C9], + 0x05: [0x1AC7D1]}, 0x09), + + InstrumentChange(0x0B, 0x00, {0x00: [0x1A9EEC, 0x1A9D26]}, 0x0F, ban=[0x0B]), + InstrumentChange(0x0B, 0x01, {0x01: [0x1A9D3F], + 0x02: [0x1A9D5A], + 0x03: [0x1A9D75], + 0x04: [0x1A9D90], + 0x05: [0x1A9DBB], + 0x06: [0x1A9DE9]}, 0x0F), + + InstrumentChange(0x0C, 0x00, {0x00: [0x1AC83A], + 0x01: [0x1AC84A], + 0x02: [0x1AC857], + 0x03: [0x1AC864], + 0x04: [0x1AC871], + 0x05: [0x1AC87E]}, 0x0B), + InstrumentChange(0x0C, 0x01, {0x02: [0x1AC89A], + 0x03: [0x1AC8AD]}, 0x11), + InstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E), + InstrumentChange(0x0C, 0x01, {0x05: [0x1AC8C3]}, 0x02), + InstrumentChange(0x0C, 0x02, {0x02: [0x1AC8E0], + 0x03: [0x1AC8F3]}, 0x11), + InstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E), + InstrumentChange(0x0C, 0x02, {0x05: [0x1AC909]}, 0x02), + + InstrumentChange(0x0D, 0x00, {0x00: [0x1AD003], + 0x03: [0x1AD02C], + 0x04: [0x1AD03A]}, 0x11), + InstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02), + InstrumentChange(0x0D, 0x00, {0x02: [0x1AD07F]}, 0x14), + InstrumentChange(0x0D, 0x01, {0x00: [0x1ACD10], + 0x04: [0x1ACD9A]}, 0x0B), + InstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02), + InstrumentChange(0x0D, 0x01, {0x03: [0x1ACD7F], + 0x05: [0x1ACDCA]}, 0x11), + InstrumentChange(0x0D, 0x03, {0x00: [0x1ACE8E], + 0x01: [0x1ACEB8]}, 0x0A), + InstrumentChange(0x0D, 0x03, {0x02: [0x1ACED4]}, 0x14), + InstrumentChange(0x0D, 0x03, {0x03: [0x1ACEE0], + 0x04: [0x1ACF07]}, 0x11), + InstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02), + + InstrumentChange(0x0E, 0x00, {0x00: [0x1AD29C]}, 0x16), + InstrumentChange(0x0E, 0x00, {0x01: [0x1AD2AD], + 0x03: [0x1AD2CB], + 0x04: [0x1AD2D9]}, 0x18), + InstrumentChange(0x0E, 0x00, {0x02: [0x1AD2BB]}, 0x12), + InstrumentChange(0x0E, 0x01, {0x00: [0x1AD1A6]}, 0x16), + InstrumentChange(0x0E, 0x01, {0x01: [0x1AD1AF], + 0x03: [0x1AD1CD]}, 0x18), + InstrumentChange(0x0E, 0x01, {0x02: [0x1AD1C5]}, 0x0A), + InstrumentChange(0x0E, 0x02, {0x00: [0x1AD24C]}, 0x16), + InstrumentChange(0x0E, 0x02, {0x01: [0x1AD255], + 0x03: [0x1AD273]}, 0x18), + InstrumentChange(0x0E, 0x02, {0x02: [0x1AD26B]}, 0x0A), + InstrumentChange(0x0E, 0x03, {0x00: [0x1AD1E4], + 0x01: [0x1AD1ED], + 0x03: [0x1AD212], + 0x04: [0x1AD22F]}, 0x18), + InstrumentChange(0x0E, 0x03, {0x02: [0x1AD20A]}, 0x12), + + InstrumentChange(0x10, 0x00, {0x00: [0x1B816D], + 0x02: [0x1B81A0], + 0x03: [0x1B81C0], + 0x07: [0x1B827F]}, 0x0B), + InstrumentChange(0x10, 0x00, {0x01: [0x1B818C]}, 0x11), + InstrumentChange(0x10, 0x00, {0x04: [0x1B81E0], + 0x05: [0x1B8220, 0x1B8229]}, 0x02), + InstrumentChange(0x10, 0x00, {0x05: [0x1B8224]}, 0x0C), + InstrumentChange(0x10, 0x00, {0x06: [0x1B825F]}, 0x16), + InstrumentChange(0x10, 0x01, {0x00: [0x1B811F]}, 0x0B), + InstrumentChange(0x10, 0x01, {0x03: [0x1B813B], + 0x05: [0x1B814E]}, 0x0A), + InstrumentChange(0x10, 0x02, {0x00: [0x1B829E], + 0x05: [0x1B8310]}, 0x0B), + InstrumentChange(0x10, 0x02, {0x01: [0x1B82C0], + 0x03: [0x1B82E3], + 0x06: [0x1B8342]}, 0x0A), + InstrumentChange(0x10, 0x02, {0x04: [0x1B8308], + 0x04: [0x1B83F2]}, 0x02), + InstrumentChange(0x10, 0x03, {0x00: [0x1B8360], + 0x02: [0x1B83AD], + 0x05: [0x1B83FA]}, 0x0B), + InstrumentChange(0x10, 0x03, {0x01: [0x1B8396], + 0x03: [0x1B83CC], + 0x06: [0x1B842E]}, 0x0A), + InstrumentChange(0x10, 0x04, {0x00: [0x1B844D], + 0x02: [0x1B84A0]}, 0x0B), + InstrumentChange(0x10, 0x04, {0x01: [0x1B847B], + 0x03: [0x1B84CD]}, 0x0A), + InstrumentChange(0x10, 0x04, {0x04: [0x1B84ED], + 0x05: [0x1B84F5]}, 0x02), + InstrumentChange(0x10, 0x05, {0x00: [0x1B8548], + 0x01: [0x1B8575], + 0x03: [0x1B85B6]}, 0x0A), + InstrumentChange(0x10, 0x05, {0x02: [0x1B859B], + 0x07: [0x1B867F]}, 0x0B), + InstrumentChange(0x10, 0x05, {0x04: [0x1B85D0], + 0x05: [0x1B862C]}, 0x02), + InstrumentChange(0x10, 0x06, {0x02: [0x1B8726], + 0x06: [0x1B8745]}, 0x0A), + InstrumentChange(0x10, 0x07, {0x00: [0x1B8768]}, 0x0B), + InstrumentChange(0x10, 0x07, {0x00: [0x1B8B04], + 0x01: [0x1B8B10], + 0x02: [0x1B8775], + 0x06: [0x1B87AD]}, 0x0A), + InstrumentChange(0x10, 0x07, {0x05: [0x1B8792]}, 0x16), + InstrumentChange(0x10, 0x08, {0x02: [0x1B86B3]}, 0x0A), + InstrumentChange(0x10, 0x09, {0x00: [0x1B8A63], + 0x05: [0x1B8879], + 0x06: [0x1B8AED]}, 0x0B), + InstrumentChange(0x10, 0x09, {0x01: [0x1B8A87], + 0x02: [0x1B87E5]}, 0x0A), + InstrumentChange(0x10, 0x0A, {0x00: [0x1B88C8], + 0x02: [0x1B8946], + 0x03: [0x1B88E5]}, 0x0B), + InstrumentChange(0x10, 0x0A, {0x01: [0x1B892C], + 0x07: [0x1B8905]}, 0x0A), + InstrumentChange(0x10, 0x0A, {0x04: [0x1B897D]}, 0x02), + + InstrumentChange(0x11, 0x00, {0x00: [0x1B8C95], + 0x01: [0x1B8CA2], + 0x02: [0x1B8CB1], + 0x03: [0x1B8CC0], + 0x04: [0x1B8CCF]}, 0x0A, type=Me, ban=[0x0D]), + InstrumentChange(0x11, 0x01, {0x05: [0x1B8CFF]}, 0x0A, ban=[0x04]), + InstrumentChange(0x11, 0x02, {0x03: [0x1B8D6B]}, 0x09), + InstrumentChange(0x11, 0x04, {0x04: [0x1B8E2B]}, 0x11), + InstrumentChange(0x11, 0x05, {0x05: [0x1B90F6]}, 0x11), + + InstrumentChange(0x12, 0x00, {0x00: [0x1B9275], + 0x01: [0x1B9282], + 0x05: [0x1B92DE]}, 0x0A), + InstrumentChange(0x12, 0x00, {0x02: [0x1B9290], + 0x03: [0x1B92AB]}, 0x11), + InstrumentChange(0x12, 0x00, {0x04: [0x1B92C6]}, 0x02), + InstrumentChange(0x12, 0x00, {0x05: [0x1B92D3]}, 0x10, type=[0x08, 0x10, 0x17]), + InstrumentChange(0x12, 0x01, {0x00: [0x1B917D], + 0x01: [0x1B918A], + 0x05: [0x1B91E6], + 0x06: [0x1B9229]}, 0x0A), + InstrumentChange(0x12, 0x01, {0x02: [0x1B9198], + 0x03: [0x1B91B3]}, 0x11), + InstrumentChange(0x12, 0x01, {0x04: [0x1B91CE]}, 0x02), + InstrumentChange(0x12, 0x01, {0x05: [0x1B91DB], + 0x06: [0x1B921E]}, 0x10, type=[0x08, 0x10, 0x17]), + InstrumentChange(0x12, 0x02, {0x00: [0x1B9313], + 0x01: [0x1B9320], + 0x05: [0x1B937C], + 0x06: [0x1B93BF]}, 0x0A), + InstrumentChange(0x12, 0x02, {0x02: [0x1B932E], + 0x03: [0x1B9349]}, 0x11), + InstrumentChange(0x12, 0x02, {0x04: [0x1B9364]}, 0x02), + InstrumentChange(0x12, 0x02, {0x05: [0x1B9371], + 0x06: [0x1B93B4]}, 0x10, type=[0x08, 0x10, 0x17]), + + InstrumentChange(0x13, 0x00, {0x00: [0x1B9458], + 0x02: [0x1B94DA], + 0x03: [0x1B953E], + 0x04: [0x1B95A2], + 0x05: [0x1B9606]}, 0x0B), + InstrumentChange(0x13, 0x00, {0x01: [0x1B94A4]}, 0x11), + InstrumentChange(0x13, 0x00, {0x06: [0x1B9650], + 0x07: [0x1B9696]}, 0x0F), + InstrumentChange(0x13, 0x00, {0x06: [0x1B967B], + 0x07: [0x1B96C0]}, 0x02), + + InstrumentChange(0x14, 0x00, {0x00: [0x1B9901, 0x1B97A8]}, 0x15), + InstrumentChange(0x14, 0x01, {0x01: [0x1B97C4], + 0x02: [0x1B97DE], + 0x03: [0x1B97FD], + 0x04: [0x1B9813], + 0x05: [0x1B982A]}, 0x15), + + InstrumentChange(0x15, 0x00, {0x00: [0x1B9A32], + 0x01: [0x1B9A50], + 0x02: [0x1B9A6D], + 0x03: [0x1B9A8A]}, 0x0B), + InstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02), + InstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02), + InstrumentChange(0x15, 0x01, {0x01: [0x1B9984], + 0x02: [0x1B99AA], + 0x03: [0x1B99D7]}, 0x0B), + InstrumentChange(0x15, 0x01, {0x04: [0x1B9A04]}, 0x14), + InstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02), + InstrumentChange(0x15, 0x02, {0x01: [0x1B9B58], + 0x02: [0x1B9B7E], + 0x03: [0x1B9BAB]}, 0x0B), + InstrumentChange(0x15, 0x02, {0x04: [0x1B9BD8]}, 0x14), + + InstrumentChange(0x16, 0x00, {0x00: [0x1B9CE6], + 0x01: [0x1B9CF1]}, 0x09), + InstrumentChange(0x16, 0x01, {0x00: [0x1B9C82], + 0x01: [0x1B9C8B], + 0x02: [0x1B9C93], + 0x03: [0x1B9CAA]}, 0x09), + InstrumentChange(0x16, 0x01, {0x04: [0x1B9CBA]}, 0x09), + InstrumentChange(0x16, 0x02, {0x00: [0x1B9CFB], + 0x01: [0x1B9D44]}, 0x09), + InstrumentChange(0x16, 0x03, {0x00: [0x1B9DBE], + 0x01: [0x1B9E47], + 0x05: [0x1B9EE4]}, 0x09), + + InstrumentChange(0x17, 0x00, {0x00: [0x1BA287], + 0x03: [0x1BA26A]}, 0x0E, type=Am|Rh, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + InstrumentChange(0x17, 0x01, {0x01: [0x1BA20F], + 0x02: [0x1BA231], + 0x03: [0x1BA24F]}, 0x0E, type=Am|Rh, ban=[0x01, 0x05, 0x06, 0x10]), + + InstrumentChange(0x19, 0x00, {0x00: [0x1BA476], + 0x01: [0x1BA49C], + 0x02: [0x1BA4C1], + 0x03: [0x1BA4D5], + 0x04: [0x1BA4F4], + 0x05: [0x1BA513]}, 0x0A), + InstrumentChange(0x19, 0x01, {0x00: [0x1BA357], + 0x01: [0x1BA379], + 0x03: [0x1BA38C], + 0x04: [0x1BA39D]}, 0x0A), + InstrumentChange(0x19, 0x02, {0x02: [0x1BA3D3]}, 0x15), + InstrumentChange(0x19, 0x03, {0x00: [0x1BA3F8]}, 0x16), + InstrumentChange(0x19, 0x03, {0x02: [0x1BA40D]}, 0x0A), + + InstrumentChange(0x1A, 0x00, {0x00: [0x1BA70E, 0x1BA71A], + 0x01: [0x1BA729], + 0x02: [0x1BA743], + 0x03: [0x1BA763], + 0x04: [0x1BA783], + 0x05: [0x1BA7A2], + 0x06: [0x1BA7B9], + 0x07: [0x1BA7D0]}, 0x0E), + InstrumentChange(0x1A, 0x01, {0x00: [0x1BA5C1], + 0x05: [0x1BA68F], + 0x06: [0x1BA6A6], + 0x07: [0x1BA6DD]}, 0x0A), + + InstrumentChange(0x1B, 0x00, {0x00: [0x1BAA55], + 0x01: [0x1BAA66], + 0x02: [0x1BAA75], + 0x03: [0x1BAA86], + 0x04: [0x1BAA97]}, 0x0F), + InstrumentChange(0x1B, 0x01, {0x00: [0x1BA956], + 0x01: [0x1BA96F], + 0x02: [0x1BA98A], + 0x03: [0x1BA9A5], + 0x04: [0x1BA9C0], + 0x05: [0x1BA9EB], + 0x06: [0x1BAA19]}, 0x0F), + + InstrumentChange(0x1C, 0x00, {0x00: [0x1BACB6, 0x1BABAD]}, 0x09), + InstrumentChange(0x1C, 0x01, {0x01: [0x1BABC7], + 0x02: [0x1BABE0], + 0x03: [0x1BABF4], + 0x04: [0x1BAC0B]}, 0x09), + InstrumentChange(0x1C, 0x02, {0x00: [0x1BAC26], + 0x01: [0x1BAC44], + 0x02: [0x1BAC61]}, 0x09), + + InstrumentChange(0x1D, 0x00, {0x00: [0x1BACEA], + 0x01: [0x1BAD06], + 0x02: [0x1BAD23], + 0x03: [0x1BAD40], + 0x04: [0x1BAD5D]}, 0x0B), + + InstrumentChange(0x1E, 0x00, {0x00: [0x1BB14D], + 0x01: [0x1BB169], + 0x02: [0x1BB17E], + 0x03: [0x1BB193], + 0x04: [0x1BB1A8]}, 0x09), + InstrumentChange(0x1E, 0x00, {0x05: [0x1BB1BD]}, 0x02), + + InstrumentChange(0x1F, 0x00, {0x00: [0x1BAE86], + 0x03: [0x1BAEC6], + 0x04: [0x1BAEE4]}, 0x0B), + InstrumentChange(0x1F, 0x00, {0x00: [0x1BAE9C]}, 0x18), + InstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13), + InstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02), + InstrumentChange(0x1F, 0x01, {0x03: [0x1BAE15], + 0x04: [0x1BAE32], + 0x05: [0x1BAE4F]}, 0x11), + InstrumentChange(0x1F, 0x02, {0x01: [0x1BAF2C]}, 0x0B), + InstrumentChange(0x1F, 0x02, {0x03: [0x1BAF53], + 0x04: [0x1BAF69], + 0x05: [0x1BAF7F]}, 0x11), + InstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13), + + InstrumentChange(0x20, 0x00, {0x00: [0x1AD49A], + 0x01: [0x1AD4BA], + 0x02: [0x1AD4D3], + 0x03: [0x1AD4EE], + 0x04: [0x1AD507]}, 0x0A), + InstrumentChange(0x20, 0x01, {0x05: [0x1AD475]}, 0x18), + + InstrumentChange(0x21, 0x00, {0x00: [0x1AE8E3], + 0x01: [0x1AF18E], + 0x02: [0x1AF1A9], + 0x03: [0x1AF1B7], + 0x04: [0x1AF1D2], + 0x05: [0x1AF1E4], + 0x06: [0x1AF201], + 0x07: [0x1AF21C]}, 0x0F), + InstrumentChange(0x21, 0x01, {0x00: [0x1AE518]}, 0x12), + InstrumentChange(0x21, 0x01, {0x01: [0x1AE540], + 0x03: [0x1AE58D]}, 0x0B), + InstrumentChange(0x21, 0x01, {0x02: [0x1AE564]}, 0x09), + InstrumentChange(0x21, 0x01, {0x04: [0x1AE5B1], + 0x05: [0x1AE5D4]}, 0x0A), + InstrumentChange(0x21, 0x01, {0x06: [0x1AE5EB, 0x1AE60F]}, 0x02), + InstrumentChange(0x21, 0x01, {0x06: [0x1AE5F9], + 0x07: [0x1AE61B]}, 0x0C), + InstrumentChange(0x21, 0x02, {0x03: [0x1AE651], + 0x05: [0x1AE69A]}, 0x0A), + InstrumentChange(0x21, 0x03, {0x00: [0x1AEC5E], + 0x01: [0x1AEC71], + 0x02: [0x1AEC90]}, 0x0A), + InstrumentChange(0x21, 0x04, {0x01: [0x1AECB6]}, 0x0A), + InstrumentChange(0x21, 0x04, {0x07: [0x1AF3A3]}, 0x0F), + InstrumentChange(0x21, 0x05, {0x00: [0x1AE6D5], + 0x01: [0x1AE6FD], + 0x03: [0x1AE748], + 0x04: [0x1AE75E]}, 0x0A), + InstrumentChange(0x21, 0x05, {0x02: [0x1AE72A]}, 0x09), + InstrumentChange(0x21, 0x06, {0x00: [0x1AE774]}, 0x0A), + InstrumentChange(0x21, 0x06, {0x02: [0x1AE7BE], + 0x04: [0x1AE825]}, 0x09), + InstrumentChange(0x21, 0x07, {0x00: [0x1AED48], + 0x01: [0x1AED7B], + 0x03: [0x1AEDCD], + 0x04: [0x1AEDF4]}, 0x0A), + InstrumentChange(0x21, 0x07, {0x02: [0x1AEDB1]}, 0x09), + InstrumentChange(0x21, 0x08, {0x00: [0x1AE876], + 0x01: [0x1AE881]}, 0x0A), + InstrumentChange(0x21, 0x0A, {0x02: [0x1AF17C], + 0x03: [0x1AE8BB]}, 0x09), + InstrumentChange(0x21, 0x0B, {0x00: [0x1AEE0F]}, 0x11), + InstrumentChange(0x21, 0x0B, {0x01: [0x1AEE22], + 0x03: [0x1AEE46], + 0x04: [0x1AEE74]}, 0x0A), + InstrumentChange(0x21, 0x0C, {0x01: [0x1AEECD], + 0x03: [0x1AEEF9], + 0x04: [0x1AEF2B]}, 0x0A), + InstrumentChange(0x21, 0x0D, {0x00: [0x1AE954], + 0x03: [0x1AE9D4], + 0x04: [0x1AEA03]}, 0x18), + InstrumentChange(0x21, 0x0D, {0x00: [0x1AE971]}, 0x12), + InstrumentChange(0x21, 0x0D, {0x01: [0x1AE983]}, 0x09), + InstrumentChange(0x21, 0x0D, {0x01: [0x1AE9A6], + 0x03: [0x1AE9F3]}, 0x0B), + InstrumentChange(0x21, 0x0D, {0x02: [0x1AE9B6]}, 0x0A), + InstrumentChange(0x21, 0x0E, {0x00: [0x1AEA4F]}, 0x12), + InstrumentChange(0x21, 0x0E, {0x01: [0x1AEA84], + 0x03: [0x1AEAE7], + 0x05: [0x1AEB94]}, 0x0B), + InstrumentChange(0x21, 0x0E, {0x02: [0x1AEAAF]}, 0x09), + InstrumentChange(0x21, 0x0E, {0x04: [0x1AEB69]}, 0x0A), + InstrumentChange(0x21, 0x0E, {0x06: [0x1AEC03]}, 0x02), + InstrumentChange(0x21, 0x0E, {0x07: [0x1AEC3C]}, 0x0C), + + InstrumentChange(0x22, 0x00, {0x00: [0x1ADA2D], + 0x01: [0x1ADA41], + 0x02: [0x1ADA51], + 0x03: [0x1ADA6C], + 0x04: [0x1ADA83]}, 0x0A), + InstrumentChange(0x22, 0x01, {0x05: [0x1ADAC9]}, 0x0A), + InstrumentChange(0x22, 0x02, {0x07: [0x1ADB60]}, 0x0A), + InstrumentChange(0x22, 0x03, {0x06: [0x1ADC10]}, 0x16), + InstrumentChange(0x22, 0x05, {0x05: [0x1AD7CC]}, 0x09), + InstrumentChange(0x22, 0x05, {0x06: [0x1AD803]}, 0x11), + InstrumentChange(0x22, 0x05, {0x07: [0x1AD81B]}, 0x0A), + InstrumentChange(0x22, 0x06, {0x02: [0x1AD8A6]}, 0x11), + InstrumentChange(0x22, 0x06, {0x03: [0x1AD959]}, 0x13), + InstrumentChange(0x22, 0x06, {0x06: [0x1AD9B7]}, 0x16), + InstrumentChange(0x22, 0x07, {0x00: [0x1ADCCD]}, 0x0B), + InstrumentChange(0x22, 0x07, {0x02: [0x1ADD73]}, 0x11), + InstrumentChange(0x22, 0x07, {0x04: [0x1ADDE0], + 0x07: [0x1ADE9E]}, 0x0A), + InstrumentChange(0x22, 0x07, {0x05: [0x1ADE14]}, 0x12), + InstrumentChange(0x22, 0x07, {0x06: [0x1ADE62]}, 0x16), + InstrumentChange(0x22, 0x08, {0x00: [0x1ADF02], + 0x04: [0x1ADFA8]}, 0x0B), + InstrumentChange(0x22, 0x08, {0x01: [0x1ADF2A]}, 0x11), + InstrumentChange(0x22, 0x08, {0x02: [0x1ADF83], + 0x05: [0x1ADFED]}, 0x09), + InstrumentChange(0x22, 0x08, {0x06: [0x1AE02D], + 0x07: [0x1AE047]}, 0x0A) +] \ No newline at end of file diff --git a/source/classes/constants.py b/source/classes/constants.py index b41d03a6..e3fbf1b1 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -139,6 +139,7 @@ SETTINGSTOPROCESS = { "uwpalettes": "uw_palettes", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_songinstruments": "shuffle_songinstruments", 'msu_resume': 'msu_resume', }, "generation": { diff --git a/source/gui/adjust/overview.py b/source/gui/adjust/overview.py index f7c6faf2..967a9780 100644 --- a/source/gui/adjust/overview.py +++ b/source/gui/adjust/overview.py @@ -107,6 +107,7 @@ def adjust_page(top, parent, settings): "reduce_flashing": "reduce_flashing", 'msu_resume': 'msu_resume', "shuffle_sfx": "shuffle_sfx", + "shuffle_songinstruments": "shuffle_songinstruments", } guiargs = Namespace() for option in options: @@ -158,6 +159,7 @@ def adjust_page(top, parent, settings): "nobgm": "disablemusic", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_songinstruments": "shuffle_songinstruments", "msu_resume": "msu_resume" } guiargs = Namespace() diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 839f2dc9..c53842d7 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -261,7 +261,8 @@ def create_guiargs(parent): "owpalettes": "ow_palettes", "uwpalettes": "uw_palettes", "reduce_flashing": "reduce_flashing", - "shuffle_sfx": "shuffle_sfx" + "shuffle_sfx": "shuffle_sfx", + "shuffle_songinstruments": "shuffle_songinstruments" } for adjustarg in adjustargs: internal = adjustargs[adjustarg] diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index d99f6c9a..069b1fbd 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -208,7 +208,8 @@ def loadadjustargs(gui, settings): "owpalettes": "adjust.owpalettes", "uwpalettes": "adjust.uwpalettes", "reduce_flashing": "adjust.reduce_flashing", - "shuffle_sfx": "adjust.shuffle_sfx" + "shuffle_sfx": "adjust.shuffle_sfx", + "shuffle_songinstruments": "adjust.shuffle_songinstruments" } } } diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 12266f22..afaf6108 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -219,6 +219,7 @@ def roll_settings(weights): ret.ow_palettes = get_choice('ow_palettes', romweights) ret.uw_palettes = get_choice('uw_palettes', romweights) ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on' + ret.shuffle_songinstruments = get_choice('shuffle_songinstruments', romweights) == 'on' ret.msu_resume = get_choice('msu_resume', romweights) == 'on' return ret From 257eabb6c67dc2965f83bca01e9198225a6f76a3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Dec 2023 06:30:12 -0600 Subject: [PATCH 063/123] Version bump 0.3.3.2 --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 869f43ed..a7380f47 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.3.1' +version_number = '0.3.3.2' # branch indicator is intentionally different across branches version_branch = '-u' From bcd311980bae16261f356fdf95aad915fbbc841c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Dec 2023 15:45:02 -0600 Subject: [PATCH 064/123] aenum import hotfix --- source/classes/SFX.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index e9f41d5d..4b6aa4ed 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -1,4 +1,4 @@ -from aenum import IntEnum +from enum import IntEnum import random from Utils import int16_as_bytes, snes_to_pc From e955fc02d879af919656483defd4ee8281e83cd9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Dec 2023 13:04:24 -0600 Subject: [PATCH 065/123] Implement Return Old Man already in starting inventory --- InitialSram.py | 6 +++++- ItemList.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/InitialSram.py b/InitialSram.py index d7d79a18..a33f2454 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -114,6 +114,9 @@ class InitialSram: equip[0x37B] = 1 equip[0x36E] = 0x80 + if startingstate.has('Return Old Man', player): + self._initial_sram_bytes[0x410] = self._initial_sram_bytes[0x410] | 0x01 + for item in world.precollected_items: if item.player != player: continue @@ -123,7 +126,8 @@ class InitialSram: 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', - 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', + 'Return Old Man']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), diff --git a/ItemList.py b/ItemList.py index fcbffadf..21d3b446 100644 --- a/ItemList.py +++ b/ItemList.py @@ -227,6 +227,12 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if 'Return Old Man' in list(map(str, [i for i in world.precollected_items if i.player == player])): + old_man = world.get_location('Old Man', player) + world.push_item(old_man, ItemFactory('Nothing', player), False) + old_man.forced_item = old_man.item + old_man.skip = True + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) From a6f244cd01ac82bccffdafcfe0fb501828159553 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Dec 2023 13:04:24 -0600 Subject: [PATCH 066/123] Implement Return Old Man already in starting inventory --- InitialSram.py | 6 +++++- ItemList.py | 6 ++++++ Rules.py | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/InitialSram.py b/InitialSram.py index d7d79a18..a33f2454 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -114,6 +114,9 @@ class InitialSram: equip[0x37B] = 1 equip[0x36E] = 0x80 + if startingstate.has('Return Old Man', player): + self._initial_sram_bytes[0x410] = self._initial_sram_bytes[0x410] | 0x01 + for item in world.precollected_items: if item.player != player: continue @@ -123,7 +126,8 @@ class InitialSram: 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', - 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', + 'Return Old Man']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), diff --git a/ItemList.py b/ItemList.py index fcbffadf..21d3b446 100644 --- a/ItemList.py +++ b/ItemList.py @@ -227,6 +227,12 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if 'Return Old Man' in list(map(str, [i for i in world.precollected_items if i.player == player])): + old_man = world.get_location('Old Man', player) + world.push_item(old_man, ItemFactory('Nothing', player), False) + old_man.forced_item = old_man.item + old_man.skip = True + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) diff --git a/Rules.py b/Rules.py index dc7463ac..e8b8a5fa 100644 --- a/Rules.py +++ b/Rules.py @@ -1541,6 +1541,7 @@ def standard_rules(world, player): world.get_entrance('Uncle S&Q', player).hide_path = True set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.has('Zelda Delivered', player)) + add_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.has('Zelda Delivered', player)) # these are because of rails if world.shuffle[player] != 'vanilla': # where ever these happen to be From 5983629d6b803f97495b7da0c4665fbc97fb1394 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 7 Dec 2023 13:24:47 -0600 Subject: [PATCH 067/123] Fixed issue with Starting Inventory not printing to Spoiler --- Main.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Main.py b/Main.py index 0fae0394..8c8e3a16 100644 --- a/Main.py +++ b/Main.py @@ -204,6 +204,13 @@ def main(args, seed=None, fish=None): if item: world.push_precollected(item) + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -221,12 +228,6 @@ def main(args, seed=None, fish=None): adjust_locations(world, player) place_bosses(world, player) - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) if args.print_custom_yaml: world.settings.record_info(world) From f954907af867ee85787dde183e7421a80ffe230f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 9 Dec 2023 16:22:25 -0600 Subject: [PATCH 068/123] Implement Beat Agahnim 1 already in starting inventory --- InitialSram.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/InitialSram.py b/InitialSram.py index a33f2454..7f0f2c89 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -115,7 +115,13 @@ class InitialSram: equip[0x36E] = 0x80 if startingstate.has('Return Old Man', player): - self._initial_sram_bytes[0x410] = self._initial_sram_bytes[0x410] | 0x01 + self._initial_sram_bytes[0x410] |= 0x01 + + if startingstate.has('Beat Agahnim 1', player): + if world.mode[player] == 'standard': + self._initial_sram_bytes[0x3C5] = 0x80 + else: + self._initial_sram_bytes[0x3C5] = 0x03 for item in world.precollected_items: if item.player != player: @@ -127,7 +133,7 @@ class InitialSram: 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', - 'Return Old Man']: + 'Return Old Man', 'Beat Agahnim 1']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), From 1d0045624b2b59dad574dd72ddaabf3ff0605128 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 12 Dec 2023 23:28:34 -0600 Subject: [PATCH 069/123] Remove song instruments output file --- AdjusterMain.py | 7 ------- Main.py | 3 --- source/classes/SFX.py | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/AdjusterMain.py b/AdjusterMain.py index a3a08924..2bb071f3 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -10,7 +10,6 @@ except ImportError: from Utils import output_path from Rom import LocalRom, apply_rom_settings -from source.classes.SFX import output_song_data from source.tools.BPS import bps_read_vlv @@ -39,9 +38,6 @@ def adjust(args): output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) - if args.shuffle_songinstruments: - output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfilebase) - logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) @@ -77,9 +73,6 @@ def patch(args): output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfile_base)) - if args.shuffle_songinstruments: - output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfile_base) - logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) diff --git a/Main.py b/Main.py index 8c8e3a16..bda0e6c9 100644 --- a/Main.py +++ b/Main.py @@ -35,7 +35,6 @@ from source.item.FillUtil import create_item_pool_config, massage_item_pool, dis from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -from source.classes.SFX import output_song_data version_number = '1.2.0.22' version_branch = '-u' @@ -406,8 +405,6 @@ def main(args, seed=None, fish=None): if world.players > 1 or world.teams > 1: outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else '' - if args.shuffle_songinstruments: - output_song_data(rom, output_path('OR_SPCInstruments.txt'), outfilebase) if args.bps: patchfile = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.bps') patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, rom.buffer) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 4b6aa4ed..08085aff 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -263,7 +263,7 @@ def output_song_data(rom, filepath, outfilebase): if tracks > 0: outfile.write(' ' * tracks) outfile.write(f' = {rom.read_byte(snes_to_pc(next(iter(change.tracks.values()))[0])):02X}') - + def randomize_songinstruments(rom): # categorize instruments in pools From 9820f79b0b90af1c0561da581e3e429a481624c1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 12 Dec 2023 23:29:12 -0600 Subject: [PATCH 070/123] Fixed minor incorrect Entrance data --- source/overworld/EntranceShuffle2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 4c96c10a..7e34f711 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -2659,7 +2659,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000), 0x00), 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000), 0x00), 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), - 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000), 0x00), From 159dbcd8dba2c3c1c188c19911682adf24e8ca11 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 13 Dec 2023 14:02:43 -0600 Subject: [PATCH 071/123] Replacing Save Settings on Exit with Settings on Load --- CLI.py | 8 ++- Gui.py | 51 ++++++++++--------- resources/app/cli/args.json | 8 +-- resources/app/gui/lang/en.json | 8 +-- .../app/gui/randomize/generation/widgets.json | 8 +-- source/classes/constants.py | 2 +- source/gui/bottom.py | 9 ++++ 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/CLI.py b/CLI.py index 5087eabf..4cd17834 100644 --- a/CLI.py +++ b/CLI.py @@ -355,7 +355,7 @@ def parse_settings(): }, "randomSprite": False, "outputpath": os.path.join("."), - "saveonexit": "ask", + "settingsonload": "saved", "outputname": "", "startinventoryarray": {}, "notes": "" @@ -367,6 +367,12 @@ def parse_settings(): # read saved settings file if it exists and set these settings_path = os.path.join(".", "resources", "user", "settings.json") settings = apply_settings_file(settings, settings_path) + if settings["settingsonload"] == "saved": + settings_path = os.path.join(".", "resources", "user", "saved.json") + settings = apply_settings_file(settings, settings_path) + elif settings["settingsonload"] == "lastused": + settings_path = os.path.join(".", "resources", "user", "last.json") + settings = apply_settings_file(settings, settings_path) return settings # Priority fallback is: diff --git a/Gui.py b/Gui.py index a0f4726f..4aecfa03 100755 --- a/Gui.py +++ b/Gui.py @@ -37,19 +37,31 @@ def check_python_version(fish): messagebox.showinfo("Door Shuffle " + ESVersion, fish.translate("cli","cli","old.python.version") % sys.version) -def guiMain(args=None): - # Save settings to file - def save_settings(args): - user_resources_path = os.path.join(".", "resources", "user") - settings_path = os.path.join(user_resources_path) - if not os.path.exists(settings_path): - os.makedirs(settings_path) - for widget in self.pages["adjust"].content.widgets: - args["adjust." + widget] = self.pages["adjust"].content.widgets[widget].storageVar.get() - with open(os.path.join(settings_path, "settings.json"), "w+") as f: - f.write(json.dumps(args, indent=2)) - os.chmod(os.path.join(settings_path, "settings.json"),0o755) +# Save settings to file +def save_settings(gui, args, filename): + user_resources_path = os.path.join(".", "resources", "user") + settings_path = os.path.join(user_resources_path) + if not os.path.exists(settings_path): + os.makedirs(settings_path) + output_args = {} + settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", + "rom", "enemizercli", "outputpath"] + if filename == "settings.json": + for s in settings: + output_args[s] = args[s] + for widget in gui.pages["adjust"].content.widgets: + output_args["adjust." + widget] = gui.pages["adjust"].content.widgets[widget].storageVar.get() + else: + for k, v in args.items(): + if k not in settings and not k.startswith("adjust."): + output_args[k] = v + with open(os.path.join(settings_path, filename), "w+") as f: + f.write(json.dumps(output_args, indent=2)) + os.chmod(os.path.join(settings_path, filename),0o755) + +def guiMain(args=None): # Save settings from GUI def save_settings_from_gui(confirm): gui_args = vars(create_guiargs(self)) @@ -57,23 +69,14 @@ def guiMain(args=None): gui_args['sprite'] = 'random' elif gui_args['sprite']: gui_args['sprite'] = gui_args['sprite'].name - save_settings(gui_args) + save_settings(self, gui_args, "saved.json") + save_settings(self, gui_args, "settings.json") if confirm: messagebox.showinfo("Overworld Shuffle " + ORVersion, "Settings saved from GUI.") # routine for exiting the app def guiExit(): - skip_exit = False - if self.settings['saveonexit'] == 'ask': - dosave = messagebox.askyesnocancel("Overworld Shuffle " + ORVersion, "Save settings before exit?") - if dosave: - save_settings_from_gui(True) - if dosave is None: - skip_exit = True - elif self.settings['saveonexit'] == 'always': - save_settings_from_gui(False) - if not skip_exit: - sys.exit(0) + sys.exit(0) # make main window # add program title & version number diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 8c21e15a..e723ee76 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -575,11 +575,11 @@ "action": "store_true", "type": "bool" }, - "saveonexit": { + "settingsonload": { "choices": [ - "ask", - "always", - "never" + "default", + "saved", + "lastused" ] }, "outputname": {}, diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 36ffe2fb..0ae493d2 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -236,10 +236,10 @@ "randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.print_custom_yaml": "Print Customizer File", - "randomizer.generation.saveonexit": "Save Settings on Exit", - "randomizer.generation.saveonexit.ask": "Ask Me", - "randomizer.generation.saveonexit.always": "Always", - "randomizer.generation.saveonexit.never": "Never", + "randomizer.generation.settingsonload": "Settings On Load", + "randomizer.generation.settingsonload.default": "Default", + "randomizer.generation.settingsonload.saved": "Saved", + "randomizer.generation.settingsonload.lastused": "Last Used", "randomizer.generation.rom": "Base Rom: ", "randomizer.generation.rom.button": "Select Rom", diff --git a/resources/app/gui/randomize/generation/widgets.json b/resources/app/gui/randomize/generation/widgets.json index 22f9decc..a410f227 100644 --- a/resources/app/gui/randomize/generation/widgets.json +++ b/resources/app/gui/randomize/generation/widgets.json @@ -1,11 +1,11 @@ { "widgets": { - "saveonexit": { + "settingsonload": { "type": "selectbox", "options": [ - "ask", - "always", - "never" + "default", + "saved", + "lastused" ] } } diff --git a/source/classes/constants.py b/source/classes/constants.py index e3fbf1b1..c8e15532 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -148,7 +148,7 @@ SETTINGSTOPROCESS = { "createrom": "create_rom", "calcplaythrough": "calc_playthrough", "print_custom_yaml": "print_custom_yaml", - "saveonexit": "saveonexit" + "settingsonload": "settingsonload" } }, "startinventory": { diff --git a/source/gui/bottom.py b/source/gui/bottom.py index c53842d7..3d37908e 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -68,6 +68,15 @@ def bottom_frame(self, parent, args=None): self.widgets[key].pack(side=LEFT) def generateRom(): + guiargs = create_guiargs(parent) + argsDump = vars(guiargs) + from Gui import save_settings + if parent.randomSprite.get(): + argsDump['sprite'] = 'random' + elif argsDump['sprite']: + argsDump['sprite'] = argsDump['sprite'].name + save_settings(parent, argsDump, "last.json") + guiargs = create_guiargs(parent) # get default values for missing parameters for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): From 0e0df126777039ccaae01c8923013de069a3eddf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 14 Dec 2023 15:12:48 -0600 Subject: [PATCH 072/123] Added new option to export pre-seed customizer template yaml --- CLI.py | 5 ++- Gui.py | 4 +- Main.py | 24 +++++------ docs/Customizer.md | 6 ++- resources/app/gui/lang/en.json | 1 + .../gui/randomize/generation/checkboxes.json | 1 + source/classes/CustomSettings.py | 41 ++++++++++++------- source/classes/constants.py | 1 + 8 files changed, 51 insertions(+), 32 deletions(-) diff --git a/CLI.py b/CLI.py index 4cd17834..e0039b65 100644 --- a/CLI.py +++ b/CLI.py @@ -34,7 +34,9 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--settingsfile', help="input json file of settings", type=str) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--customizer', help='input yaml file for customizations', type=str) - parser.add_argument('--print_custom_yaml', help='print example yaml for current settings', + parser.add_argument('--print_template_yaml', help='print example yaml for current settings', + default=False, action="store_true") + parser.add_argument('--print_custom_yaml', help='print example plando yaml for current settings and placements', default=False, action="store_true") parser.add_argument('--mystery', dest="mystery", default=False, action="store_true") @@ -94,6 +96,7 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str) parser.add_argument('--customizer', dest="customizer", help='input yaml file for customizations', type=str) + parser.add_argument('--print_template_yaml', dest="print_template_yaml", default=False, action="store_true") parser.add_argument('--print_custom_yaml', dest="print_custom_yaml", default=False, action="store_true") if player_num: diff --git a/Gui.py b/Gui.py index 4aecfa03..0dcdaa75 100755 --- a/Gui.py +++ b/Gui.py @@ -45,8 +45,8 @@ def save_settings(gui, args, filename): os.makedirs(settings_path) output_args = {} settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", - "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", - "rom", "enemizercli", "outputpath"] + "calc_playthrough", "skip_playthrough", "print_template_yaml", "print_custom_yaml", + "settingsonload", "rom", "enemizercli", "outputpath"] if filename == "settings.json": for s in settings: output_args[s] = args[s] diff --git a/Main.py b/Main.py index bda0e6c9..afcb1568 100644 --- a/Main.py +++ b/Main.py @@ -183,8 +183,6 @@ def main(args, seed=None, fish=None): for player, name in enumerate(team, 1): world.player_names[player].append(name) logger.info('') - world.settings = CustomSettings() - world.settings.create_from_world(world, args) outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' @@ -210,6 +208,12 @@ def main(args, seed=None, fish=None): if item: world.push_precollected(item) + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + if args.print_template_yaml: + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}_template.yaml')) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -227,9 +231,6 @@ def main(args, seed=None, fish=None): adjust_locations(world, player) place_bosses(world, player) - if args.print_custom_yaml: - world.settings.record_info(world) - if any(world.potshuffle.values()): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) for player in range(1, world.players + 1): @@ -247,8 +248,6 @@ def main(args, seed=None, fish=None): update_world_regions(world, player) mark_light_dark_world_regions(world, player) create_dynamic_exits(world, player) - if args.print_custom_yaml: - world.settings.record_overworld(world) init_districts(world) @@ -262,8 +261,6 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_doors_prep(world, player) - if args.print_custom_yaml: - world.settings.record_entrances(world) create_item_pool_config(world) logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons")) @@ -271,8 +268,6 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_doors(world, player) mark_light_dark_world_regions(world, player) - if args.print_custom_yaml: - world.settings.record_doors(world) logger.info(world.fish.translate("cli", "cli", "generating.itempool")) @@ -300,8 +295,6 @@ def main(args, seed=None, fish=None): lock_shop_locations(world, player) massage_item_pool(world) - if args.print_custom_yaml: - world.settings.record_item_pool(world) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) fill_prizes(world) @@ -351,6 +344,11 @@ def main(args, seed=None, fish=None): ensure_good_pots(world, True) if args.print_custom_yaml: + world.settings.record_info(world) + world.settings.record_overworld(world) + world.settings.record_entrances(world) + world.settings.record_doors(world) + world.settings.record_item_pool(world) world.settings.record_item_placements(world) world.settings.write_to_file(output_path(f'{outfilebase}_custom.yaml')) diff --git a/docs/Customizer.md b/docs/Customizer.md index 872003c5..3f3b31e9 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -6,11 +6,13 @@ This can also be used to roll a mystery or mutli-mystery seed via the GUI. [Exam The cli includes a couple arguments to help: +`--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. -Present on the GUI as `Print Customizer File` and `Customizer File` on the Generation Setup tab. +Present on the GUI as `Print Customizer Template`, `Print Customizer File`, and `Customizer File` on the Generation Setup tab. ### meta @@ -44,7 +46,7 @@ Start inventory is not supported here. It has a separate section. ###### Not Yet Implemented -Rom/Adjust flags like sprite, quickswap are not outputing with the print_custom_yaml settings +Rom/Adjust flags like sprite, quickswap are not outputing with the print_template_yaml or print_custom_yaml settings ### item_pool diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 0ae493d2..5d082636 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -234,6 +234,7 @@ "randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", + "randomizer.generation.print_template_yaml": "Print Customizer Template", "randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.settingsonload": "Settings On Load", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 6e377027..658ea7f0 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,6 +4,7 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, + "print_template_yaml":{ "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } } diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index a3402318..ba9c9422 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -262,10 +262,11 @@ class CustomSettings(object): self.world_rep['meta'] = meta_dict meta_dict['players'] = world.players meta_dict['algorithm'] = world.algorithm - meta_dict['seed'] = world.seed meta_dict['race'] = settings.race meta_dict['user_notes'] = settings.notes self.world_rep['settings'] = settings_dict + if world.precollected_items: + self.world_rep['start_inventory'] = start_inv = {} for p in self.player_range: settings_dict[p] = {} settings_dict[p]['ow_shuffle'] = world.owShuffle[p] @@ -323,6 +324,10 @@ class CustomSettings(object): settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p] settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p] settings_dict[p]['beemizer'] = world.beemizer[p] + if world.precollected_items: + start_inv[p] = [] + for item in world.precollected_items: + start_inv[item.player].append(item.name) # rom adjust stuff # settings_dict[p]['sprite'] = world.sprite[p] @@ -335,34 +340,42 @@ class CustomSettings(object): # settings_dict[p]['ow_palettes'] = world.ow_palettes[p] # settings_dict[p]['uw_palettes'] = world.uw_palettes[p] # settings_dict[p]['shuffle_sfx'] = world.shuffle_sfx[p] + # settings_dict[p]['shuffle_songinstruments'] = world.shuffle_songinstruments[p] # more settings? def record_info(self, world): + self.world_rep['meta']['seed'] = world.seed self.world_rep['bosses'] = bosses = {} - self.world_rep['start_inventory'] = start_inv = {} + self.world_rep['medallions'] = medallions = {} for p in self.player_range: bosses[p] = {} - start_inv[p] = [] + medallions[p] = {} for dungeon in world.dungeons: for level, boss in dungeon.bosses.items(): location = dungeon.name if level is None else f'{dungeon.name} ({level})' if boss and 'Agahnim' not in boss.name: bosses[dungeon.player][location] = boss.name - for item in world.precollected_items: - start_inv[item.player].append(item.name) - - def record_item_pool(self, world): - self.world_rep['item_pool'] = item_pool = {} - self.world_rep['medallions'] = medallions = {} - for p in self.player_range: - item_pool[p] = defaultdict(int) - medallions[p] = {} - for item in world.itempool: - item_pool[item.player][item.name] += 1 for p, req_medals in world.required_medallions.items(): medallions[p]['Misery Mire'] = req_medals[0] medallions[p]['Turtle Rock'] = req_medals[1] + def record_item_pool(self, world, use_custom_pool=False): + if not use_custom_pool or world.custom: + self.world_rep['item_pool'] = item_pool = {} + for p in self.player_range: + if not use_custom_pool or p in world.customitemarray: + item_pool[p] = defaultdict(int) + if use_custom_pool and world.custom: + import source.classes.constants as CONST + for p in world.customitemarray: + for i, c in world.customitemarray[p].items(): + if c > 0: + item = CONST.CUSTOMITEMLABELS[CONST.CUSTOMITEMS.index(i)] + item_pool[p][item] += c + else: + for item in world.itempool: + item_pool[item.player][item.name] += 1 + def record_item_placements(self, world): self.world_rep['placements'] = placements = {} for p in self.player_range: diff --git a/source/classes/constants.py b/source/classes/constants.py index c8e15532..6bf5f55e 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -147,6 +147,7 @@ SETTINGSTOPROCESS = { "createspoiler": "create_spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", + "print_template_yaml": "print_template_yaml", "print_custom_yaml": "print_custom_yaml", "settingsonload": "settingsonload" } From be18412cdd189c53e4699cf1dedc427d9fbcc9fa Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 15 Dec 2023 00:46:02 -0600 Subject: [PATCH 073/123] Some fixes to Song Instrument shuffle data --- source/classes/SFX.py | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 08085aff..825adef4 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -337,10 +337,10 @@ spc_instruments = { # table @ $19FB1C 0x02: SPCInstrument("Timpani", 0x02, [0xFF, 0xE0], 0xB8, 0x09C0).beat(), 0x03: SPCInstrument("Square Wave", 0x03, [0xFF, 0xE0], 0xB8, 0x0400).bass().mel(), 0x04: SPCInstrument("Saw Wave", 0x04, [0xFF, 0xE0], 0xB8, 0x0400).bass(), - 0x05: SPCInstrument("Clink", 0x05, [0xFF, 0xE0], 0xB8, 0x0470).amb(), + 0x05: SPCInstrument("Clink", 0x05, [0xFF, 0xE0], 0xB8, 0x0470), 0x06: SPCInstrument("Wobbly Lead", 0x06, [0xFF, 0xE0], 0xB8, 0x0470).amb(), 0x07: SPCInstrument("Compound Saw", 0x07, [0xFF, 0xE0], 0xB8, 0x0470), - 0x08: SPCInstrument("Tweet", 0x08, [0xFF, 0xE0], 0xB8, 0x07A0).amb(), + 0x08: SPCInstrument("Tweet", 0x08, [0xFF, 0xE0], 0xB8, 0x07A0).amb().beat(), 0x09: SPCInstrument("Strings A", 0x09, [0x8F, 0xE9], 0xB8, 0x01E0).mel().bass(True), 0x0A: SPCInstrument("Strings B", 0x0A, [0x8A, 0xE9], 0xB8, 0x01E0).mel().bass(True), 0x0B: SPCInstrument("Trombone", 0x0B, [0xFF, 0xE0], 0xB8, 0x0300).mel().bass(True).beat(), @@ -468,7 +468,7 @@ spc_instrument_changes = [ 0x02: [0x1ABBE6], 0x03: [0x1ABC0B]}, 0x09), InstrumentChange(0x07, 0x00, {0x04: [0x1ABB53, 0x1AB8C9]}, 0x16), - InstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x06]), + InstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x01, 0x05, 0x06, 0x17]), InstrumentChange(0x07, 0x01, {0x05: [0x1AB8EB]}, 0x0A), InstrumentChange(0x07, 0x02, {0x04: [0x1AB981]}, 0x16), InstrumentChange(0x07, 0x03, {0x02: [0x1ABC37], @@ -486,7 +486,7 @@ spc_instrument_changes = [ InstrumentChange(0x07, 0x06, {0x05: [0x1ABCE6]}, 0x0A), InstrumentChange(0x08, 0x00, {0x00: [0x1ABD3A], - 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am), + 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am, ban=[0x05]), InstrumentChange(0x08, 0x00, {0x02: [0x1ABD70], 0x05: [0x1ABE06]}, 0x0F), InstrumentChange(0x08, 0x00, {0x03: [0x1ABDAC]}, 0x0A), @@ -495,22 +495,22 @@ spc_instrument_changes = [ InstrumentChange(0x09, 0x00, {0x00: [0x1AC25A], 0x05: [0x1AC28E]}, 0x0A), - InstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14), - InstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14), + InstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + InstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x01, {0x06: [0x1ABF43]}, 0x09), InstrumentChange(0x09, 0x02, {0x00: [0x1AC450], 0x05: [0x1AC56D], 0x07: [0x1AC595]}, 0x0A), - InstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14), + InstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x02, {0x03: [0x1AC4B3], 0x04: [0x1AC510]}, 0x11), InstrumentChange(0x09, 0x02, {0x06: [0x1AC2E9]}, 0x09), InstrumentChange(0x09, 0x03, {0x00: [0x1ABF63]}, 0x0A), - InstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14), + InstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x03, {0x03: [0x1ABFA4]}, 0x11), InstrumentChange(0x09, 0x03, {0x05: [0x1AC01C]}, 0x16), InstrumentChange(0x09, 0x04, {0x00: [0x1AC04D]}, 0x0A), - InstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14), + InstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x04, {0x02: [0x1AC5CF]}, 0x18), InstrumentChange(0x09, 0x04, {0x03: [0x1AC085], 0x04: [0x1AC5ED]}, 0x11), @@ -518,20 +518,20 @@ spc_instrument_changes = [ InstrumentChange(0x09, 0x04, {0x06: [0x1AC146]}, 0x12), InstrumentChange(0x09, 0x05, {0x00: [0x1AC178], 0x07: [0x1AC229]}, 0x0A), - InstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14), + InstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x05, {0x02: [0x1AC19E]}, 0x18), InstrumentChange(0x09, 0x05, {0x03: [0x1AC1D3], 0x04: [0x1AC1F4]}, 0x12), InstrumentChange(0x09, 0x06, {0x00: [0x1AC317], 0x07: [0x1AC3ED]}, 0x0A), - InstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14), + InstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x06, {0x02: [0x1AC33A]}, 0x18), InstrumentChange(0x09, 0x06, {0x03: [0x1AC36F], 0x04: [0x1AC3A4], 0x05: [0x1AC3D9]}, 0x12), InstrumentChange(0x09, 0x07, {0x00: [0x1AC40A], 0x05: [0x1AC43C]}, 0x0A), - InstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14), + InstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), InstrumentChange(0x09, 0x07, {0x02: [0x1AC492]}, 0x18), InstrumentChange(0x09, 0x07, {0x03: [0x1AC680], 0x04: [0x1AC6C1]}, 0x11), @@ -563,21 +563,21 @@ spc_instrument_changes = [ 0x05: [0x1AC87E]}, 0x0B), InstrumentChange(0x0C, 0x01, {0x02: [0x1AC89A], 0x03: [0x1AC8AD]}, 0x11), - InstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E), + InstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E, type=Rh|Am, ban=[0x05]), InstrumentChange(0x0C, 0x01, {0x05: [0x1AC8C3]}, 0x02), InstrumentChange(0x0C, 0x02, {0x02: [0x1AC8E0], 0x03: [0x1AC8F3]}, 0x11), - InstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E), + InstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E, type=Rh|Am, ban=[0x05]), InstrumentChange(0x0C, 0x02, {0x05: [0x1AC909]}, 0x02), InstrumentChange(0x0D, 0x00, {0x00: [0x1AD003], 0x03: [0x1AD02C], 0x04: [0x1AD03A]}, 0x11), - InstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02), + InstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), InstrumentChange(0x0D, 0x00, {0x02: [0x1AD07F]}, 0x14), InstrumentChange(0x0D, 0x01, {0x00: [0x1ACD10], 0x04: [0x1ACD9A]}, 0x0B), - InstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02), + InstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), InstrumentChange(0x0D, 0x01, {0x03: [0x1ACD7F], 0x05: [0x1ACDCA]}, 0x11), InstrumentChange(0x0D, 0x03, {0x00: [0x1ACE8E], @@ -585,7 +585,7 @@ spc_instrument_changes = [ InstrumentChange(0x0D, 0x03, {0x02: [0x1ACED4]}, 0x14), InstrumentChange(0x0D, 0x03, {0x03: [0x1ACEE0], 0x04: [0x1ACF07]}, 0x11), - InstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02), + InstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), InstrumentChange(0x0E, 0x00, {0x00: [0x1AD29C]}, 0x16), InstrumentChange(0x0E, 0x00, {0x01: [0x1AD2AD], @@ -710,7 +710,7 @@ spc_instrument_changes = [ InstrumentChange(0x13, 0x00, {0x06: [0x1B9650], 0x07: [0x1B9696]}, 0x0F), InstrumentChange(0x13, 0x00, {0x06: [0x1B967B], - 0x07: [0x1B96C0]}, 0x02), + 0x07: [0x1B96C0]}, 0x02, ban=[0x0B]), InstrumentChange(0x14, 0x00, {0x00: [0x1B9901, 0x1B97A8]}, 0x15), InstrumentChange(0x14, 0x01, {0x01: [0x1B97C4], @@ -723,13 +723,13 @@ spc_instrument_changes = [ 0x01: [0x1B9A50], 0x02: [0x1B9A6D], 0x03: [0x1B9A8A]}, 0x0B), - InstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02), - InstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02), + InstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + InstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), InstrumentChange(0x15, 0x01, {0x01: [0x1B9984], 0x02: [0x1B99AA], 0x03: [0x1B99D7]}, 0x0B), InstrumentChange(0x15, 0x01, {0x04: [0x1B9A04]}, 0x14), - InstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02), + InstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), InstrumentChange(0x15, 0x02, {0x01: [0x1B9B58], 0x02: [0x1B9B7E], 0x03: [0x1B9BAB]}, 0x0B), @@ -749,10 +749,10 @@ spc_instrument_changes = [ 0x05: [0x1B9EE4]}, 0x09), InstrumentChange(0x17, 0x00, {0x00: [0x1BA287], - 0x03: [0x1BA26A]}, 0x0E, type=Am|Rh, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + 0x03: [0x1BA26A]}, 0x0E, type=[0x0E, 0x0F, 0x18]), InstrumentChange(0x17, 0x01, {0x01: [0x1BA20F], - 0x02: [0x1BA231], - 0x03: [0x1BA24F]}, 0x0E, type=Am|Rh, ban=[0x01, 0x05, 0x06, 0x10]), + 0x03: [0x1BA24F]}, 0x0E, type=Rh, ban=[0x0B, 0x0D, 0x16]), + InstrumentChange(0x17, 0x01, {0x02: [0x1BA231]}, 0x0F), InstrumentChange(0x19, 0x00, {0x00: [0x1BA476], 0x01: [0x1BA49C], @@ -775,7 +775,7 @@ spc_instrument_changes = [ 0x04: [0x1BA783], 0x05: [0x1BA7A2], 0x06: [0x1BA7B9], - 0x07: [0x1BA7D0]}, 0x0E), + 0x07: [0x1BA7D0]}, 0x0E, type=Rh|Am, ban=[0x01, 0x05, 0x06, 0x10]), InstrumentChange(0x1A, 0x01, {0x00: [0x1BA5C1], 0x05: [0x1BA68F], 0x06: [0x1BA6A6], @@ -820,8 +820,8 @@ spc_instrument_changes = [ 0x03: [0x1BAEC6], 0x04: [0x1BAEE4]}, 0x0B), InstrumentChange(0x1F, 0x00, {0x00: [0x1BAE9C]}, 0x18), - InstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13), - InstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02), + InstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), + InstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02, ban=[0x10]), InstrumentChange(0x1F, 0x01, {0x03: [0x1BAE15], 0x04: [0x1BAE32], 0x05: [0x1BAE4F]}, 0x11), @@ -829,7 +829,7 @@ spc_instrument_changes = [ InstrumentChange(0x1F, 0x02, {0x03: [0x1BAF53], 0x04: [0x1BAF69], 0x05: [0x1BAF7F]}, 0x11), - InstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13), + InstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), InstrumentChange(0x20, 0x00, {0x00: [0x1AD49A], 0x01: [0x1AD4BA], From 80eac44ac0a061a76fb8e4b7749997826a77deb1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 15 Dec 2023 11:35:27 -0600 Subject: [PATCH 074/123] Fixed issue with Insanity ER and Pyramid Crack exit --- source/overworld/EntranceShuffle2.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 7e34f711..c909107b 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1686,13 +1686,18 @@ def connect_exit(exit_name, entrancename, avail): if exit.connected_region is not None: exit.connected_region.entrances.remove(exit) - exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) + dest_region = entrance.parent_region + if dest_region.name == 'Pyramid Crack': + # Needs to logically exit into greater Pyramid Area + dest_region = entrance.parent_region.entrances[0].parent_region + + exit.connect(dest_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) if exit_name != 'Chris Houlihan Room Exit': if avail.coupled: avail.entrances.remove(entrancename) avail.exits.remove(exit_name) world.spoiler.set_entrance(entrance.name, exit.name, 'exit', player) - logging.getLogger('').debug(f'Connected (exit) {entrance.name} to {exit.name}') + logging.getLogger('').debug(f'Connected (exit) {exit.name} to {entrance.name}') def connect_two_way(entrancename, exit_name, avail): From bd5232bf24ee43369de03718d4ca8bb4fe252798 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 03:42:52 -0600 Subject: [PATCH 075/123] Fixed missing OWR brand labels --- Gui.py | 2 +- resources/app/cli/lang/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gui.py b/Gui.py index 0dcdaa75..11ba1383 100755 --- a/Gui.py +++ b/Gui.py @@ -34,7 +34,7 @@ def check_python_version(fish): import sys version = sys.version_info if version.major < 3 or version.minor < 7: - messagebox.showinfo("Door Shuffle " + ESVersion, fish.translate("cli","cli","old.python.version") % sys.version) + messagebox.showinfo("Overworld Shuffle %s (DR %s)" % (ORVersion, ESVersion), fish.translate("cli","cli","old.python.version") % sys.version) # Save settings to file diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 7d028961..87c72e3d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -2,7 +2,7 @@ "cli": { "yes": "Yes", "no": "No", - "app.title": "ALttP Door Randomizer Version %s : --seed %s --code %s", + "app.title": "ALttP Overworld Randomizer Version %s : --seed %s --code %s", "version": "Version", "seed": "Seed", "player": "Player", From d8319ca72771043a4256f36b2255c794e4a1cfea Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 04:18:35 -0600 Subject: [PATCH 076/123] Renamed a couple buttons --- Gui.py | 4 ++-- resources/app/gui/lang/en.json | 3 ++- source/gui/bottom.py | 19 ++++++++++++------- source/gui/loadcliargs.py | 8 ++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Gui.py b/Gui.py index 11ba1383..a0aac7af 100755 --- a/Gui.py +++ b/Gui.py @@ -175,8 +175,8 @@ def guiMain(args=None): self.pages["bottom"].pages = {} self.pages["bottom"].pages["content"] = bottom_frame(self, self, None) ## Save Settings Button - savesettingsButton = Button(self.pages["bottom"].pages["content"], text='Save Settings to File', command=lambda: save_settings_from_gui(True)) - savesettingsButton.pack(side=RIGHT) + savesettingsButton = self.pages["bottom"].pages["content"].widgets["savesettings"].pieces["button"] + savesettingsButton.configure(command=lambda: save_settings_from_gui(True)) # set bottom frame to main window self.pages["bottom"].pages["content"].pack(side=BOTTOM, fill=X, padx=5, pady=5) diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 5d082636..cc33e73f 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -390,10 +390,11 @@ "bottom.content.names": "Player names", "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", - "bottom.content.go": "Generate Patched Rom", + "bottom.content.go": "Generate Game", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", + "bottom.content.savesettings": "Save Settings", "bottom.content.outputdir": "Select Destination", "bottom.content.docs": "Open Documentation" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 3d37908e..9cc05d23 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -150,9 +150,10 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Generate Patched Rom', command=generateRom) + self.widgets[widget].pieces["button"] = Button(self, command=generateRom) # button: pack self.widgets[widget].pieces["button"].pack(side=LEFT) + self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") def open_output(): if output_path.cached_path is None: @@ -170,12 +171,8 @@ def bottom_frame(self, parent, args=None): args.outputpath = parent.settings["outputpath"] = folder_selected ## Output Button - # widget ID widget = "outputdir" - - # Empty object self.widgets[widget] = Empty() - # pieces self.widgets[widget].pieces = {} # storagevar @@ -183,8 +180,16 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=select_output) - # button: pack + self.widgets[widget].pieces["button"] = Button(self, command=select_output) + self.widgets[widget].pieces["button"].pack(side=RIGHT) + + ## Save Settings Button + widget = "savesettings" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self) self.widgets[widget].pieces["button"].pack(side=RIGHT) ## Documentation Button diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index 069b1fbd..e7e217b9 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,14 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Save Settings button + mainpage = "bottom" + subpage = "content" + widget = "savesettings" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Output Directory button mainpage = "bottom" subpage = "content" From 18fde2a3f3af3f9b5ea97e18df9f3016604e28bd Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 04:23:34 -0600 Subject: [PATCH 077/123] Changed Print Template Yaml from checkbox to button --- Gui.py | 2 +- Main.py | 201 +++++++++++------- docs/Customizer.md | 4 +- resources/app/gui/lang/en.json | 2 +- .../gui/randomize/generation/checkboxes.json | 1 - source/classes/constants.py | 1 - source/gui/bottom.py | 37 +++- source/gui/loadcliargs.py | 8 + 8 files changed, 173 insertions(+), 83 deletions(-) diff --git a/Gui.py b/Gui.py index a0aac7af..899935ea 100755 --- a/Gui.py +++ b/Gui.py @@ -45,7 +45,7 @@ def save_settings(gui, args, filename): os.makedirs(settings_path) output_args = {} settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", - "calc_playthrough", "skip_playthrough", "print_template_yaml", "print_custom_yaml", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", "rom", "enemizercli", "outputpath"] if filename == "settings.json": for s in settings: diff --git a/Main.py b/Main.py index afcb1568..347aa451 100644 --- a/Main.py +++ b/Main.py @@ -56,32 +56,25 @@ def check_python_version(): def main(args, seed=None, fish=None): check_python_version() + + if args.print_template_yaml: + return export_yaml(args, fish) + if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() + world = init_world(args, fish) + if args.securerandom: random.use_secure() seeded = False - # initialize the world - if args.code: - for player, code in args.code.items(): - if code: - Settings.adjust_args_from_code(code, player, args) - customized = None - if args.customizer: - customized = CustomSettings() - customized.load_yaml(args.customizer) - seed = customized.determine_seed(seed) + if world.customizer: + seed = world.customizer.determine_seed(seed) seeded = True - customized.adjust_args(args) - world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, - args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, - args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) - world.customizer = customized if customized else None - logger = logging.getLogger('') + world.customizer.adjust_args(args) if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -93,53 +86,9 @@ def main(args, seed=None, fish=None): if args.securerandom: world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9)) - world.boots_hint = args.boots_hint.copy() - world.remote_items = args.remote_items.copy() - world.mapshuffle = args.mapshuffle.copy() - world.compassshuffle = args.compassshuffle.copy() - world.keyshuffle = args.keyshuffle.copy() - world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.bombbag = args.bombbag.copy() - world.flute_mode = args.flute_mode.copy() - world.bow_mode = args.bow_mode.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} - world.crystals_ganon_orig = args.crystals_ganon.copy() - world.crystals_gt_orig = args.crystals_gt.copy() - world.owTerrain = args.ow_terrain.copy() - world.owKeepSimilar = args.ow_keepsimilar.copy() - world.owWhirlpoolShuffle = args.ow_whirlpool.copy() - world.owFluteShuffle = args.ow_fluteshuffle.copy() - world.shuffle_bonk_drops = args.bonk_drops.copy() - world.open_pyramid = args.openpyramid.copy() - world.boss_shuffle = args.shufflebosses.copy() - world.enemy_shuffle = args.shuffleenemies.copy() - world.enemy_health = args.enemy_health.copy() - world.enemy_damage = args.enemy_damage.copy() - world.beemizer = args.beemizer.copy() world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} - world.door_type_mode = args.door_type_mode.copy() - world.trap_door_mode = args.trap_door_mode.copy() - world.key_logic_algorithm = args.key_logic_algorithm.copy() - world.decoupledoors = args.decoupledoors.copy() - world.door_self_loops = args.door_self_loops.copy() - world.experimental = args.experimental.copy() - world.dungeon_counters = args.dungeon_counters.copy() - world.fish = fish - world.shopsanity = args.shopsanity.copy() - world.dropshuffle = args.dropshuffle.copy() - world.pottery = args.pottery.copy() - world.potshuffle = args.shufflepots.copy() - world.mixed_travel = args.mixed_travel.copy() - world.standardize_palettes = args.standardize_palettes.copy() - world.shufflelinks = args.shufflelinks.copy() - world.shuffletavern = args.shuffletavern.copy() - world.pseudoboots = args.pseudoboots.copy() - world.overworld_map = args.overworld_map.copy() - world.take_any = args.take_any.copy() - world.restrict_boss_items = args.restrict_boss_items.copy() - world.collection_rate = args.collection_rate.copy() - world.colorizepots = args.colorizepots.copy() world.treasure_hunt_count = {} world.treasure_hunt_total = {} @@ -193,27 +142,11 @@ def main(args, seed=None, fish=None): if hasattr(world,"escape_assist") and player in world.escape_assist: world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it - if args.usestartinventory[player]: - for tok in filter(None, args.startinventory[player].split(',')): - name = tok.strip() - name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' - item = ItemFactory(name, player) - if item: - world.push_precollected(item) - - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) + set_starting_inventory(world, args) world.settings = CustomSettings() world.settings.create_from_world(world, args) - if args.print_template_yaml: - world.settings.record_item_pool(world, True) - world.settings.write_to_file(output_path(f'{outfilebase}_template.yaml')) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -466,6 +399,120 @@ def main(args, seed=None, fish=None): return world +def export_yaml(args, fish): + if args.outputpath: + os.makedirs(args.outputpath, exist_ok=True) + output_path.cached_path = args.outputpath + + outfilebase = f'{args.outputname if args.outputname else "export"}' + logger = logging.getLogger('') + + world = init_world(args, fish) + + from OverworldShuffle import __version__ as ORVersion + logger.info( + world.fish.translate("cli","cli","app.title") + "\n", + ORVersion, + "(%s)" % outfilebase, + Settings.make_code(world, 1) if world.players == 1 else '' + ) + + for k,v in {"DR":__version__,"OR":ORVersion}.items(): + logger.info((k + ' Version:').ljust(16) + '%s' % v) + + set_starting_inventory(world, args) + + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}.yaml')) + + return world + + +def init_world(args, fish): + if args.code: + for player, code in args.code.items(): + if code: + Settings.adjust_args_from_code(code, player, args) + + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + + world.boots_hint = args.boots_hint.copy() + world.remote_items = args.remote_items.copy() + world.mapshuffle = args.mapshuffle.copy() + world.compassshuffle = args.compassshuffle.copy() + world.keyshuffle = args.keyshuffle.copy() + world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.bombbag = args.bombbag.copy() + world.flute_mode = args.flute_mode.copy() + world.bow_mode = args.bow_mode.copy() + world.crystals_ganon_orig = args.crystals_ganon.copy() + world.crystals_gt_orig = args.crystals_gt.copy() + world.owTerrain = args.ow_terrain.copy() + world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owWhirlpoolShuffle = args.ow_whirlpool.copy() + world.owFluteShuffle = args.ow_fluteshuffle.copy() + world.shuffle_bonk_drops = args.bonk_drops.copy() + world.open_pyramid = args.openpyramid.copy() + world.boss_shuffle = args.shufflebosses.copy() + world.enemy_shuffle = args.shuffleenemies.copy() + world.enemy_health = args.enemy_health.copy() + world.enemy_damage = args.enemy_damage.copy() + world.beemizer = args.beemizer.copy() + world.intensity = {player: 'random' if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} + world.door_type_mode = args.door_type_mode.copy() + world.trap_door_mode = args.trap_door_mode.copy() + world.key_logic_algorithm = args.key_logic_algorithm.copy() + world.decoupledoors = args.decoupledoors.copy() + world.door_self_loops = args.door_self_loops.copy() + world.experimental = args.experimental.copy() + world.dungeon_counters = args.dungeon_counters.copy() + world.fish = fish + world.shopsanity = args.shopsanity.copy() + world.dropshuffle = args.dropshuffle.copy() + world.pottery = args.pottery.copy() + world.potshuffle = args.shufflepots.copy() + world.mixed_travel = args.mixed_travel.copy() + world.standardize_palettes = args.standardize_palettes.copy() + world.shufflelinks = args.shufflelinks.copy() + world.shuffletavern = args.shuffletavern.copy() + world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() + world.take_any = args.take_any.copy() + world.restrict_boss_items = args.restrict_boss_items.copy() + world.collection_rate = args.collection_rate.copy() + world.colorizepots = args.colorizepots.copy() + + world.customizer = None + if args.customizer: + world.customizer = CustomSettings() + world.customizer.load_yaml(args.customizer) + + return world + + +def set_starting_inventory(world, args): + for player in range(1, world.players + 1): + if args.usestartinventory[player]: + for tok in filter(None, args.startinventory[player].split(',')): + name = tok.strip() + name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + item = ItemFactory(name, player) + if item: + world.push_precollected(item) + + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + + def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, diff --git a/docs/Customizer.md b/docs/Customizer.md index 3f3b31e9..c881119f 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -8,11 +8,13 @@ The cli includes a couple arguments to help: `--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. +Present on the GUI as `Export Yaml` on the bottom. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. -Present on the GUI as `Print Customizer Template`, `Print Customizer File`, and `Customizer File` on the Generation Setup tab. +Present on the GUI as `Print Customizer File` and `Customizer File` on the Generation Setup tab. ### meta diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index cc33e73f..9b4a0690 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -234,7 +234,6 @@ "randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", - "randomizer.generation.print_template_yaml": "Print Customizer Template", "randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.settingsonload": "Settings On Load", @@ -391,6 +390,7 @@ "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", "bottom.content.go": "Generate Game", + "bottom.content.exportyaml": "Export Yaml", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 658ea7f0..6e377027 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,7 +4,6 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, - "print_template_yaml":{ "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } } diff --git a/source/classes/constants.py b/source/classes/constants.py index 6bf5f55e..c8e15532 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -147,7 +147,6 @@ SETTINGSTOPROCESS = { "createspoiler": "create_spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", - "print_template_yaml": "print_template_yaml", "print_custom_yaml": "print_custom_yaml", "settingsonload": "settingsonload" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 9cc05d23..fddc29a1 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -6,7 +6,7 @@ import random import re from CLI import parse_cli from Fill import FillError -from Main import main, EnemizerError +from Main import main, export_yaml, EnemizerError from Utils import local_path, output_path, open_file, update_deprecated_args import source.classes.constants as CONST from source.gui.randomize.multiworld import multiworld_page @@ -155,6 +155,41 @@ def bottom_frame(self, parent, args=None): self.widgets[widget].pieces["button"].pack(side=LEFT) self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") + def exportYaml(): + guiargs = create_guiargs(parent) + # get default values for missing parameters + for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): + if k not in vars(guiargs): + setattr(guiargs, k, v) + elif type(v) is dict: # use same settings for every player + setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) + + filename = None + try: + from tkinter import filedialog + filename = filedialog.asksaveasfilename(initialdir=guiargs.outputpath, title="Save file", filetypes=(("Customizer Yamls", "*.yaml"), ("All files", "*.*"))) + if filename is not None and filename != '': + guiargs.outputpath = parent.settings["outputpath"] = os.path.dirname(filename) + guiargs.outputname = os.path.splitext(os.path.basename(filename))[0] + export_yaml(args=guiargs, fish=parent.fish) + except (FillError, EnemizerError, Exception, RuntimeError) as e: + logging.exception(e) + messagebox.showerror(title="Error while exporting yaml", message=str(e)) + else: + if filename is not None and filename != '': + successMsg = "File Exported" + # FIXME: English + messagebox.showinfo(title="Success", message=successMsg) + + ## Export Yaml Button + widget = "exportyaml" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self, command=exportYaml) + self.widgets[widget].pieces["button"].pack(side=LEFT) + def open_output(): if output_path.cached_path is None: if args and args.outputpath: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index e7e217b9..6e6695c0 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,14 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Export Yaml button + mainpage = "bottom" + subpage = "content" + widget = "exportyaml" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Save Settings button mainpage = "bottom" subpage = "content" From 55caf240114755457763de1650a2f36568ce18b6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 23 Dec 2023 04:23:34 -0600 Subject: [PATCH 078/123] Changed Print Template Yaml from checkbox to button --- Gui.py | 2 +- Main.py | 201 +++++++++++------- docs/Customizer.md | 4 +- resources/app/gui/lang/en.json | 2 +- .../gui/randomize/generation/checkboxes.json | 1 - source/classes/constants.py | 1 - source/gui/bottom.py | 37 +++- source/gui/loadcliargs.py | 8 + 8 files changed, 173 insertions(+), 83 deletions(-) diff --git a/Gui.py b/Gui.py index a0aac7af..899935ea 100755 --- a/Gui.py +++ b/Gui.py @@ -45,7 +45,7 @@ def save_settings(gui, args, filename): os.makedirs(settings_path) output_args = {} settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", - "calc_playthrough", "skip_playthrough", "print_template_yaml", "print_custom_yaml", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", "settingsonload", "rom", "enemizercli", "outputpath"] if filename == "settings.json": for s in settings: diff --git a/Main.py b/Main.py index afcb1568..347aa451 100644 --- a/Main.py +++ b/Main.py @@ -56,32 +56,25 @@ def check_python_version(): def main(args, seed=None, fish=None): check_python_version() + + if args.print_template_yaml: + return export_yaml(args, fish) + if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() + world = init_world(args, fish) + if args.securerandom: random.use_secure() seeded = False - # initialize the world - if args.code: - for player, code in args.code.items(): - if code: - Settings.adjust_args_from_code(code, player, args) - customized = None - if args.customizer: - customized = CustomSettings() - customized.load_yaml(args.customizer) - seed = customized.determine_seed(seed) + if world.customizer: + seed = world.customizer.determine_seed(seed) seeded = True - customized.adjust_args(args) - world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, - args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, - args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) - world.customizer = customized if customized else None - logger = logging.getLogger('') + world.customizer.adjust_args(args) if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -93,53 +86,9 @@ def main(args, seed=None, fish=None): if args.securerandom: world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9)) - world.boots_hint = args.boots_hint.copy() - world.remote_items = args.remote_items.copy() - world.mapshuffle = args.mapshuffle.copy() - world.compassshuffle = args.compassshuffle.copy() - world.keyshuffle = args.keyshuffle.copy() - world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.bombbag = args.bombbag.copy() - world.flute_mode = args.flute_mode.copy() - world.bow_mode = args.bow_mode.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} - world.crystals_ganon_orig = args.crystals_ganon.copy() - world.crystals_gt_orig = args.crystals_gt.copy() - world.owTerrain = args.ow_terrain.copy() - world.owKeepSimilar = args.ow_keepsimilar.copy() - world.owWhirlpoolShuffle = args.ow_whirlpool.copy() - world.owFluteShuffle = args.ow_fluteshuffle.copy() - world.shuffle_bonk_drops = args.bonk_drops.copy() - world.open_pyramid = args.openpyramid.copy() - world.boss_shuffle = args.shufflebosses.copy() - world.enemy_shuffle = args.shuffleenemies.copy() - world.enemy_health = args.enemy_health.copy() - world.enemy_damage = args.enemy_damage.copy() - world.beemizer = args.beemizer.copy() world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} - world.door_type_mode = args.door_type_mode.copy() - world.trap_door_mode = args.trap_door_mode.copy() - world.key_logic_algorithm = args.key_logic_algorithm.copy() - world.decoupledoors = args.decoupledoors.copy() - world.door_self_loops = args.door_self_loops.copy() - world.experimental = args.experimental.copy() - world.dungeon_counters = args.dungeon_counters.copy() - world.fish = fish - world.shopsanity = args.shopsanity.copy() - world.dropshuffle = args.dropshuffle.copy() - world.pottery = args.pottery.copy() - world.potshuffle = args.shufflepots.copy() - world.mixed_travel = args.mixed_travel.copy() - world.standardize_palettes = args.standardize_palettes.copy() - world.shufflelinks = args.shufflelinks.copy() - world.shuffletavern = args.shuffletavern.copy() - world.pseudoboots = args.pseudoboots.copy() - world.overworld_map = args.overworld_map.copy() - world.take_any = args.take_any.copy() - world.restrict_boss_items = args.restrict_boss_items.copy() - world.collection_rate = args.collection_rate.copy() - world.colorizepots = args.colorizepots.copy() world.treasure_hunt_count = {} world.treasure_hunt_total = {} @@ -193,27 +142,11 @@ def main(args, seed=None, fish=None): if hasattr(world,"escape_assist") and player in world.escape_assist: world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it - if args.usestartinventory[player]: - for tok in filter(None, args.startinventory[player].split(',')): - name = tok.strip() - name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' - item = ItemFactory(name, player) - if item: - world.push_precollected(item) - - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) + set_starting_inventory(world, args) world.settings = CustomSettings() world.settings.create_from_world(world, args) - if args.print_template_yaml: - world.settings.record_item_pool(world, True) - world.settings.write_to_file(output_path(f'{outfilebase}_template.yaml')) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt')) @@ -466,6 +399,120 @@ def main(args, seed=None, fish=None): return world +def export_yaml(args, fish): + if args.outputpath: + os.makedirs(args.outputpath, exist_ok=True) + output_path.cached_path = args.outputpath + + outfilebase = f'{args.outputname if args.outputname else "export"}' + logger = logging.getLogger('') + + world = init_world(args, fish) + + from OverworldShuffle import __version__ as ORVersion + logger.info( + world.fish.translate("cli","cli","app.title") + "\n", + ORVersion, + "(%s)" % outfilebase, + Settings.make_code(world, 1) if world.players == 1 else '' + ) + + for k,v in {"DR":__version__,"OR":ORVersion}.items(): + logger.info((k + ' Version:').ljust(16) + '%s' % v) + + set_starting_inventory(world, args) + + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}.yaml')) + + return world + + +def init_world(args, fish): + if args.code: + for player, code in args.code.items(): + if code: + Settings.adjust_args_from_code(code, player, args) + + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + + world.boots_hint = args.boots_hint.copy() + world.remote_items = args.remote_items.copy() + world.mapshuffle = args.mapshuffle.copy() + world.compassshuffle = args.compassshuffle.copy() + world.keyshuffle = args.keyshuffle.copy() + world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.bombbag = args.bombbag.copy() + world.flute_mode = args.flute_mode.copy() + world.bow_mode = args.bow_mode.copy() + world.crystals_ganon_orig = args.crystals_ganon.copy() + world.crystals_gt_orig = args.crystals_gt.copy() + world.owTerrain = args.ow_terrain.copy() + world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owWhirlpoolShuffle = args.ow_whirlpool.copy() + world.owFluteShuffle = args.ow_fluteshuffle.copy() + world.shuffle_bonk_drops = args.bonk_drops.copy() + world.open_pyramid = args.openpyramid.copy() + world.boss_shuffle = args.shufflebosses.copy() + world.enemy_shuffle = args.shuffleenemies.copy() + world.enemy_health = args.enemy_health.copy() + world.enemy_damage = args.enemy_damage.copy() + world.beemizer = args.beemizer.copy() + world.intensity = {player: 'random' if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} + world.door_type_mode = args.door_type_mode.copy() + world.trap_door_mode = args.trap_door_mode.copy() + world.key_logic_algorithm = args.key_logic_algorithm.copy() + world.decoupledoors = args.decoupledoors.copy() + world.door_self_loops = args.door_self_loops.copy() + world.experimental = args.experimental.copy() + world.dungeon_counters = args.dungeon_counters.copy() + world.fish = fish + world.shopsanity = args.shopsanity.copy() + world.dropshuffle = args.dropshuffle.copy() + world.pottery = args.pottery.copy() + world.potshuffle = args.shufflepots.copy() + world.mixed_travel = args.mixed_travel.copy() + world.standardize_palettes = args.standardize_palettes.copy() + world.shufflelinks = args.shufflelinks.copy() + world.shuffletavern = args.shuffletavern.copy() + world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() + world.take_any = args.take_any.copy() + world.restrict_boss_items = args.restrict_boss_items.copy() + world.collection_rate = args.collection_rate.copy() + world.colorizepots = args.colorizepots.copy() + + world.customizer = None + if args.customizer: + world.customizer = CustomSettings() + world.customizer.load_yaml(args.customizer) + + return world + + +def set_starting_inventory(world, args): + for player in range(1, world.players + 1): + if args.usestartinventory[player]: + for tok in filter(None, args.startinventory[player].split(',')): + name = tok.strip() + name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + item = ItemFactory(name, player) + if item: + world.push_precollected(item) + + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + + def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, diff --git a/docs/Customizer.md b/docs/Customizer.md index 3f3b31e9..c881119f 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -8,11 +8,13 @@ The cli includes a couple arguments to help: `--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. +Present on the GUI as `Export Yaml` on the bottom. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. -Present on the GUI as `Print Customizer Template`, `Print Customizer File`, and `Customizer File` on the Generation Setup tab. +Present on the GUI as `Print Customizer File` and `Customizer File` on the Generation Setup tab. ### meta diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index cc33e73f..9b4a0690 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -234,7 +234,6 @@ "randomizer.generation.createspoiler": "Create Spoiler Log", "randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.calcplaythrough": "Calculate Playthrough", - "randomizer.generation.print_template_yaml": "Print Customizer Template", "randomizer.generation.print_custom_yaml": "Print Customizer File", "randomizer.generation.settingsonload": "Settings On Load", @@ -391,6 +390,7 @@ "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", "bottom.content.go": "Generate Game", + "bottom.content.exportyaml": "Export Yaml", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 658ea7f0..6e377027 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -4,7 +4,6 @@ "bps": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" }, - "print_template_yaml":{ "type": "checkbox" }, "print_custom_yaml": { "type": "checkbox" } } } diff --git a/source/classes/constants.py b/source/classes/constants.py index 6bf5f55e..c8e15532 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -147,7 +147,6 @@ SETTINGSTOPROCESS = { "createspoiler": "create_spoiler", "createrom": "create_rom", "calcplaythrough": "calc_playthrough", - "print_template_yaml": "print_template_yaml", "print_custom_yaml": "print_custom_yaml", "settingsonload": "settingsonload" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 9cc05d23..14859951 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -6,7 +6,7 @@ import random import re from CLI import parse_cli from Fill import FillError -from Main import main, EnemizerError +from Main import main, export_yaml, EnemizerError from Utils import local_path, output_path, open_file, update_deprecated_args import source.classes.constants as CONST from source.gui.randomize.multiworld import multiworld_page @@ -155,6 +155,41 @@ def bottom_frame(self, parent, args=None): self.widgets[widget].pieces["button"].pack(side=LEFT) self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") + def exportYaml(): + guiargs = create_guiargs(parent) + # get default values for missing parameters + for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): + if k not in vars(guiargs): + setattr(guiargs, k, v) + elif type(v) is dict: # use same settings for every player + setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) + + filename = None + try: + from tkinter import filedialog + filename = filedialog.asksaveasfilename(initialdir=guiargs.outputpath, title="Save file", filetypes=(("Yaml Files", (".yaml", ".yml")), ("All Files", "*"))) + if filename is not None and filename != '': + guiargs.outputpath = parent.settings["outputpath"] = os.path.dirname(filename) + guiargs.outputname = os.path.splitext(os.path.basename(filename))[0] + export_yaml(args=guiargs, fish=parent.fish) + except (FillError, EnemizerError, Exception, RuntimeError) as e: + logging.exception(e) + messagebox.showerror(title="Error while exporting yaml", message=str(e)) + else: + if filename is not None and filename != '': + successMsg = "File Exported" + # FIXME: English + messagebox.showinfo(title="Success", message=successMsg) + + ## Export Yaml Button + widget = "exportyaml" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self, command=exportYaml) + self.widgets[widget].pieces["button"].pack(side=LEFT) + def open_output(): if output_path.cached_path is None: if args and args.outputpath: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index e7e217b9..6e6695c0 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,14 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Export Yaml button + mainpage = "bottom" + subpage = "content" + widget = "exportyaml" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Save Settings button mainpage = "bottom" subpage = "content" From 95fc30a8bda53dbebecd2d616fcf85b812aff2ab Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Dec 2023 15:27:49 -0600 Subject: [PATCH 079/123] Implement Beat Agahnim 1 already in starting inventory --- InitialSram.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/InitialSram.py b/InitialSram.py index 7f0f2c89..b3a969e9 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -118,10 +118,11 @@ class InitialSram: self._initial_sram_bytes[0x410] |= 0x01 if startingstate.has('Beat Agahnim 1', player): + self.pre_open_lumberjack() if world.mode[player] == 'standard': - self._initial_sram_bytes[0x3C5] = 0x80 + self.set_progress_indicator(0x80) else: - self._initial_sram_bytes[0x3C5] = 0x03 + self.set_progress_indicator(0x03) for item in world.precollected_items: if item.player != player: From 29504b7d53f210ffdc153cdbc18e9262ab318b95 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 18 Dec 2023 14:10:14 -0700 Subject: [PATCH 080/123] fix: accept shufflebosses and shuffleenemies --- source/classes/CustomSettings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index ba9c9422..3a48132e 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -147,8 +147,8 @@ class CustomSettings(object): args.mapshuffle[p] = True args.compassshuffle[p] = True - args.shufflebosses[p] = get_setting(settings['boss_shuffle'], args.shufflebosses[p]) - args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p]) + args.shufflebosses[p] = get_setting(settings['boss_shuffle'], get_setting(settings['shufflebosses'], args.shufflebosses[p])) + args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], get_setting(settings['shuffleenemies'], args.shuffleenemies[p])) args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p]) args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p]) args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p]) @@ -311,8 +311,8 @@ class CustomSettings(object): settings_dict[p]['keyshuffle'] = world.keyshuffle[p] settings_dict[p]['mapshuffle'] = world.mapshuffle[p] settings_dict[p]['compassshuffle'] = world.compassshuffle[p] - settings_dict[p]['shufflebosses'] = world.boss_shuffle[p] - settings_dict[p]['shuffleenemies'] = world.enemy_shuffle[p] + settings_dict[p]['boss_shuffle'] = world.boss_shuffle[p] + settings_dict[p]['enemy_shuffle'] = world.enemy_shuffle[p] settings_dict[p]['enemy_health'] = world.enemy_health[p] settings_dict[p]['enemy_damage'] = world.enemy_damage[p] settings_dict[p]['shufflepots'] = world.potshuffle[p] From 2d467efdedcc40b387295ee20275ff9f1c45d1b2 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 13 Dec 2023 11:45:59 -0700 Subject: [PATCH 081/123] fix: don't bother blocking rain doors in no logic --- Rom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index 6095ace0..6aafa704 100644 --- a/Rom.py +++ b/Rom.py @@ -1434,10 +1434,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) # block HC upstairs doors in rain state in standard mode - prevent_rain = world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' + prevent_rain = world.mode[player] == 'standard' and world.shuffle[player] != 'vanilla' and world.logic[player] != 'nologic' rom.write_byte(0x18008A, 0x01 if prevent_rain else 0x00) # block sanc door in rain state and the dungeon is not vanilla - rom.write_byte(0x13f0fa, 0x01 if world.mode[player] == "standard" and world.doorShuffle[player] != 'vanilla' else 0x00) + block_sanc = world.mode[player] == 'standard' and world.doorShuffle[player] != 'vanilla' and world.logic[player] != 'nologic' + rom.write_byte(0x13f0fa, 0x01 if block_sanc else 0x00) if prevent_rain: portals = [world.get_portal('Hyrule Castle East', player), world.get_portal('Hyrule Castle West', player)] From bd7ce54dd35f37af1319966e598fc5d28b728e85 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 13 Dec 2023 13:59:34 -0700 Subject: [PATCH 082/123] fix: minor fix for take_anys fix: money balancing - initialization in a good case --- BaseClasses.py | 3 +++ Fill.py | 1 + ItemList.py | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 94287939..19f45551 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1555,6 +1555,9 @@ class Region(object): return self.is_dark_world if self.world.mode[player] != 'inverted' else self.is_light_world + def is_outdoors(self): + return self.type in {RegionType.LightWorld, RegionType.DarkWorld} + def __str__(self): return str(self.__unicode__()) diff --git a/Fill.py b/Fill.py index 64abd448..a600ce17 100644 --- a/Fill.py +++ b/Fill.py @@ -1007,6 +1007,7 @@ def balance_money_progression(world): logger.debug(f'Money balancing needed: Player {target_player} short {difference}') else: difference = 0 + target_player = next(p for p in solvent) while difference > 0: swap_targets = [x for x in unchecked_locations if x not in sphere_locations and x.item.name.startswith('Rupees') and x.item.player == target_player] if len(swap_targets) == 0: diff --git a/ItemList.py b/ItemList.py index 21d3b446..6a8761f7 100644 --- a/ItemList.py +++ b/ItemList.py @@ -558,7 +558,9 @@ def set_up_take_anys(world, player, skip_adjustments=False): world.dynamic_regions.append(take_any) target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) reg = regions.pop() - entrance = world.get_region(reg, player).entrances[0] + entrance = next((ent for ent in world.get_region(reg, player).entrances if ent.parent_region.is_outdoors()), None) + if entrance is None: + raise Exception(f'No outside entrance found for {reg}') connect_entrance(world, entrance, take_any, player) entrance.target = target take_any.shop = Shop(take_any, room_id, take_any_type, 0xE3, True, not world.shopsanity[player], 33 + num*2) From 8f3b3dc760737028fd76ac8b73be43c1248e7128 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 14 Dec 2023 10:13:22 -0700 Subject: [PATCH 083/123] feat: MW progresssion balancing tweaked to be percentage based instead of raw count. Tries to keep each player's locations in each sphere within 80% of the player with the most locations available. (Measured with percentage instead of raw count.) Old algo tried to keep everyone within 20 locations of each other. Difficult if one player has a lot more locations than another. fix: Potential fix for early Trinexx start --- BaseClasses.py | 8 ++++---- Fill.py | 46 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 19f45551..c57e6a1b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -565,7 +565,7 @@ class World(object): if not sphere: # ran out of places and did not finish yet, quit if log_error: - missing_locations = ", ".join([x.name for x in prog_locations]) + missing_locations = ", ".join([f'{x.name} (#{x.player})' for x in prog_locations]) logging.getLogger('').error(f'Cannot reach the following locations: {missing_locations}') return False @@ -600,7 +600,7 @@ class CollectionState(object): self.opened_doors = {player: set() for player in range(1, parent.players + 1)} self.dungeons_to_check = {player: defaultdict(dict) for player in range(1, parent.players + 1)} self.dungeon_limits = None - self.placing_item = None + self.placing_items = None # self.trace = None def update_reachable_regions(self, player): @@ -878,7 +878,7 @@ class CollectionState(object): return door_candidates door_candidates, skip = [], set() if (state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name != 'Universal' - and state.placing_item and state.placing_item.name == small_key_name): + and state.placing_items and any(i.name == small_key_name and i.player == player for i in state.placing_items)): key_logic = state.world.key_logic[player][dungeon_name] for door, paired in key_logic.sm_doors.items(): if door.name in key_logic.door_rules: @@ -923,7 +923,7 @@ class CollectionState(object): player: defaultdict(dict, {name: copy.copy(checklist) for name, checklist in self.dungeons_to_check[player].items()}) for player in range(1, self.world.players + 1)} - ret.placing_item = self.placing_item + ret.placing_items = self.placing_items return ret def apply_dungeon_exploration(self, rrp, player, dungeon_name, checklist): diff --git a/Fill.py b/Fill.py index a600ce17..5c1214a1 100644 --- a/Fill.py +++ b/Fill.py @@ -3,6 +3,7 @@ import collections import itertools import logging import math +from collections import Counter from contextlib import suppress from BaseClasses import CollectionState, FillError, LocationType @@ -71,13 +72,13 @@ def fill_dungeons_restrictive(world, shuffled_locations): def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, vanilla=False): - def sweep_from_pool(placing_item=None): + def sweep_from_pool(placing_items=None): new_state = base_state.copy() for item in itempool: new_state.collect(item, True) - new_state.placing_item = placing_item + new_state.placing_items = placing_items new_state.sweep_for_events() - new_state.placing_item = None + new_state.placing_items = None return new_state unplaced_items = [] @@ -94,7 +95,7 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing while any(player_items.values()) and locations: items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items] - maximum_exploration_state = sweep_from_pool(placing_item=items_to_place[0]) + maximum_exploration_state = sweep_from_pool(placing_items=items_to_place) has_beaten_game = world.has_beaten_game(maximum_exploration_state) for item_to_place in items_to_place: @@ -734,24 +735,44 @@ def balance_multiworld_progression(world): checked_locations = set() unchecked_locations = set(world.get_locations()) + total_locations_count = Counter(location.player for location in world.get_locations() if not location.locked and not location.forced_item) + reachable_locations_count = {} for player in range(1, world.players + 1): reachable_locations_count[player] = 0 + sphere_num = 1 + moved_item_count = 0 def get_sphere_locations(sphere_state, locations): sphere_state.sweep_for_events(key_only=True, locations=locations) return {loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)} + def item_percentage(player, num): + return num / total_locations_count[player] + while True: sphere_locations = get_sphere_locations(state, unchecked_locations) for location in sphere_locations: unchecked_locations.remove(location) - reachable_locations_count[location.player] += 1 + if not location.locked and not location.forced_item: + reachable_locations_count[location.player] += 1 + + logging.debug(f'Sphere {sphere_num}') + logging.debug(f'Reachable locations: {reachable_locations_count}') + debug_percentages = { + player: round(item_percentage(player, num), 2) + for player, num in reachable_locations_count.items() + } + logging.debug(f'Reachable percentages: {debug_percentages}\n') + sphere_num += 1 if checked_locations: - threshold = max(reachable_locations_count.values()) - 20 + max_percentage = max(map(lambda p: item_percentage(p, reachable_locations_count[p]), reachable_locations_count)) + threshold_percentages = {player: max_percentage * .8 for player in range(1, world.players + 1)} + logging.debug(f'Thresholds: {threshold_percentages}') - balancing_players = {player for player, reachables in reachable_locations_count.items() if reachables < threshold} + balancing_players = {player for player, reachables in reachable_locations_count.items() + if item_percentage(player, reachables) < threshold_percentages[player]} if balancing_players: balancing_state = state.copy() balancing_unchecked_locations = unchecked_locations.copy() @@ -769,7 +790,8 @@ def balance_multiworld_progression(world): for location in balancing_sphere: balancing_unchecked_locations.remove(location) balancing_reachables[location.player] += 1 - if world.has_beaten_game(balancing_state) or all(reachables >= threshold for reachables in balancing_reachables.values()): + if world.has_beaten_game(balancing_state) or all(item_percentage(player, reachables) >= threshold_percentages[player] + for player, reachables in balancing_reachables.items()): break elif not balancing_sphere: raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') @@ -796,7 +818,8 @@ def balance_multiworld_progression(world): items_to_replace.append(testing) else: reduced_sphere = get_sphere_locations(reducing_state, locations_to_test) - if reachable_locations_count[player] + len(reduced_sphere) < threshold: + p = item_percentage(player, reachable_locations_count[player] + len(reduced_sphere)) + if p < threshold_percentages[player]: items_to_replace.append(testing) replaced_items = False @@ -821,6 +844,7 @@ def balance_multiworld_progression(world): new_location.event, old_location.event = True, False logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, " f"displacing {old_location.item} into {old_location}") + moved_item_count += 1 state.collect(new_location.item, True, new_location) replaced_items = True break @@ -828,6 +852,7 @@ def balance_multiworld_progression(world): logging.warning(f"Could not Progression Balance {old_location.item}") if replaced_items: + logging.debug(f'Moved {moved_item_count} items so far\n') unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]} for location in get_sphere_locations(state, unlocked): unchecked_locations.remove(location) @@ -842,7 +867,8 @@ def balance_multiworld_progression(world): if world.has_beaten_game(state): break elif not sphere_locations: - raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') + logging.warning('Progression Balancing ran out of paths.') + break def check_shop_swap(l, make_item_free=False): From 7def7081b93c77e1b1d0d94bdf087c3142538fb6 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 18 Dec 2023 14:10:30 -0700 Subject: [PATCH 084/123] fix: ganonhunt playthrough --- Rules.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index e8b8a5fa..b2073ab1 100644 --- a/Rules.py +++ b/Rules.py @@ -864,8 +864,11 @@ def global_rules(world, player): add_mc_rule('Agahnim 1') add_mc_rule('Agahnim 2') - set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player) + set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times + if world.goal[player] != 'ganonhunt': + add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) + set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop From 6ed937971d6df5c9fa2aff37f36b14d372a2ff01 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 22 Dec 2023 14:41:05 -0700 Subject: [PATCH 085/123] fix(vanilla_fill): make uncle weapon non-random, make medallions vanilla fix(enemizer): enemy bans --- ItemList.py | 12 +- source/enemizer/enemy_deny.yaml | 710 ++++++++++++++++++++++++++++++++ 2 files changed, 720 insertions(+), 2 deletions(-) create mode 100644 source/enemizer/enemy_deny.yaml diff --git a/ItemList.py b/ItemList.py index 6a8761f7..d603ae0e 100644 --- a/ItemList.py +++ b/ItemList.py @@ -341,6 +341,8 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] != 'swordless': found_sword = True possible_weapons.append(item) + if world.algorithm == 'vanilla_fill': # skip other possibilities + continue if (item in ['Progressive Bow', 'Bow'] and not found_bow and not world.bow_mode[player].startswith('retro')): found_bow = True @@ -434,9 +436,15 @@ def generate_itempool(world, player): if tr_medallion == 'Random': tr_medallion = None if not mm_medallion: - mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] + if world.algorithm == 'vanilla_fill': + mm_medallion = 'Ether' + else: + mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] if not tr_medallion: - tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] + if world.algorithm == 'vanilla_fill': + tr_medallion = 'Quake' + else: + tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] world.required_medallions[player] = (mm_medallion, tr_medallion) # shuffle bottle refills diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml new file mode 100644 index 00000000..46842052 --- /dev/null +++ b/source/enemizer/enemy_deny.yaml @@ -0,0 +1,710 @@ +UwGeneralDeny: + - [ 0x0002, 0, [ "RollerVerticalDown", "Statue" ] ] #"Sewers - Rat Pots - Rat 1" + - [ 0x0002, 1, [ "RollerVerticalDown", "Statue" ] ] #"Sewers - Rat Pots - Rat 2" + - [ 0x0002, 2, [ "RollerVerticalUp", "Statue" ] ] #"Sewers - Rat Pots - Rat 3" + - [ 0x0002, 3, [ "RollerVerticalUp", "Statue" ] ] #"Sewers - Rat Pots - Rat 4" + - [ 0x0002, 4, [ "Statue" ] ] #"Sewers - Rat Pots - Rat 5" + - [ 0x0002, 15, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Sewers - Rat Pots - Rat 6" + - [ 0x0002, 16, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Sewers - Rat Pots - Rat 7" + - [ 0x0004, 1, ["Statue"]] + - [ 0x0004, 2, ["Statue"]] + - [ 0x0004, 3, ["Statue"]] + - [ 0x0004, 4, ["Statue"]] + - [ 0x0004, 15, ["Statue"]] + - [ 0x000a, 0, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Basement Ledge - Terrorpin 1" + - [ 0x000a, 1, [ "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Palace of Darkness - Basement Ledge - Terrorpin 2" + - [ 0x000b, 1, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Callback - Terrorpin 1" + - [ 0x000e, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Entrance - Freezor" + - [ 0x000e, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari Key - Top Bari" + - [ 0x000e, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari Key - Middle Bari" + - [ 0x0016, 0, [ "RollerVerticalDown", "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 1" + - [ 0x0016, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 2" + - [ 0x0016, 2, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Blue Bari" + - [ 0x0016, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 3" + - [ 0x0017, 5, [ "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Bumper Room - Fire Bar (Clockwise)" + - [ 0x0019, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Dark Maze - Kodongo 1" + - [ 0x0019, 1, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 2" + - [ 0x0019, 2, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 3" + - [ 0x0019, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Palace of Darkness - Dark Maze - Kodongo 4" + - [ 0x001a, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Compass Room - Mini Helmasaur 1" + - [ 0x001a, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Compass Room - Mini Helmasaur 2" + - [ 0x001b, 3, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 2 - Red Eyegore" + - [ 0x001b, 4, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Mimics 2 - Green Eyegore L" + - [ 0x001e, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 3" + - [ 0x001e, 4, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 4" + - [ 0x001e, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 1" + - [ 0x001e, 6, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 2" + - [ 0x001f, 0, [ "RollerHorizontalRight" ] ] #"Ice Palace - Big Key View - Pengator 1" + - [ 0x001f, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0021, 3, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 2" + - [ 0x0021, 4, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 3" + - [ 0x0024, 6, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0026, 1, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Statue" ] ] #"Swamp Palace - Big Spoon - Red Bari 1" + - [ 0x0026, 8, [ "AntiFairyCircle", "Bumper", "Statue" ] ] #"Swamp Palace - Big Spoon - Red Bari 3" + - [ 0x0026, 9, [ "RollerHorizontalRight", "Statue" ] ] #"Swamp Palace - Big Spoon - Kyameron" + - [ 0x0026, 10, [ "Statue" ] ] # multiple push statues in this room can cause issues + - [ 0x0026, 11, [ "Statue" ] ] # multiple push statues in this room can cause issues + - [ 0x0027, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "FirebarCW" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 1" + - [ 0x0027, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 2" + - [ 0x0027, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 3" + - [ 0x0027, 4, ["Bumper", "BigSpike", "AntiFairyCircle", "RollerVerticalDown", "RollerVerticalUp"]] + - [ 0x0027, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Kodongo 1" + - [ 0x0028, 4, [ "RollerVerticalUp" ] ] #"Swamp Palace - Entrance Ledge - Spike Trap" + - [ 0x002a, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] #"Palace of Darkness - Arena Main - Hardhat Beetle 1" + - [ 0x002a, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 2" + - [ 0x002a, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ]] + - [ 0x002a, 6, [ "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 5" + - [ 0x002b, 5, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Fairies - Red Bari 2" + - [ 0x002e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 1" + - [ 0x002e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 2" + - [ 0x002e, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 3" + - [ 0x002e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 4" + - [ 0x002e, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 5" + - [ 0x002e, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 6" + - [ 0x0034, 0, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Hover 1" + - [ 0x0034, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Hover 2" + - [ 0x0034, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Kyameron" + - [ 0x0034, 4, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Zol" + - [ 0x0035, 6, [ "RollerHorizontalRight" ] ] #"Swamp Palace - West Lever - Stalfos 2" + - [ 0x0035, 9, [ "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Swamp Palace - West Lever - Blue Bari" + - [ 0x0036, 7, [ "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Lobby - Hover 3" + - [ 0x0037, 7, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Water 1 - Blue Bari" + - [ 0x0038, 4, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Long Hall - Kyameron 2" + - [ 0x0039, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Mini Helmasaur" + - [ 0x0039, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 1" + - [ 0x0039, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Hardhat Beetle" + - [ 0x0039, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 2" + - [ 0x003b, 1, [ "Bumper" ]] + - [ 0x003c, 0, ["BigSpike"]] + - [ 0x003c, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hookshot Cave - Blue Bari 1" + - [ 0x003c, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hookshot Cave - Blue Bari 2" + - [ 0x003d, 9, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Spark (Counterclockwise)" + - [ 0x003d, 10, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Spark (Clockwise) 1" + - [ 0x003d, 12, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Bunny Beam" + - [ 0x003d, 13, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Antifairy" + - [ 0x003f, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "Statue"] ] #"Ice Palace - P Room - Stalfos Knight 1" + - [ 0x003f, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Statue"] ] #"Ice Palace - P Room - Stalfos Knight 2" + - [ 0x003f, 4, [ "Wizzrobe", "Statue", "Bumper", "BigSpike", "AntiFairyCircle"]] # Wizzrobes can't spawn on pots + - [ 0x0040, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Bridge - Blue Guard 1 + - [ 0x0040, 1, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Bridge - Blue Guard 2 + - [ 0x0041, 0, [ "RollerHorizontalLeft" ] ] #"Sewers - Dark Cactus - Rat 1" + - [ 0x0041, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Sewers - Dark Cactus - Rat 2" + - [ 0x0041, 2, [ "RollerVerticalUp" ] ] #"Sewers - Dark Cactus - Rat 3" + - [ 0x0042, 0, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 1" + - [ 0x0042, 1, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 2" + - [ 0x0042, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 3" + - [ 0x0042, 3, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 4" + - [ 0x0042, 4, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 5" + - [ 0x0042, 5, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 6" + - [ 0x0044, 4, [ "RollerVerticalUp", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Zol" + - [ 0x0044, 6, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "BigSpike" ] ] #"Thieves' Town - Joke Room - Red Bari" + - [ 0x0044, 8, [ "Statue", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Blue Bari 4" + - [ 0x0045, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Basement Block Totems - Red Zazak" + - [ 0x0045, 4, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0045, 7, [ "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Cells - Blue Zazak 4" + - [ 0x0045, 8, [ "RollerHorizontalRight" ] ] #"Thieves' Town - Cells - Zol" + - [ 0x0046, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 1" + - [ 0x0046, 2, [ "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 2" + - [ 0x0046, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 3" + - [ 0x0049, 5, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 2" + - [ 0x0049, 7, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 4" + - [ 0x0049, 8, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Skull Woods - Bari Pits - Gibdo 5" + - [ 0x004b, 0, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 1 - Red Eyegore" + - [ 0x004b, 1, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Warp Hint - Antifairy 1" + - [ 0x004b, 5, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 1" + - [ 0x004b, 6, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 2" + - [ 0x004b, 7, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 3" + - [ 0x004e, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 1" + - [ 0x004e, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 2" + - [ 0x004e, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 3" + - [ 0x0050, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Guard" + - [ 0x0050, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 1" + - [ 0x0050, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 2" + - [ 0x0052, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Guard" + - [ 0x0052, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 1" + - [ 0x0052, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 2" + - [ 0x0053, 1, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - Bridge - Beamos 1" + - [ 0x0053, 5, [ "RollerVerticalDown" ] ] #"Desert Palace - Popo Genocide - Popo TL" + - [ 0x0053, 7, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - Bridge - Popo 5" + - [ 0x0055, 1, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Secret Passage Exit - Green Knife Guard 1" + - [ 0x0057, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0057, 1, ["Statue"]] # Statue switch issues + - [ 0x0057, 2, [ "RollerVerticalUp", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Spike Trap" + - [ 0x0057, 3, ["Statue"]] # Statue switch issues + - [ 0x0057, 4, ["Statue"]] # Statue switch issues + - [ 0x0057, 5, ["Statue"]] # Statue switch issues + - [ 0x0057, 7, [ "RollerVerticalUp", "RollerVerticalDown", "Statue" ] ] #"Skull Woods - Big Key Room - Gibdo 2" + - [ 0x0057, 8, ["Statue"]] # Statue switch issues + - [ 0x0057, 9, ["Statue"]] # Statue switch issues + - [ 0x0057, 10, ["Statue"]] # Statue switch issues + - [ 0x0057, 11, ["Statue"]] # Statue switch issues + - [ 0x0057, 12, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "BigSpike", "SpikeBlock", "Statue"]] #"Skull Woods - Big Key Room - Gibdo 6" + - [ 0x0057, 13, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Blue Bari 1" + - [ 0x0057, 14, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Blue Bari 2" + - [ 0x0058, 0, ["Statue"]] + - [ 0x0058, 1, ["Statue"]] + - [ 0x0058, 2, ["Statue"]] + - [ 0x0058, 3, ["Statue"]] + - [ 0x0058, 4, ["Statue"]] + - [ 0x0058, 6, ["Statue"]] + - [ 0x0058, 7, [ "RollerHorizontalLeft", "Statue" ] ] #"Skull Woods - Lever Room - Hardhat Beetle 2" + - [ 0x0058, 8, ["Statue"]] + - [ 0x0059, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Mini Moldorm 1" + - [ 0x0059, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Mini Moldorm 2" + - [ 0x0059, 9, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Gibdo 1" + - [ 0x005e, 3, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Pit Trap - Big Spike Trap" + - [ 0x005e, 4, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ice Palace - Pit Trap - Fire Bar (Clockwise)" + - [ 0x005f, 0, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari University - Blue Bari 1" + - [ 0x005f, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Ice Palace - Bari University - Blue Bari 2" + - [ 0x0060, 0, [ "RollerVerticalUp", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hyrule Castle - West - Blue Guard" + - [ 0x0062, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hyrule Castle - East - Blue Guard" + - [ 0x0064, 2, [ "Bumper" , "Beamos" ] ] #"Thieves' Town - Attic Hall Left - Keese 2" + - [ 0x0064, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0064, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Hall Left - Rat 1" + - [ 0x0065, 0, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 1" + - [ 0x0065, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Window - Rat 2" + - [ 0x0065, 2, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 3" + - [ 0x0066, 0, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Swamp Palace - Waterfall Room - Hover 1" + - [ 0x0066, 2, [ "AntiFairyCircle", "Bumper"]] + - [ 0x0067, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Blue Bari 1" + - [ 0x0067, 2, ["Bumper"]] #"Skull Woods - Firebar Pits - Blue Bari 2" + - [ 0x0067, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 1" + - [ 0x0067, 4, [ "AntiFairyCircle", "Bumper" ]] + - [ 0x0067, 5, [ "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 3" + - [ 0x0067, 6, [ "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 4" + - [ 0x0067, 7, [ "Beamos", "AntiFairyCircle", "Bumper", "BunnyBeam" ] ] #"Skull Woods - Firebar Pits - Fire Bar (Clockwise)" + - [ 0x006a, 0, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 1" + - [ 0x006a, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 2" + - [ 0x006a, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Palace of Darkness - Dark Alley - Antifairy 1" + - [ 0x006a, 4, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 3" + - [ 0x006a, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 4" + - [ 0x006b, 7, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Mimics 1 - Spike Trap 1" + - [ 0x0071, 0, [ "RollerHorizontalLeft" ] ] #"Hyrule Castle - Basement Trap - Green Guard" + - [ 0x0074, 0, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - North Hallway - Red Devalant 1" + - [ 0x0074, 1, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - North Hallway - Red Devalant 2" + - [ 0x0074, 4, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - North Hallway - Leever 1" + - [ 0x0074, 5, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - North Hallway - Leever 2" + - [ 0x0076, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Hover 1" + - [ 0x0076, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Kyameron" + - [ 0x0076, 3, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Hover 2" + - [ 0x0076, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Zol" + - [ 0x0076, 6, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Swamp Palace - Toilet Left - Blue Bari" + - [ 0x007b, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - DMs Room - Blue Bari 1" + - [ 0x007b, 1, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - DMs Room - Blue Bari 2" + - [ 0x007b, 6, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Statue" + - [ 0x007b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Hardhat Beetle" + - [ 0x007c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Counterclockwise)" + - [ 0x007c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Spike Trap" + - [ 0x007c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Clockwise)" + - [ 0x007c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Hardhat Beetle" + - [ 0x007d, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 1" + - [ 0x007d, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 2" + - [ 0x007d, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 3" + - [ 0x007d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 4" + - [ 0x007d, 4, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [ 0x007d, 7, ["RollerVerticalUp", "RollerHorizontalLeft", "StalfosKnight", "Geldman", "Blob", "Stal"]] #"Ganon's Tower - The Zoo - Mini Helmasaur" + - [ 0x007d, 8, ["RollerVerticalUp", "RollerHorizontalLeft", "RollerHorizontalRight", "StalfosKnight", "Geldman", "Blob", "Stal"]] #"Ganon's Tower - The Zoo - Red Bari" + - [ 0x007d, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] +# todo - consider adding firesnake to 0-3: has a hard time moving, could block hookshots for quite a while + - [ 0x007f, 0, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 1" + - [ 0x007f, 1, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "ArmosStatue" ] ] #"Ice Palace - Big Spikes - Red Bari 2" + - [ 0x007f, 2, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 3" + - [ 0x007f, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 4" + - [ 0x007f, 4, [ "RollerVerticalDown" ] ] #"Ice Palace - Big Spikes - Big Spike Trap 1" + - [ 0x007f, 5, [ "RollerVerticalDown" ] ] #"Ice Palace - Big Spikes - Big Spike Trap 2" + - [ 0x0082, 0, [ "RollerVerticalDown" ] ] #"Hyrule Castle - Basement Playpit - Blue Guard 1" + - [ 0x0082, 2, [ "RollerVerticalUp" ] ] #"Hyrule Castle - Basement Playpit - Blue Guard 3" + - [ 0x0083, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Desert Palace - Left Hallway - Blue Devalant 1" + - [ 0x0084, 0, [ "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Main Room - Left - Leever 1" + - [ 0x0084, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Main Room - Left - Leever 2" + - [ 0x0085, 2, [ "RollerHorizontalRight" ] ] #"Desert Palace - Compass Room - Popo TL" + - [ 0x0085, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Right Hallway - Leever 2" + - [ 0x008b, 3, ["RollerHorizontalRight"]] + - [ 0x008b, 4, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "BigSpike"]] #"Ganon's Tower - Map Room - Spike Trap" + - [ 0x008b, 6, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Map Room - Fire Bar (Clockwise)" + - [ 0x008b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Map Room - Fire Bar (Counterclockwise)" + - [ 0x008c, 14, ["AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x008d, 1, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Tile Room - Yomo Medusa T" + - [ 0x008d, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Tile Room - Spike Trap" + - [ 0x008d, 8, [ "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Tile Room - Stalfos" + - [ 0x008d, 9, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Tile Room - Fire Bar (Clockwise)" + - [ 0x008d, 10, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ganon's Tower - Tile Room - Blue Bari 1" + - [ 0x008d, 12, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ganon's Tower - Tile Room - Blue Bari 2" + - [ 0x008e, 2, [ "Wizzrobe", "Statue"] ] # Wizzrobes can't spawn on pots + - [ 0x0092, 8, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Misery Mire - Dark Weave - Spike Trap" + - [ 0x0092, 9, [ "RollerHorizontalRight" ] ] #"Misery Mire - Dark Weave - Antifairy 3" + - [ 0x0092, 10, [ "RollerHorizontalLeft" ] ] #"Misery Mire - Dark Weave - Stalfos" + - [ 0x0095, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 1" + - [ 0x0095, 1, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 2" + - [ 0x0095, 2, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 3" + - [ 0x0095, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 4" + - [ 0x0096, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 1 - Fire Bar (Clockwise)" + - [ 0x0098, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 1" + - [ 0x0098, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 2" + - [ 0x0098, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 3" + - [ 0x0098, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 4" + - [ 0x0098, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 5" + - [ 0x009b, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 1" + - [ 0x009b, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 2" + - [ 0x009b, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] + - [ 0x009b, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 4" + - [ 0x009b, 8, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 5" + - [ 0x009b, 9, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 6" + - [ 0x009b, 10, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 7" + - [ 0x009c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Mini Helmasaur" + - [ 0x009c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 2" + - [ 0x009c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 3" + - [ 0x009c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 4" + - [ 0x009c, 5, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 5" + - [ 0x009d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Compass Room - Gibdo 2" + - [ 0x009d, 6, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 1" + - [ 0x009d, 7, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Compass Room - Blue Bari 2" + - [ 0x009d, 8, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 3" + - [ 0x009e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 1" + - [ 0x009e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 2" + - [ 0x009e, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Stalfos Knight" + - [ 0x009e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 3" + - [ 0x00a0, 1, [ "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Boss Antechamber - Antifairy" + - [ 0x00a1, 2, [ "Statue", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Fish Room - Spark (Clockwise) 2" + - [ 0x00a1, 7, [ "Wizzrobe", "Statue"] ] # Wizzrobes can't spawn on pots + - [ 0x00a5, 2, [ "BigSpike" ] ] #"GT Wizzrobes 1 - Wizzrobe 3" + - [ 0x00a5, 10, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Laser Bridge - Red Spear Guard" + - [ 0x00a8, 1, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 2" + - [ 0x00a8, 3, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 4" + - [ 0x00a9, 1, [ "RollerHorizontalRight", "RollerHorizontalLeft" ] ] + - [ 0x00aa, 4, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - East Wing - Top - Stalfos 3" + - [ 0x00aa, 5, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - East Wing - Top - Popo B 2" + - [ 0x00ab, 7, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Thieves' Town - Spike Dodge - Spike Trap 6" + - [ 0x00ae, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 1" + - [ 0x00ae, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 2" + - [ 0x00af, 0, [ "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Ice Clock - Fire Bar (Clockwise)" + - [ 0x00b1, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 1" + - [ 0x00b1, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 2" + - [ 0x00b1, 4, ["Bumper", "BigSpike", "AntiFairyCircle" ]] + - [ 0x00b2, 1, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00b2, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00b2, 6, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Misery Mire - Sluggula Cross - Sluggula TR" + - [ 0x00b2, 8, [ "RollerVerticalDown" ] ] #"Misery Mire - Popo Push - Medusa 1" + - [ 0x00b2, 9, [ "RollerVerticalUp" ] ] #"Misery Mire - Sluggula Cross - Sluggula BL" + - [ 0x00b3, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "BigSpike", "SpikeBlock" ] ] #"Misery Mire - Spike Room - Stalfos 1" + - [ 0x00b3, 2, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "Bumper" ] ] #"Misery Mire - Spike Room - Beamos" + - [ 0x00b3, 3, [ "AntiFairyCircle", "Bumper" ] ] #"Misery Mire - Spike Room - Yomo Medusa" + - [ 0x00b6, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Tile Room - Zol 1" + - [ 0x00b6, 8, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Tile Room - Zol 2" + - [ 0x00ba, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Antifairy 1" + - [ 0x00ba, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Antifairy 2" + - [ 0x00ba, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Popo B 1" + - [ 0x00ba, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Popo B 2" + - [ 0x00bb, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Gibo 1" + - [ 0x00bb, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Antifairy 1" + - [ 0x00bb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Gibo 3" + - [ 0x00bb, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Fire Snake" + - [ 0x00bb, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Gibo 4" + - [ 0x00bb, 8, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Gibo 5" + - [ 0x00bb, 9, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Antifairy 2" + - [ 0x00bc, 6, [ "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Toilet - Stalfos 2" + - [ 0x00bc, 7, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Toilet - Stalfos 3" + - [ 0x00bc, 8, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Toilet - Stalfos 4" + - [ 0x00bf, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on collision + - [ 0x00c1, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Misery Mire - 4 Rails - Stalfos 1" + - [ 0x00c2, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Misery Mire - Main Lobby - blue - Fire Snake 1" + - [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur" + - [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4" + - [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1" + - [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2" + - [ 0x00cb, 9, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] + - [ 0x00cb, 10, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] + - [ 0x00cb, 11, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00cc, 8, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) + - [ 0x00cc, 12, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) + - [ 0x00ce, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCCW", "Bumper" ] ] #"Ice Palace - Over Boss - top - Red Bari 1" + - [ 0x00ce, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCW", "Bumper" ] ] #"Ice Palace - Over Boss - top - Red Bari 2" + - [ 0x00ce, 3, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Over Boss - top - Statue" + - [ 0x00ce, 4, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 5, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 6, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 7, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00d0, 0, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 1, [ "AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x00d0, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 5, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 6, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 7, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 9, [ "AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x00d0, 6, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Dark Maze - Blue Guard 2 + - [ 0x00d2, 8, [ "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Mire 2 - Popo BL" + - [ 0x00d5, 4, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Eye Bridge - Hardhat Beetle" + - [ 0x00d8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Red Eyegore L" + - [ 0x00d8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Red Eyegore R" + - [ 0x00d8, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B TL" + - [ 0x00d8, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B TR" + - [ 0x00d8, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B LT" + - [ 0x00d8, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B RT" + - [ 0x00d8, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo LB" + - [ 0x00d8, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo RB" + - [ 0x00d8, 8, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Kill Room 1 - Red Eyegore" + - [ 0x00d9, 1, [ "RollerHorizontalRight" ] ] #"Eastern Palace - Dodgeball - Green Eyegore 1" + - [ 0x00db, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00dc, 2, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] + - [ 0x00dc, 9, [ "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Thieves' Town - Grand Room SE - Fire Snake 2" + - [ 0x00df, 0, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Paradox Cave - Top - Mini Moldorm 1" + - [ 0x00df, 1, [ "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle" ] ] #"Paradox Cave - Top - Mini Moldorm 2" + - [ 0x00e4, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 1" + - [ 0x00e4, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 2" + - [ 0x00e4, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 3" + - [ 0x00e5, 4, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 5" + - [ 0x00e5, 5, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 6" + - [ 0x00e7, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 1" + - [ 0x00e7, 1, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 2" + - [ 0x00e7, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 3" + - [ 0x00e7, 3, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 4" + - [ 0x00e7, 4, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 5" + - [ 0x00e7, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 6" + - [ 0x00e7, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 7" + - [ 0x00e8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 1" + - [ 0x00e8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 2" + - [ 0x00ee, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 1" + - [ 0x00ee, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 2" + - [ 0x00ee, 2, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 3" + - [ 0x00ee, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Blue Bari 1" + - [ 0x00ee, 4, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Blue Bari 2" + - [ 0x00ef, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Paradox Cave - Middle - Mini Moldorm 2" + - [ 0x00f1, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 1" + - [ 0x00f1, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 2" + - [ 0x00f1, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 3" + - [ 0x00f1, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 4" + - [ 0x00f1, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 5" + - [ 0x00f1, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 6" + - [ 0x00fd, 0, [ "Bumper" ] ] + - [ 0x0107, 1, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] + - [ 0x0107, 2, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] + - [0x010b, 6, ["RollerHorizontalRight"]] + - [0x010c, 6, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] + - [0x010c, 7, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] +OwGeneralDeny: + - [0x03, 2, ["Gibo"]] # OldMan eating Gibo + - [0x03, 4, ["Gibo"]] # OldMan eating Gibo + - [0x03, 5, ["Gibo"]] # OldMan eating Gibo + - [0x03, 6, ["Gibo"]] # OldMan eating Gibo + - [0x03, 8, ["Gibo"]] # OldMan eating Gibo + - [0x03, 9, ["Gibo"]] # OldMan eating Gibo + - [0x03, 10, ["Gibo"]] # OldMan eating Gibo + - [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal + - [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here + - [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x5e, 0, ["Gibo"]] # kiki eating Gibo + - [0x5e, 1, ["Gibo"]] # kiki eating Gibo + - [0x5e, 2, ["Gibo"]] # kiki eating Gibo + - [0x5e, 3, ["Gibo"]] # kiki eating Gibo + - [0x5e, 4, ["RollerVerticalUp", "Gibo"]] # forbid that one roller for kiki pod, and the kiki eating Gibo + - [0x5e, 5, ["Gibo"]] # kiki eating Gibo + - [0x5e, 6, ["Gibo"]] # kiki eating Gibo + - [0x5e, 7, ["Gibo"]] # kiki eating Gibo + - [0x5e, 8, ["Gibo"]] # kiki eating Gibo + - [0x5e, 9, ["Gibo"]] # kiki eating Gibo + - [0x5e, 10, ["Gibo"]] # kiki eating Gibo + - [0x5e, 11, ["Gibo"]] # kiki eating Gibo + - [0x5e, 12, ["Gibo"]] # kiki eating Gibo + - [0x5e, 13, ["Gibo"]] # kiki eating Gibo + - [0x5e, 14, ["Gibo"]] # kiki eating Gibo + - [0x5e, 15, ["Gibo"]] # kiki eating Gibo + - [0x5e, 16, ["Gibo"]] # kiki eating Gibo + - [0x5e, 17, ["Gibo"]] # kiki eating Gibo + - [0x5e, 18, ["Gibo"]] # kiki eating Gibo + - [0x5e, 19, ["Gibo"]] # kiki eating Gibo + - [0x5e, 20, ["Gibo"]] # kiki eating Gibo + - [0x77, 1, ["Bumper"]] # soft-lock potential near ladder +UwEnemyDrop: + - [0x0085, 9, ["Babasu"]] # ran off the edge and didn't return + - [0x00cb, 3, ["Zoro"]] # layer issues + - [0x00cb, 5, ["Zoro"]] # layer issues + - [0x00cb, 9, ["Zoro"]] # layer issues + - [0x00cb, 10, ["Zoro"]] # layer issues + - [0x00cc, 5, ["Babasu"]] # little hard to see and kill appropriately +# the following are behind rails or otherwise unactivate-able + - [0x0077, 4, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] # can't activate here + - [0x0077, 5, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] + - [0x008D, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [0x008D, 12, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [0x00b0, 7, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay + - [0x00b0, 8, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay +# the following are not allowed at certain pits (or on conveyors near pits) +# because they despawned or clipped away or immediately fell, etc + - [0x003d, 9, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x003d, 10, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0044, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 4, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 8, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0049, 10, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007b, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007b, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007f, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0095, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x00b5, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00b5, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00b5, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 4, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Bumper", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00e6, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + # wizzrobe despawn issues - on pots/blocks - too close to some object + - [0x0013, 3, ["Wizzrobe"]] + - [0x0016, 0, ["Wizzrobe"]] + - [0x0016, 1, ["Wizzrobe"]] + - [0x0016, 2, ["Wizzrobe"]] + - [0x0016, 3, ["Wizzrobe"]] + - [0x0017, 5, ["Wizzrobe", "Stal"]] + - [0x0019, 2, ["Wizzrobe"]] + - [0x0019, 3, ["Wizzrobe"]] + - [0x001e, 1, ["Wizzrobe"]] + - [0x001e, 2, ["Wizzrobe"]] + - [0x0027, 5, ["Wizzrobe"]] + - [0x0027, 6, ["Wizzrobe"]] + - [0x002a, 3, ["Wizzrobe"]] + - [0x002a, 7, ["Wizzrobe"]] + - [0x002e, 4, ["Wizzrobe"]] + - [0x0035, 5, ["Wizzrobe"]] + - [0x0036, 8, ["Wizzrobe"]] + - [0x003b, 0, ["Wizzrobe"]] + - [0x003b, 2, ["Wizzrobe"]] + - [0x003b, 4, ["Wizzrobe"]] + - [0x003b, 6, ["Wizzrobe"]] + - [0x003c, 1, ["Wizzrobe"]] + - [0x003d, 11, ["Wizzrobe"]] + - [0x003d, 12, ["Wizzrobe"]] + - [0x003d, 13, ["Wizzrobe"]] + - [0x004b, 2, ["Wizzrobe"]] + - [0x004b, 6, ["Wizzrobe"]] + - [0x004b, 7, ["Wizzrobe"]] + - [0x004e, 3, ["Wizzrobe", "Stal"]] + - [0x0054, 3, ["Wizzrobe", "Stal"]] + - [0x0055, 2, ["Wizzrobe"]] # slightly on wall + - [0x005e, 4, ["Wizzrobe", "Stal"]] + - [0x0065, 3, ["Wizzrobe"]] + - [0x0067, 5, ["Wizzrobe"]] + - [0x0067, 6, ["Wizzrobe"]] + - [0x0067, 7, ["Wizzrobe", "Stal"]] + - [0x0067, 8, ["Wizzrobe", "Stal"]] + - [0x0074, 5, ["Wizzrobe"]] + - [0x007c, 1, ["Wizzrobe", "Stal"]] + - [0x007c, 3, ["Wizzrobe", "Stal"]] + - [0x007e, 1, ["Wizzrobe", "Stal"]] + - [0x007e, 6, ["Wizzrobe", "Stal"]] + - [0x0083, 9, ["Wizzrobe"]] + - [0x008b, 6, ["Wizzrobe", "Stal"]] + - [0x008b, 7, ["Wizzrobe", "Stal"]] + - [0x008d, 9, ["Wizzrobe", "Stal"]] + - [0x0096, 0, ["Wizzrobe", "Stal"]] + - [0x009b, 11, ["Wizzrobe"]] + - [0x009f, 5, ["Wizzrobe", "Stal"]] + - [0x00a1, 1, ["Wizzrobe"]] + - [0x00aa, 5, ["Wizzrobe"]] + - [0x00af, 0, ["Wizzrobe", "Stal"]] + - [0x00b0, 1, ["Wizzrobe"]] + - [0x00b0, 2, ["Wizzrobe"]] + - [0x00b2, 4, ["Wizzrobe"]] + - [0x00b8, 2, ["Wizzrobe"]] + - [0x00bf, 1, ["Wizzrobe"]] + - [0x00c1, 3, ["Wizzrobe", "Stal"]] + - [0x00c2, 0, ["Wizzrobe"]] + - [0x00c2, 3, ["Wizzrobe"]] + - [0x00c2, 7, ["Wizzrobe"]] + - [0x00ce, 4, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 5, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 6, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 7, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00d0, 0, ["Wizzrobe"]] + - [0x00d0, 2, ["Wizzrobe"]] + - [0x00d0, 4, ["Wizzrobe"]] + - [0x00d0, 5, ["Wizzrobe"]] + - [0x00d0, 9, ["Wizzrobe"]] + - [0x00d5, 4, ["Wizzrobe"]] + - [0x00df, 1, ["Wizzrobe"]] # slightly on wall + - [0x00e7, 4, ["Wizzrobe"]] # slightly on wall + - [0x00fd, 1, ["Wizzrobe"]] # slightly on rock + - [0x010c, 4, ["Wizzrobe"]] + - [0x010c, 5, ["Wizzrobe"]] + # other mimic cave spots are in the rail section + + # enemies that have problems with conveyors + - [0x003b, 0, ["GreenMimic", "RedMimic"]] + - [0x003b, 1, ["GreenMimic", "RedMimic"]] + - [0x003b, 2, ["GreenMimic", "RedMimic"]] + - [0x003b, 3, ["GreenMimic", "RedMimic"]] + - [0x003b, 4, ["GreenMimic", "RedMimic"]] + - [0x003b, 5, ["GreenMimic", "RedMimic"]] + - [0x003b, 6, ["GreenMimic", "RedMimic"]] + - [0x003d, 6, ["GreenMimic", "RedMimic"]] + - [0x003d, 7, ["GreenMimic", "RedMimic"]] + - [0x003e, 8, ["GreenMimic", "RedMimic"]] + - [0x003e, 9, ["GreenMimic", "RedMimic"]] + - [0x003e, 10, ["GreenMimic", "RedMimic"]] + - [0x003e, 11, ["GreenMimic", "RedMimic"]] + - [0x0044, 0, ["GreenMimic", "RedMimic"]] + - [0x0044, 1, ["GreenMimic", "RedMimic"]] + - [0x0044, 2, ["GreenMimic", "RedMimic"]] + - [0x0044, 3, ["GreenMimic", "RedMimic"]] + - [0x0044, 5, ["GreenMimic", "RedMimic"]] + - [0x004c, 0, ["GreenMimic", "RedMimic"]] + - [0x004c, 1, ["GreenMimic", "RedMimic"]] + - [0x004c, 2, ["GreenMimic", "RedMimic"]] + - [0x004c, 3, ["GreenMimic", "RedMimic"]] + - [0x004c, 4, ["GreenMimic", "RedMimic"]] + - [0x004c, 5, ["GreenMimic", "RedMimic"]] + - [0x004c, 6, ["GreenMimic", "RedMimic"]] + - [0x004c, 7, ["GreenMimic", "RedMimic"]] + - [0x005d, 3, ["GreenMimic", "RedMimic"]] + - [0x005d, 4, ["GreenMimic", "RedMimic"]] + - [0x005d, 5, ["GreenMimic", "RedMimic"]] + - [0x005d, 6, ["GreenMimic", "RedMimic"]] + - [0x005d, 8, ["GreenMimic", "RedMimic"]] + - [0x005d, 9, ["GreenMimic", "RedMimic"]] + - [0x005d, 10, ["GreenMimic", "RedMimic"]] + - [0x005d, 11, ["GreenMimic", "RedMimic"]] + - [0x005d, 12, ["GreenMimic", "RedMimic"]] +# - [0x006d, ?, ["GreenMimic", "RedMimic"]] # conveyor doesn't hit edge +# - [0x008b, ?, ["GreenMimic", "RedMimic"]] # conveyor doesn't hit edge + - [0x0092, 2, ["GreenMimic", "RedMimic"]] + - [0x00a5, 0, ["GreenMimic", "RedMimic"]] + - [0x00a5, 1, ["GreenMimic", "RedMimic"]] + - [0x00a5, 4, ["GreenMimic", "RedMimic"]] + - [0x00a5, 5, ["GreenMimic", "RedMimic"]] + - [0x00a5, 6, ["GreenMimic", "RedMimic"]] + - [0x00bb, 1, ["GreenMimic", "RedMimic"]] + - [0x00bb, 4, ["GreenMimic", "RedMimic"]] + - [0x00bb, 5, ["GreenMimic", "RedMimic"]] + - [0x00bb, 6, ["GreenMimic", "RedMimic"]] + - [0x00bb, 7, ["GreenMimic", "RedMimic"]] + - [0x00bb, 8, ["GreenMimic", "RedMimic"]] + - [0x00bb, 9, ["GreenMimic", "RedMimic"]] + - [0x00bb, 10, ["GreenMimic", "RedMimic"]] + - [0x00bc, 0, ["GreenMimic", "RedMimic"]] + - [0x00bc, 1, ["GreenMimic", "RedMimic"]] + - [0x00bc, 2, ["GreenMimic", "RedMimic"]] + - [0x00bc, 3, ["GreenMimic", "RedMimic"]] + - [0x00bc, 4, ["GreenMimic", "RedMimic"]] + - [0x00bc, 5, ["GreenMimic", "RedMimic"]] + - [0x00c1, 5, ["GreenMimic", "RedMimic"]] + - [0x00c1, 8, ["GreenMimic", "RedMimic"]] + - [0x00c1, 9, ["GreenMimic", "RedMimic"]] + - [0x00c1, 10, ["GreenMimic", "RedMimic"]] + - [0x00c1, 11, ["GreenMimic", "RedMimic"]] + - [0x00d1, 5, ["GreenMimic", "RedMimic"]] + - [0x00d1, 6, ["GreenMimic", "RedMimic"]] + + # the following are all slightly in the wall on spawn - not too applicable right now, these don't drop anyway + - [0x0064, 0, ["Leever"]] + - [0x00e5, 3, ["Wizzrobe"]] + - [0x00e5, 4, ["Leever", "Wizzrobe"]] + - [0x00e5, 5, ["Leever", "Wizzrobe"]] + # the pit one in 0xe6 room is in the pit section + - [0x00e6, 1, ["Leever", "Wizzrobe"]] + - [0x00e6, 2, ["Leever", "Wizzrobe"]] + - [0x00e6, 3, ["Leever", "Wizzrobe"]] + - [0x00e6, 4, ["Leever", "Wizzrobe"]] + - [0x00e7, 0, ["Wizzrobe"]] + - [0x00e7, 1, ["Wizzrobe"]] + - [0x00e7, 2, ["Wizzrobe"]] + - [0x00e7, 3, ["Leever"]] + - [0x00e7, 4, ["Leever", "Wizzrobe"]] + - [0x00e7, 5, ["Leever", "Wizzrobe"]] + - [0x00e7, 6, ["Wizzrobe"]] + - [0x00f0, 2, ["Leever"]] # clipped away + - [0x00f0, 3, ["Leever"]] + - [0x00f0, 4, ["Leever"]] + - [0x00f0, 5, ["Leever"]] + - [0x00f0, 6, ["Leever"]] + - [0x00f0, 8, ["Leever"]] + - [0x00f1, 0, ["Leever", "Wizzrobe"]] + - [0x00f1, 1, ["Leever", "Wizzrobe"]] + - [0x00f1, 2, ["Wizzrobe"]] + - [0x00f1, 3, ["Wizzrobe"]] + - [0x00f1, 4, ["Wizzrobe"]] + - [0x00f1, 5, ["Wizzrobe"]] + - [0x00f1, 6, ["Leever", "Wizzrobe"]] + - [0x00f1, 7, ["Leever", "Wizzrobe"]] + - [0x00f1, 8, ["Leever", "Wizzrobe"]] + - [0x00f1, 9, ["Leever", "Wizzrobe"]] + From 92f7d4da9276c929c886519fc924005ec7b8b33c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 27 Dec 2023 20:40:32 -0600 Subject: [PATCH 086/123] Added new option to export pre-seed customizer template yaml --- Main.py | 12 ++++++++---- source/classes/CustomSettings.py | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Main.py b/Main.py index 347aa451..27ab9447 100644 --- a/Main.py +++ b/Main.py @@ -75,6 +75,7 @@ def main(args, seed=None, fish=None): seed = world.customizer.determine_seed(seed) seeded = True world.customizer.adjust_args(args) + world = init_world(args, fish) if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -437,10 +438,17 @@ def init_world(args, fish): if code: Settings.adjust_args_from_code(code, player, args) + customized = None + if args.customizer: + customized = CustomSettings() + customized.load_yaml(args.customizer) + customized.adjust_args(args, False) + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + world.customizer = customized world.boots_hint = args.boots_hint.copy() world.remote_items = args.remote_items.copy() world.mapshuffle = args.mapshuffle.copy() @@ -487,10 +495,6 @@ def init_world(args, fish): world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() - world.customizer = None - if args.customizer: - world.customizer = CustomSettings() - world.customizer.load_yaml(args.customizer) return world diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 3a48132e..f15a38f0 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -48,11 +48,13 @@ class CustomSettings(object): meta = defaultdict(lambda: None, self.file_source['meta']) return meta['players'] - def adjust_args(self, args): + def adjust_args(self, args, resolve_weighted=True): def get_setting(value: Any, default): if value or value == 0: if isinstance(value, dict): - return random.choices(list(value.keys()), list(value.values()), k=1)[0] + if resolve_weighted: + return random.choices(list(value.keys()), list(value.values()), k=1)[0] + return None else: return value return default From ba904f3f63752f2a1f26e8aa84f989d99b266421 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 00:32:38 -0600 Subject: [PATCH 087/123] Fixed duplicate exit issue in copy_world --- Main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index 27ab9447..3599fee3 100644 --- a/Main.py +++ b/Main.py @@ -634,7 +634,9 @@ def copy_world(world): for exit in region.exits: if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) - ret.get_entrance(exit.name, exit.player).connect(dest_region) + src_exit = ret.get_entrance(exit.name, exit.player) + if exit.name not in [e.name for e in dest_region.entrances]: + src_exit.connect(dest_region) # fill locations for location in world.get_locations(): @@ -796,7 +798,9 @@ def copy_world_premature(world, player): for exit in region.exits: if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) - ret.get_entrance(exit.name, exit.player).connect(dest_region) + src_exit = ret.get_entrance(exit.name, exit.player) + if exit.name not in [e.name for e in dest_region.entrances]: + src_exit.connect(dest_region) from OverworldShuffle import categorize_world_regions categorize_world_regions(ret, player) From 121091c5a322eb3524fe9bcbc21754709ec6a05f Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Dec 2023 17:22:36 -0700 Subject: [PATCH 088/123] fix(generation): reduce memory usage for bunny walk calculations fix(key logic): make partial the default --- BaseClasses.py | 2 +- CLI.py | 2 +- RELEASENOTES.md | 14 +++++++++++++- Rules.py | 12 +++++++----- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c57e6a1b..ea311db9 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -170,7 +170,7 @@ class World(object): set_player_attr('door_self_loops', False) set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') - set_player_attr('key_logic_algorithm', 'default') + set_player_attr('key_logic_algorithm', 'partial') set_player_attr('shopsanity', False) set_player_attr('mixed_travel', 'prevent') diff --git a/CLI.py b/CLI.py index e0039b65..ac628018 100644 --- a/CLI.py +++ b/CLI.py @@ -228,7 +228,7 @@ def parse_settings(): "intensity": 3, "door_type_mode": "original", "trap_door_mode": "optional", - "key_logic_algorithm": "default", + "key_logic_algorithm": "partial", "decoupledoors": False, "door_self_loops": False, "experimental": False, diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0c754ac6..f299105b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,7 +109,19 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes -* 1.2.0.22u +* 1.4.0.0v + * Generation: fix for bunny walk logic taking up too much memory + * Key Logic: Partial is now the new default +* 1.3.0.9v + * Ganonhunt: playthrough no longer collects crystals + * Vanilla Fill: Uncle weapon is always a sword, medallions for Mire/TR will be vanilla + * Customizer: support shufflebosses/shuffleenemies as well as boss_shuffle/enemy_shuffle +* 1.3.0.8v + * No Logic Standard ER: Rain doors aren't blocked if no logic is enabled. + * MW Progression Balancing: Change to be percentage based instead of raw count. (80% threshold) + * Take anys: Good Bee cave chosen as take any should no longer prevent generation + * Money balancing: Fixed generation issue + 1.2.0.22u * Flute can't be activated in rain state (except glitched modes) (Thanks codemann!) * ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used) * Logic issues: diff --git a/Rules.py b/Rules.py index b2073ab1..91c35c63 100644 --- a/Rules.py +++ b/Rules.py @@ -1711,16 +1711,17 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - seen = {region} - queue = deque([(region, [])]) + queue = deque([(region, [], {region})]) + seen_sets = set([frozenset({region})]) while queue: (current, path) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region - if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: + new_seen = seen.union({new_region}) + if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_seen in seen_sets: continue new_path = path + [entrance.access_rule] - seen.add(new_region) + seen_sets.add(frozenset(new_seen)) if not is_link(new_region): if world.logic[player] == 'owglitches': if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: @@ -1757,7 +1758,8 @@ def set_bunny_rules(world, player, inverted): else: continue if is_bunny(new_region): - queue.append((new_region, new_path)) + # todo: if not owg or hmg and entrance is in bunny_impassible_doors, then skip this nonsense? + queue.append((new_region, new_path, new_seen)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) From eb0276926debfd1424491ca8d2b5da421a1066f4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 03:51:49 -0600 Subject: [PATCH 089/123] Fixed duplicate exit issue in copy_world --- Main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Main.py b/Main.py index 3599fee3..5ec9f234 100644 --- a/Main.py +++ b/Main.py @@ -635,8 +635,11 @@ def copy_world(world): if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) src_exit = ret.get_entrance(exit.name, exit.player) - if exit.name not in [e.name for e in dest_region.entrances]: - src_exit.connect(dest_region) + if exit.name not in [e.name for e in dest_region.entrances if e.connected_region is not None]: + if exit.name in [e.name for e in dest_region.entrances]: + src_exit.connected_region = dest_region + else: + src_exit.connect(dest_region) # fill locations for location in world.get_locations(): @@ -799,8 +802,11 @@ def copy_world_premature(world, player): if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) src_exit = ret.get_entrance(exit.name, exit.player) - if exit.name not in [e.name for e in dest_region.entrances]: - src_exit.connect(dest_region) + if exit.name not in [e.name for e in dest_region.entrances if e.connected_region is not None]: + if exit.name in [e.name for e in dest_region.entrances]: + src_exit.connected_region = dest_region + else: + src_exit.connect(dest_region) from OverworldShuffle import categorize_world_regions categorize_world_regions(ret, player) From 785188904d52b198dc2224d2c3337a785ceb8a74 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 03:52:57 -0600 Subject: [PATCH 090/123] Added missing OW Terrain exits --- OWEdges.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWEdges.py b/OWEdges.py index 751bd307..90786d6f 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1643,6 +1643,7 @@ OWExitTypes = { 'Lake Hylia East Pier', 'Lake Hylia Water D Approach', 'Lake Hylia Water D Leave', + 'Ice Cave Pier', 'Desert Pass Ladder (South)', 'Desert Pass Rocks (North)', 'Desert Pass Rocks (South)', @@ -1702,6 +1703,7 @@ OWExitTypes = { 'Ice Lake Northeast Pier', 'Ice Lake Northeast Pier Hop', 'Ice Lake Iceberg Water Entry', + 'Shopping Mall Pier', 'Bomber Corner Water Drop', 'Bomber Corner Pier' ], From e1a86221ea41b904a58ff8512c20b0da39bdc107 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 03:54:50 -0600 Subject: [PATCH 091/123] Fixed logic with Laser Bridge with Byrna Item Functionality --- BaseClasses.py | 4 +++- Rules.py | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ea311db9..37d365c6 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1300,7 +1300,9 @@ class CollectionState(object): return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) def can_avoid_lasers(self, player): - return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player) + return (self.has('Mirror Shield', player) or + self.has('Cape', player) or + (self.has('Cane of Byrna', player) and self.world.difficulty_adjustments[player] not in ['hard', 'expert'])) def is_not_bunny(self, region, player): return self.has_Pearl(player) or not region.can_cause_bunny(player) diff --git a/Rules.py b/Rules.py index 91c35c63..3945be15 100644 --- a/Rules.py +++ b/Rules.py @@ -604,10 +604,10 @@ def global_rules(world, player): set_rule(location, lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Final Abyss Ledge Path', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.can_avoid_lasers(player)) set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize', player)) From 359d2b132e82898239dfb35fe85ab36b633ba47b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 29 Dec 2023 04:14:44 -0600 Subject: [PATCH 092/123] fix(generation): reduce memory usage for bunny walk calculations --- Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index 3945be15..6600d819 100644 --- a/Rules.py +++ b/Rules.py @@ -1714,7 +1714,7 @@ def set_bunny_rules(world, player, inverted): queue = deque([(region, [], {region})]) seen_sets = set([frozenset({region})]) while queue: - (current, path) = queue.popleft() + (current, path, seen) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region new_seen = seen.union({new_region}) From d44ac89faf146e069848d7ec40cec5eba67f3412 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Dec 2023 17:22:36 -0700 Subject: [PATCH 093/123] fix(generation): reduce memory usage for bunny walk calculations fix(key logic): make partial the default --- BaseClasses.py | 4 ++-- CLI.py | 2 +- RELEASENOTES.md | 3 +++ Rules.py | 7 +++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6d7430f4..cfa4e48d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -149,7 +149,7 @@ class World(object): set_player_attr('door_self_loops', False) set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') - set_player_attr('key_logic_algorithm', 'default') + set_player_attr('key_logic_algorithm', 'partial') set_player_attr('shopsanity', False) set_player_attr('mixed_travel', 'prevent') @@ -1158,7 +1158,7 @@ class CollectionState(object): or self.has('Ice Rod', player) or self.has('Cane of Somaria', player) or self.has('Cane of Byrna', player)) - + def can_hit_crystal_through_barrier(self, player): return (self.can_use_bombs(player) or self.can_shoot_arrows(player) diff --git a/CLI.py b/CLI.py index 71023217..1cd4488a 100644 --- a/CLI.py +++ b/CLI.py @@ -216,7 +216,7 @@ def parse_settings(): 'intensity': 2, 'door_type_mode': 'original', 'trap_door_mode': 'optional', - 'key_logic_algorithm': 'default', + 'key_logic_algorithm': 'partial', 'decoupledoors': False, 'door_self_loops': False, 'experimental': False, diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0c754ac6..9f04d779 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.23u + * Generation: fix for bunny walk logic taking up too much memory + * Key Logic: Partial is now the new default * 1.2.0.22u * Flute can't be activated in rain state (except glitched modes) (Thanks codemann!) * ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used) diff --git a/Rules.py b/Rules.py index 1560dba2..942eef33 100644 --- a/Rules.py +++ b/Rules.py @@ -1891,14 +1891,16 @@ def set_bunny_rules(world, player, inverted): # a) being able to reach it, and # b) being able to access all entrances from there to `region` queue = deque([(region, [], {region})]) + seen_sets = set([frozenset({region})]) while queue: (current, path, seen) = queue.popleft() for entrance in current.entrances: new_region = entrance.parent_region - if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: + new_seen = seen.union({new_region}) + if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_seen in seen_sets: continue new_path = path + [entrance.access_rule] - new_seen = seen.union({new_region}) + seen_sets.add(frozenset(new_seen)) if not is_link(new_region): if world.logic[player] == 'owglitches': if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: @@ -1935,6 +1937,7 @@ def set_bunny_rules(world, player, inverted): else: continue if is_bunny(new_region): + # todo: if not owg or hmg and entrance is in bunny_impassible_doors, then skip this nonsense? queue.append((new_region, new_path, new_seen)) else: # we have reached pure light world, so we have a new possible option From 6357e4d979b555342fb894a633069f10ada51807 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 29 Dec 2023 11:16:31 -0700 Subject: [PATCH 094/123] build: bump version --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index d5b39084..0fd377c6 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.22' +version_number = '1.2.0.23' version_branch = '-u' __version__ = f'{version_number}{version_branch}' From 24d1eefb8c626c756ad3cf3ce84206b379f00fb9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 30 Dec 2023 16:48:38 -0600 Subject: [PATCH 095/123] Adding back in missing logger --- Main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Main.py b/Main.py index 5ec9f234..c1fb8cba 100644 --- a/Main.py +++ b/Main.py @@ -68,6 +68,8 @@ def main(args, seed=None, fish=None): world = init_world(args, fish) + logger = logging.getLogger('') + if args.securerandom: random.use_secure() seeded = False From c521980982430a806d7d1d16f6c0b835dec0ef89 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 31 Dec 2023 03:17:43 -0600 Subject: [PATCH 096/123] Added sparkles to Bonk Drop locations --- Regions.py | 52 +++---- Rom.py | 8 +- asm/owrando.asm | 334 +++++++++++++++++++++++------------------- data/base2current.bps | Bin 107745 -> 107783 bytes 4 files changed, 211 insertions(+), 183 deletions(-) diff --git a/Regions.py b/Regions.py index 7dd3617a..f9152e08 100644 --- a/Regions.py +++ b/Regions.py @@ -1287,32 +1287,32 @@ bonk_prize_table = { 'Forgotten Forest Southwest Tree': (0x0e, 0x10, False, '', 'Forgotten Forest Area', 'in a tree'), 'Forgotten Forest Central Tree': (0x0f, 0x08, False, '', 'Forgotten Forest Area', 'in a tree'), #'Forgotten Forest Southeast Tree': (0x10, 0x04, False, '', 'Forgotten Forest Area', 'in a tree'), - 'Hyrule Castle Tree': (0x11, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'), - 'Wooden Bridge Tree': (0x12, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'), - 'Eastern Palace Tree': (0x13, 0x10, True, '', 'Eastern Palace Area', 'in a tree'), - 'Flute Boy South Tree': (0x14, 0x10, True, '', 'Flute Boy Area', 'in a tree'), - 'Flute Boy East Tree': (0x15, 0x08, True, '', 'Flute Boy Area', 'in a tree'), - 'Central Bonk Rocks Tree': (0x16, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'), - 'Tree Line Tree 2': (0x17, 0x10, True, '', 'Tree Line Area', 'in a tree'), - 'Tree Line Tree 4': (0x18, 0x08, True, '', 'Tree Line Area', 'in a tree'), - 'Flute Boy Approach South Tree': (0x19, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'), - 'Flute Boy Approach North Tree': (0x1a, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'), - 'Dark Lumberjack Tree': (0x1b, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'), - 'Dark Fortune Bonk Rocks (Drop 1)': (0x1c, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'), - 'Dark Fortune Bonk Rocks (Drop 2)': (0x1d, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'), - 'Dark Graveyard West Bonk Rocks': (0x1e, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'), - 'Dark Graveyard North Bonk Rocks': (0x1f, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'), - 'Dark Graveyard Tomb Bonk Rocks': (0x20, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'), - 'Qirn Jump West Tree': (0x21, 0x10, False, '', 'Qirn Jump Area', 'in a tree'), - 'Qirn Jump East Tree': (0x22, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'), - 'Dark Witch Tree': (0x23, 0x10, False, '', 'Dark Witch Area', 'in a tree'), - 'Pyramid Tree': (0x24, 0x10, False, '', 'Pyramid Area', 'in a tree'), - 'Palace of Darkness Tree': (0x25, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'), - 'Dark Tree Line Tree 2': (0x26, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Dark Tree Line Tree 3': (0x27, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Dark Tree Line Tree 4': (0x28, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Hype Cave Statue': (0x29, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), - 'Cold Fairy Statue': (0x2a, 0x02, False, '', 'Good Bee Cave', 'encased in stone') + 'Hyrule Castle Tree': (0x10, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'), + 'Wooden Bridge Tree': (0x11, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'), + 'Eastern Palace Tree': (0x12, 0x10, True, '', 'Eastern Palace Area', 'in a tree'), + 'Flute Boy South Tree': (0x13, 0x10, True, '', 'Flute Boy Area', 'in a tree'), + 'Flute Boy East Tree': (0x14, 0x08, True, '', 'Flute Boy Area', 'in a tree'), + 'Central Bonk Rocks Tree': (0x15, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'), + 'Tree Line Tree 2': (0x16, 0x10, True, '', 'Tree Line Area', 'in a tree'), + 'Tree Line Tree 4': (0x17, 0x08, True, '', 'Tree Line Area', 'in a tree'), + 'Flute Boy Approach South Tree': (0x18, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'), + 'Flute Boy Approach North Tree': (0x19, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'), + 'Dark Lumberjack Tree': (0x1a, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'), + 'Dark Fortune Bonk Rocks (Drop 1)': (0x1b, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'), + 'Dark Fortune Bonk Rocks (Drop 2)': (0x1c, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'), + 'Dark Graveyard West Bonk Rocks': (0x1d, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'), + 'Dark Graveyard North Bonk Rocks': (0x1e, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'), + 'Dark Graveyard Tomb Bonk Rocks': (0x1f, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'), + 'Qirn Jump West Tree': (0x20, 0x10, False, '', 'Qirn Jump Area', 'in a tree'), + 'Qirn Jump East Tree': (0x21, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'), + 'Dark Witch Tree': (0x22, 0x10, False, '', 'Dark Witch Area', 'in a tree'), + 'Pyramid Tree': (0x23, 0x10, False, '', 'Pyramid Area', 'in a tree'), + 'Palace of Darkness Tree': (0x24, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'), + 'Dark Tree Line Tree 2': (0x25, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Dark Tree Line Tree 3': (0x26, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Dark Tree Line Tree 4': (0x27, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Hype Cave Statue': (0x28, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), + 'Cold Fairy Statue': (0x29, 0x02, False, '', 'Good Bee Cave', 'encased in stone') } bonk_table_by_location_id = {0x2ABB00+(data[0]*6)+3: name for name, data in bonk_prize_table.items()} diff --git a/Rom.py b/Rom.py index 6aafa704..d6bb10ad 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'fe9e7870071daa40829c1072829bf30b' +RANDOMIZERBASEHASH = '3f08ebba5a2c79b373dbd0c9151ade9b' class JsonRom(object): @@ -802,10 +802,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # setting spriteID to D8, a placeholder sprite we use to inform ROM to spawn a dynamic item #for address in bonk_addresses: for address in [b for b in bonk_addresses if b != 0x4D0AE]: # temp fix for screen 1A murahdahla sprite replacement - rom.write_byte(address, 0xD8) + rom.write_byte(address, 0xD9) # temporary fix for screen 1A - rom.write_byte(snes_to_pc(0x09AE32), 0xD8) - rom.write_byte(snes_to_pc(0x09AE35), 0xD8) + rom.write_byte(snes_to_pc(0x09AE32), 0xD9) + rom.write_byte(snes_to_pc(0x09AE35), 0xD9) rom.write_byte(snes_to_pc(0x06918E), 0x80) # skip good bee bottle check diff --git a/asm/owrando.asm b/asm/owrando.asm index 485bab50..bb9856b4 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -161,15 +161,22 @@ jsl.l OWWorldCheck16 : nop org $02b16e ; AND #$3F : ORA 7EF3CA and #$7f : eor #$40 : nop #2 -org $06AD4C -jsl.l OWBonkDrops : nop #4 -org $1EDE6F -jsl.l OWBonkGoodBeeDrop : bra + +org $09C3C4 +jsl.l OWBonkDropPrepSprite : nop #2 +org $09C801 +jsl.l OWBonkDropPrepSprite : nop #2 +org $06D052 +jsl.l OWBonkDropSparkle +org $06AD49 +jsl.l OWBonkDropsOverworld : nop +org $1EDE6A +jsl.l OWBonkDropSparkle : BNE GoldBee_Dormant_exit +jsl.l OWBonkDropsUnderworld : bra + GoldBee_SpawnSelf_SetProperties: phb : lda.b #$1E : pha : plb ; switch to bank 1E jsr GoldBee_SpawnSelf+12 plb : rtl -nop #3 +nop #2 + ;Code @@ -408,7 +415,66 @@ LoadMapDarkOrMixed: dw $0400+$0210 ; bottom right } -OWBonkGoodBeeDrop: +OWBonkDropPrepSprite: +{ + LDA.b $1B : BEQ + + LDA.w $0FB5 ; what we wrote over + PHA + BRA .continue + + + STZ.w $0F20,X : STZ.w $0E30,X ; what we wrote over + PHA + + .continue + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BEQ .return + + LDA.w $0E20,X : CMP.b #$D9 : BNE + + LDA.b #$03 : STA.w $0F20,X + BRA .prep + + CMP.b #$B2 : BEQ .prep + PLA : RTL + + .prep + STZ.w !SPRITE_REDRAW,X + PHB : PHK : PLB : PHY + TXY : JSR OWBonkDropLookup : BCC .done + ; found match ; X = rec + 1 + INX : LDA.w OWBonkPrizeData,X : PHA + JSR OWBonkDropCollected : PLA : BCC .done + TYX : LDA.b #$01 : STA.w !SPRITE_REDRAW,X + .done + TYX : PLY : PLB + + .return + PLA : RTL +} + +OWBonkDropSparkle: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BEQ .nosparkle + LDA.w $0E90,X : BEQ .nosparkle + LDA.w !SPRITE_REDRAW,X : BNE .nosparkle + JSL Sprite_SpawnSparkleGarnish + ; move sparkle down 1 tile + PHX : TYX : PLY + LDA.l $7FF81E,X : CLC : ADC.b #$10 : STA.l $7FF81E,X + LDA.l $7FF85A,X : ADC.b #$00 : STA.l $7FF85A,X + PHY : TXY : PLX + + .nosparkle + LDA $0E20,X : CMP.b #$D9 : BEQ .greenrupee + CMP.b #$B2 : BEQ .goodbee + RTL + + .goodbee + LDA $0E90,X ; what we wrote over + RTL + + .greenrupee + JSL Sprite_DrawRippleIfInWater ; what we wrote over + RTL +} + +OWBonkDropsUnderworld: { LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled .vanilla ; what we wrote over @@ -416,20 +482,63 @@ OWBonkGoodBeeDrop: LDA.l BottleContentsOne : ORA.l BottleContentsTwo ORA.l BottleContentsThree : ORA.l BottleContentsFour RTL + .shuffled LDA.w $0DD0,X : BNE + - JMP .return+1 + BRA .return+1 + PHY : TXY - LDA.l RoomDataWRAM[$0120].high : AND.b #$02 : PHA : BNE + ; check if collected - LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - + - LDA.l OWBonkPrizeTable[42].mw_player : BEQ + ; multiworld item - LDA.l OWBonkPrizeTable[42].loot - JMP .spawn_item - + + JSL OWBonkDrops - .determine_type ; S = Collected - LDA.l OWBonkPrizeTable[42].loot ; A = item id + .return + PLY + LDA #$08 ; makes original good bee not spawn + RTL +} + +OWBonkDropsOverworld: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled + BRA .vanilla + + .shuffled + LDA.w $0DD0,Y : BNE + + BRA .vanilla + + LDA.w $0E20,Y : CMP.b #$D9 : BEQ + + BRA .vanilla+3 + + + LDA.b #$00 : STA.w $0F20,Y ; restore proper layer + JSL OWBonkDrops + + .vanilla + LDA.w $0E20,Y : CMP.b #$D8 ; what we wrote over + RTL +} + +OWBonkDrops: +{ + PHB : PHK : PLB + LDA.b $1B : BEQ + + LDX.b #((UWBonkPrizeData-OWBonkPrizeData)+1) + BRA .found_match + + + JSR OWBonkDropLookup : BCS .found_match + JMP .return+2 + + .found_match + INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) + JSR OWBonkDropCollected : PHA : BCS .load_item_and_mw ; S = Collected, FlagBitmask, X (row + 2) + LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx + ; JSLSpriteSFX_QueueSFX3WithPan + + .load_item_and_mw + LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X + PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + + ; multiworld item + DEX : PLA ; A = item id; X = row + 3 + JMP .spawn_item + + DEX : PLA ; A = item id; X = row + 3 + + .determine_type ; A = item id; X = row + 3; S = Collected, FlagBitmask, X (row + 2) CMP.b #$B0 : BNE + LDA.b #$79 : JMP .sprite_transform ; transform to bees + CMP.b #$42 : BNE + @@ -461,140 +570,12 @@ OWBonkGoodBeeDrop: LDA.b #$AC : BRA .sprite_transform ; transform to apples + CMP.b #$B2 : BNE + LDA.b #$E3 : BRA .sprite_transform ; transform to fairy - + CMP.b #$B3 : BNE .spawn_item - INX : INX : LDA.l OWBonkPrizeTable[42].vert_offset - CLC : ADC.b #$08 : PHA - LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y - LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX - LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken - - .sprite_transform - JSL.l OWBonkSpritePrep - - .mark_collected ; S = Collected - PLA : BNE + - LDA.l RoomDataWRAM[$0120].high : ORA.b #$02 : STA.l RoomDataWRAM[$0120].high - - REP #$20 - LDA.l TotalItemCounter : INC : STA.l TotalItemCounter - SEP #$20 - + BRA .return - - ; spawn itemget item - .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected - PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return - + PHA - - LDA.b #$01 : STA !FORCE_HEART_SPAWN - - LDA.b #$EB : STA.l $7FFE00 - JSL Sprite_SpawnDynamically+15 ; +15 to skip finding a new slot, use existing sprite - - LDA.b #$01 : STA.w !SPRITE_REDRAW,Y - - PLA : STA.w $0E80,Y - - ; affects the rate the item moves in the Y/X direction - LDA.b #$00 : STA.w $0D40,Y - LDA.b #$0A : STA.w $0D50,Y - - LDA.b #$1A : STA.w $0F80,Y ; amount of force (gives height to the arch) - LDA.b #$FF : STA.w $0B58,Y ; stun timer - LDA.b #$30 : STA.w $0F10,Y ; aux delay timer 4 ?? dunno what that means - - LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on - - ; sets the tile type that is underneath the sprite, water - TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this - - ; sets OW event bitmask flag, uses free RAM - LDA.l OWBonkPrizeTable[42].flag : STA.w $0ED0,Y - - ; determines the initial spawn point of item - LDA.w $0D00,Y : SEC : SBC.l OWBonkPrizeTable[42].vert_offset : STA.w $0D00,Y - LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - - .return - PLY - LDA #$08 ; makes original good bee not spawn - RTL -} - -; Y = sprite slot index of bonk sprite -OWBonkDrops: -{ - CMP.b #$D8 : BEQ + - RTL - + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE + - JSL.l Sprite_TransmuteToBomb : RTL - + LDA.w $0DD0,Y : BNE + - RTL - + - - ; loop thru rando bonk table to find match - PHB : PHK : PLB - LDA.b $8A - LDX.b #(41*6) ; 41 bonk items, 6 bytes each - - CMP.w OWBonkPrizeData,X : BNE + - INX - LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A - EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1 - BRA .found_match - ++ DEX : LDA.b $8A - + CPX.b #$00 : BNE + - PLB : RTL - + DEX : DEX : DEX : DEX : DEX : DEX : BRA - - - .found_match - INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) - LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 1,S : PHA : BNE + ; S = Collected, FlagBitmask, X (row + 2) - LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - + - LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X - PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + - ; multiworld item - DEX : PLA ; X = row + 3 - JMP .spawn_item - + DEX : PLA ; X = row + 3 - - .determine_type ; A = item id ; S = Collected, FlagBitmask, X (row + 2) - CMP.b #$B0 : BNE + - LDA.b #$79 : JMP .sprite_transform ; transform to bees - + CMP.b #$42 : BNE + - JSL.l Sprite_TransmuteToBomb ; transform a heart to bomb, vanilla behavior - JMP .mark_collected - + CMP.b #$34 : BNE + - LDA.b #$D9 : CLC : JMP .sprite_transform ; transform to single rupee - + CMP.b #$35 : BNE + - LDA.b #$DA : CLC : JMP .sprite_transform ; transform to blue rupee - + CMP.b #$36 : BNE + - LDA.b #$DB : CLC : BRA .sprite_transform ; transform to red rupee - + CMP.b #$27 : BNE + - LDA.b #$DC : CLC : BRA .sprite_transform ; transform to 1 bomb - + CMP.b #$28 : BNE + - LDA.b #$DD : CLC : BRA .sprite_transform ; transform to 4 bombs - + CMP.b #$31 : BNE + - LDA.b #$DE : CLC : BRA .sprite_transform ; transform to 8 bombs - + CMP.b #$45 : BNE + - LDA.b #$DF : CLC : BRA .sprite_transform ; transform to small magic - + CMP.b #$B4 : BNE + - LDA.b #$E0 : CLC : BRA .sprite_transform ; transform to big magic - + CMP.b #$B5 : BNE + - LDA.b #$79 : JSL.l OWBonkSpritePrep - JSL.l GoldBee_SpawnSelf_SetProperties ; transform to good bee - BRA .mark_collected - + CMP.b #$44 : BNE + - LDA.b #$E2 : CLC : BRA .sprite_transform ; transform to 10 arrows - + CMP.b #$B1 : BNE + - LDA.b #$AC : BRA .sprite_transform ; transform to apples - + CMP.b #$B2 : BNE + - LDA.b #$E3 : BRA .sprite_transform ; transform to fairy + CMP.b #$B3 : BNE .spawn_item INX : INX : LDA.w OWBonkPrizeData,X ; X = row + 5 CLC : ADC.b #$08 : PHA LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX - LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken + LDA.b #$0B ; BRA .sprite_transform ; transform to chicken .sprite_transform JSL.l OWBonkSpritePrep @@ -602,16 +583,22 @@ OWBonkDrops: .mark_collected ; S = Collected, FlagBitmask, X (row + 2) PLA : BNE + ; S = FlagBitmask, X (row + 2) TYX : JSL Sprite_IsOnscreen : BCC + + LDA.b $1B : BEQ ++ + LDA.l RoomDataWRAM[$0120].high : ORA 1,S : STA.l RoomDataWRAM[$0120].high + LDA.w $0400 : ORA 1,S : STA.w $0400 + BRA .increment_collection + ++ LDX.b $8A : LDA.l OverworldEventDataWRAM,X : ORA 1,S : STA.l OverworldEventDataWRAM,X + .increment_collection REP #$20 LDA.l TotalItemCounter : INC : STA.l TotalItemCounter SEP #$20 - + JMP .return + + BRA .return ; spawn itemget item - .spawn_item ; A = item id ; Y = tree sprite slot ; S = Collected, FlagBitmask, X (row + 2) - PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : JMP .return ; S = FlagBitmask, X (row + 2) + .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected, FlagBitmask, X (row + 2) + PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return ; S = FlagBitmask, X (row + 2) + PHA LDA.b #$01 : STA !FORCE_HEART_SPAWN @@ -633,18 +620,57 @@ OWBonkDrops: LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on - ; sets OW event bitmask flag, uses free RAM + LDA.b $1B : BEQ + + ; sets the tile type that is underneath the sprite, water + TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this + + + + ; sets bitmask flag, uses free RAM PLA : STA.w $0ED0,Y ; S = X (row + 2) ; determines the initial spawn point of item PLX : INX : INX : INX LDA.w $0D00,Y : SEC : SBC.w OWBonkPrizeData,X : STA.w $0D00,Y LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - - PLB : RTL + + BRA .return+2 .return - PLA : PLA : PLB : RTL + PLA : PLA : PLB + RTL +} + +; Y = sprite slot; returns X = row + 1 +OWBonkDropLookup: +{ + ; loop thru rando bonk table to find match + LDA.b $8A + LDX.b #((UWBonkPrizeData-OWBonkPrizeData)-sizeof(OWBonkPrizeTable)) ; 41 bonk items, 6 bytes each + - CMP.w OWBonkPrizeData,X : BNE + + INX + LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A + EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1 + SEC : RTS + ++ DEX : LDA.b $8A + + CPX.b #$00 : BNE + + CLC : RTS + + DEX : DEX : DEX : DEX : DEX : DEX : BRA - +} + +; S = FlagBitmask ; returns SEC if collected +OWBonkDropCollected: +{ + ; check if collected + CLC + LDA.b $1B : BEQ + + LDA.l RoomDataWRAM[$0120].high : AND.b 3,S : BEQ .return ; S = Collected, FlagBitmask, X (row + 2) + SEC : RTS + + + LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 3,S : BEQ .return ; S = Collected, FlagBitmask, X (row + 2) + SEC : RTS + + .return + RTS } ; A = SpriteID, Y = Sprite Slot Index, X = free/overwritten @@ -1717,7 +1743,7 @@ db $18, $a8, $10, $b2, $00, $20 db $18, $36, $08, $35, $00, $20 db $1a, $8a, $10, $42, $00, $20 db $1a, $1d, $08, $b2, $00, $20 -db $ff, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree +;db $1a, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree db $1b, $46, $10, $b1, $00, $10 db $1d, $6b, $10, $b1, $00, $20 db $1e, $72, $10, $b2, $00, $20 @@ -1728,6 +1754,7 @@ db $2e, $9c, $10, $b2, $00, $20 db $2e, $b4, $08, $b0, $00, $20 db $32, $29, $10, $42, $00, $20 db $32, $9a, $08, $b2, $00, $20 +;db $34, $xx, $10, $xx, $00, $1c ; pre aga ONLY db $42, $66, $10, $b2, $00, $20 db $51, $08, $10, $b2, $00, $04 db $51, $09, $08, $b2, $00, $04 @@ -1743,6 +1770,7 @@ db $6e, $8c, $10, $35, $00, $10 db $6e, $90, $08, $b0, $00, $10 db $6e, $a4, $04, $b1, $00, $10 db $74, $4e, $10, $b1, $00, $1c +UWBonkPrizeData: db $ff, $00, $02, $b5, $00, $08 ; temporary fix - murahdahla replaces one of the bonk tree prizes diff --git a/data/base2current.bps b/data/base2current.bps index a21ad3abf7406231397ccf4c057391d8ef5b7d7a..c31273e3cc04262cd7610b9c7a1a14d8fea61339 100644 GIT binary patch delta 1198 zcmW-eZA?>F7{~9q_x9cbrNGp!gxJ*z8%#tI7-%%x3?e9QIx7P0Gt*6CmY7BQjc zTogm0-ohPRyCV3KE^8}kwZo3x)x{XI+%DN}7WcyI6ijSbTyC}xDj#&Go)72uJkNRl z&+|{tU70x@XTsfpzSo!28R5VKHsupx(7fafEawX+Qe66px58gTunbvqt22-r$xQ8~ zGp#+tW)tpMu0JmtSGJ!N&gx6aHb>-VeHmFezM+jrn?B67%x`KjPOm{Zsx?nA>`LSaKELK`94v$Vel zrQYPw20o0CSOQUzL_u^~)FI>&{y3Pg5wP4N(3X5KE^M@9*Z6QA6giiLQ1U{}9J&(h z=tWV{+KXcN<&Y$yo8s?C#&I2g7X2c+(Yw-K4*eE7d=l+m;(jbe!%fNcvjT51thyWI z(0%bbdLULp)Xp0r`Y-aGC6h8_a<{&c0vrKChn6;Tj|q7X773&$viQ#5pJ~omHbS59 zYy28j{*gD*|I@9lv_Mn6qD2ELDAT?#qPzwiA}NykK6;-8?`?W%sHm*x0>dyG22hMv zT+@^ZeI#HgiUfeB#k#b>*b5yOqPB9NWk6@NZ3n1h9FuBFCgaOdnTg4aYRHbX)NrWU zOB()ZfMqe0UY=H1KXuNO0U7zKO4{sWZnl4Ku+eK88gOSNVuVy}x;|iYGm;;ibF)%c z&?dH*4$qTnLCmdJb7O8+dC{zeni;9<2je5hmuxFPVt9r3j5j`VGTKXH*TdrG)G^!b}TzG;<&IzQnt zW$?X2Z2Rm;{Whlts`SN&P>cPAsap3y(&9|gV|7^L&v9)6m#Bb{hIoQYfvP6%u+`=b z#BFj|(fE(o-s_ilDy+GFf~igP%cZzB%`1atm@|A|9`~w3`)6UB{4(YoY?1RZ=iw2# z7;^zO$h$EAf(PVpFz>(`c~a5&kDF%(0_M&IBitcZ;`$;imakx5h6VCfg+APvdi;2HKPg$=p17-d&78YIW$1f4eEb<9mXjDc~Y# zs1u9xro_6VgOPk29E2?^Dmy}rqW8sECCGLNud#C;tYjsB%20>w@hJM0L?SV?{&{$c zkV3n5IAf&E7PR}E7u9(}Qr0A z^E(mX?y1~pGtj+QJ$=?EfQM1B$Hab<)8UyTYl(O)+Ntk;b94lPxvjqVMQu7d)hkMI z8FZn}Hxrjhrwr_G9Jt5BSb2H4IYqBAJOtVEwenV&o&v)gL>{*1gj2<~Xej0wUt$ur z*v0pZ8<{n^t-gL^clxr_)2b=a?tun}w9@QN?3wmj=X^!EojTCyn<|)y0SjW^TThJ# z@F-g063`JWpp`@$MX7Xo>c{vE)UBtqC>hDGK(sTs>r=F7Qb226qv!YU3g5n zj1WbM&%1q3_B{B29h*(8h2n$2C8j#7&v*96E1PvEx9oM`1{P#9>q^}I5f<#GnGLl+ zT+)NRX-T4?{_ih2j$=8%aC#iy)Hh56j$vp3bUIuf;~A*g*6$ZK0X+vA{f<{bIcIZ* zqnB`)=6`S1mIaO)v`5wn&7d*Gc1M+4-tlL13T@U{X}L2E$kb&|7}p(kB?zk>@`B<~ zM@gosm~*JPT9m2cE3SBvL(L$un~_*1F}ETnOVdfW;fLzWM5mxtO(uE=)~V}>PTOF$ znorCOELFcDdKVU{V_K}#WXg1UL?*Z%Zddb2d=_q0FA}`~*Q#F=4Z>BbPum^}$>6ew zKcF{{Q-nbpq zW1!)+A;v?(HpLuChS`9n_?M1=#eJrAq+_V%%7qSslap@p#UmYrCnwz!^IBhrS%mCu z9v&k^S!s%gM+j0@y2Hc$gefac^Kc(w%1SdlEF(}^=`Mf0sHK~gW_h^5ZAWX|!mx?Z zGlwFtKI!SNN>ohsV_$SWhi#^DT;9;-Tb*&Pt9ZS0K8%x&(y*P_8kkPpxTRN%n-7IT z5#0Y#ink6yHrqKVM|vGaa;WJRlv10=FnXCt^$f09rqjjry78DlRoX*%}=J13eEom Dv}Ptf From 108b67cbcfaa67e8a3559e3248d994943db3afe3 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 2 Jan 2024 17:09:08 -0700 Subject: [PATCH 097/123] fix: fix up some vanilla key logic fix: fix tile pattern --- DoorShuffle.py | 22 +++++++++++++++++++--- Main.py | 10 ++++++++++ RELEASENOTES.md | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 474df204..7f824683 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -7,7 +7,7 @@ from typing import DefaultDict, Dict, List from itertools import chain from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys -from BaseClasses import PotFlags, LocationType, Direction +from BaseClasses import PotFlags, LocationType, Direction, KeyRuleType from Doors import reset_portals from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts from Dungeons import dungeon_bigs, dungeon_hints @@ -261,8 +261,24 @@ def vanilla_key_logic(world, player): world.key_logic[player][builder.name] = key_layout.key_logic world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) - # if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: - # validate_vanilla_key_logic(world, player) + # special adjustments for vanilla + if world.mode[player] != 'standard': + # adjust hc doors + def adjust_hc_door(door_rule): + if door_rule.new_rules[KeyRuleType.WorstCase] == 3: + door_rule.new_rules[KeyRuleType.WorstCase] = 2 + door_rule.small_key_num = 2 + + rules = world.key_logic[player]['Hyrule Castle'].door_rules + adjust_hc_door(rules['Sewers Secret Room Key Door S']) + adjust_hc_door(rules['Hyrule Dungeon Map Room Key Door S']) + adjust_hc_door(rules['Sewers Dark Cross Key Door N']) + # adjust pod front door + pod_front = world.key_logic[player]['Palace of Darkness'].door_rules['PoD Middle Cage N'] + if pod_front.new_rules[KeyRuleType.WorstCase] == 6: + pod_front.new_rules[KeyRuleType.WorstCase] = 1 + pod_front.small_key_num = 1 + # gt logic? I'm unsure it needs adjusting def validate_vanilla_reservation(dungeon, world, player): diff --git a/Main.py b/Main.py index c1fb8cba..735942a2 100644 --- a/Main.py +++ b/Main.py @@ -573,6 +573,10 @@ def copy_world(world): ret.intensity = world.intensity.copy() ret.decoupledoors = world.decoupledoors.copy() ret.door_self_loops = world.door_self_loops.copy() + ret.door_type_mode = world.door_type_mode.copy() + ret.trap_door_mode = world.trap_door_mode.copy() + ret.key_logic_algorithm = world.key_logic_algorithm.copy() + ret.aga_randomness = world.aga_randomness.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() @@ -754,6 +758,12 @@ def copy_world_premature(world, player): ret.enemy_damage = world.enemy_damage.copy() ret.beemizer = world.beemizer.copy() ret.intensity = world.intensity.copy() + ret.decoupledoors = world.decoupledoors.copy() + ret.door_self_loops = world.door_self_loops.copy() + ret.door_type_mode = world.door_type_mode.copy() + ret.trap_door_mode = world.trap_door_mode.copy() + ret.key_logic_algorithm = world.key_logic_algorithm.copy() + ret.aga_randomness = world.aga_randomness.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f299105b..9cbf5b6b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,8 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.4.0.1v + * Key logic: Vanilla key logic fixes. Statically set some HC logic and PoD front door * 1.4.0.0v * Generation: fix for bunny walk logic taking up too much memory * Key Logic: Partial is now the new default From 6edc2959514cb638772bcd154b8953b44b85a518 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 2 Jan 2024 18:14:31 -0700 Subject: [PATCH 098/123] fix: vanilla key logic adjustment (dropshuffle must be none for reduction) --- DoorShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 7f824683..99577f37 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -262,7 +262,7 @@ def vanilla_key_logic(world, player): world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) # special adjustments for vanilla - if world.mode[player] != 'standard': + if world.mode[player] != 'standard' and world.dropshuffle[player] == 'none': # adjust hc doors def adjust_hc_door(door_rule): if door_rule.new_rules[KeyRuleType.WorstCase] == 3: From 7c5a5f39fe89b9858addadd7b65a0968433a2655 Mon Sep 17 00:00:00 2001 From: Cody Bailey Date: Sun, 19 Nov 2023 17:32:45 -0500 Subject: [PATCH 099/123] Add aga_randomness to exposed settings --- BaseClasses.py | 2 +- CLI.py | 3 ++- Main.py | 1 + Rom.py | 2 +- resources/app/cli/args.json | 4 ++++ source/classes/CustomSettings.py | 2 ++ source/tools/MysteryUtils.py | 1 + 7 files changed, 12 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 37d365c6..e8f13ade 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -64,7 +64,6 @@ class World(object): self.dark_world_light_cone = False self.clock_mode = 'none' self.rupoor_cost = 10 - self.aga_randomness = True self.lock_aga_door_in_escape = False self.save_and_quit_from_boss = True self.override_bomb_check = False @@ -171,6 +170,7 @@ class World(object): set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') set_player_attr('key_logic_algorithm', 'partial') + set_player_attr('aga_randomness', True) set_player_attr('shopsanity', False) set_player_attr('mixed_travel', 'prevent') diff --git a/CLI.py b/CLI.py index ac628018..92decc8e 100644 --- a/CLI.py +++ b/CLI.py @@ -145,7 +145,7 @@ def parse_cli(argv, no_defaults=False): 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', - 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']: + 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'aga_randomness']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -235,6 +235,7 @@ def parse_settings(): "dungeon_counters": "default", "mixed_travel": "prevent", "standardize_palettes": "standardize", + 'aga_randomness': True, "triforce_pool": 0, "triforce_goal": 0, diff --git a/Main.py b/Main.py index 735942a2..cd6c80f0 100644 --- a/Main.py +++ b/Main.py @@ -496,6 +496,7 @@ def init_world(args, fish): world.restrict_boss_items = args.restrict_boss_items.copy() world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() + world.aga_randomness = args.aga_randomness.copy() return world diff --git a/Rom.py b/Rom.py index d6bb10ad..85a7dd67 100644 --- a/Rom.py +++ b/Rom.py @@ -1391,7 +1391,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable - rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness + rom.write_byte(0x180086, 0x00 if world.aga_randomness[player] else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index e723ee76..276b581c 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -575,6 +575,10 @@ "action": "store_true", "type": "bool" }, + "aga_randomness": { + "action": "store_false", + "type": "bool" + }, "settingsonload": { "choices": [ "default", diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index f15a38f0..0cf93eb3 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -169,6 +169,7 @@ class CustomSettings(object): args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p]) args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p]) args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p]) + args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[p]) # mystery usage args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p]) @@ -326,6 +327,7 @@ class CustomSettings(object): settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p] settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p] settings_dict[p]['beemizer'] = world.beemizer[p] + settings_dict[p]['aga_randomness'] = world.aga_randomness[p] if world.precollected_items: start_inv[p] = [] for item in world.precollected_items: diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index afaf6108..2b8ba457 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -114,6 +114,7 @@ def roll_settings(weights): ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on' ret.shufflepots = get_choice('pot_shuffle') == 'on' + ret.aga_randomness = get_choice('aga_randomness') == 'on' ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent' ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize') From b5795e4bf258ff7c1fa190d0c6d65a9c7a5db4e1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 27 Dec 2023 13:00:44 -0700 Subject: [PATCH 100/123] Merge branch 'KrisDavie-hmg_logic' into DoorDevVolatile --- BaseClasses.py | 53 +++- EntranceShuffle.py | 2 +- Fill.py | 5 +- ItemList.py | 13 +- KeyDoorShuffle.py | 16 + Main.py | 23 +- OWEdges.py | 49 +-- OverworldGlitchRules.py | 181 ++++++------ OverworldShuffle.py | 4 +- Rom.py | 6 +- Rules.py | 72 ++++- UnderworldGlitchRules.py | 278 ++++++++++++++++++ resources/app/cli/args.json | 1 + resources/app/cli/lang/en.json | 15 +- resources/app/gui/lang/en.json | 1 + resources/app/gui/randomize/item/widgets.json | 1 + source/overworld/EntranceShuffle2.py | 2 +- source/tools/MysteryUtils.py | 5 +- test/customizer/hmg/fireless_ice.yaml | 35 +++ test/customizer/hmg/hammer_in_swamp.yaml | 42 +++ test/customizer/hmg/mearl_in_pod.yaml | 34 +++ test/customizer/hmg/mirrorless_swamp.yaml | 42 +++ test/customizer/hmg/pod_as_connector.yaml | 44 +++ test/customizer/hmg/swamp_as_connector.yaml | 55 ++++ .../hmg/swamp_small_in_swamp_back.yaml | 44 +++ test/suite/hmg/entrance_bunny_pocket_sw.yaml | 34 +++ test/suite/hmg/fireless_ice.yaml | 17 ++ test/suite/hmg/flippers_locked_flippers.yaml | 20 ++ test/suite/hmg/flippers_wraps.yaml | 29 ++ test/suite/hmg/hera_from_mire.yaml | 26 ++ .../hmg/inverted_inaccessible_desert.yaml | 26 ++ test/suite/hmg/inverted_moon_pearl_locs.yaml | 31 ++ test/suite/hmg/moon_pearl_locs.yaml | 48 +++ test/suite/hmg/pearlless_sw.yaml | 25 ++ test/suite/hmg/swamp_from_mire.yaml | 43 +++ 35 files changed, 1168 insertions(+), 154 deletions(-) create mode 100644 UnderworldGlitchRules.py create mode 100644 test/customizer/hmg/fireless_ice.yaml create mode 100644 test/customizer/hmg/hammer_in_swamp.yaml create mode 100644 test/customizer/hmg/mearl_in_pod.yaml create mode 100644 test/customizer/hmg/mirrorless_swamp.yaml create mode 100644 test/customizer/hmg/pod_as_connector.yaml create mode 100644 test/customizer/hmg/swamp_as_connector.yaml create mode 100644 test/customizer/hmg/swamp_small_in_swamp_back.yaml create mode 100644 test/suite/hmg/entrance_bunny_pocket_sw.yaml create mode 100644 test/suite/hmg/fireless_ice.yaml create mode 100644 test/suite/hmg/flippers_locked_flippers.yaml create mode 100644 test/suite/hmg/flippers_wraps.yaml create mode 100644 test/suite/hmg/hera_from_mire.yaml create mode 100644 test/suite/hmg/inverted_inaccessible_desert.yaml create mode 100644 test/suite/hmg/inverted_moon_pearl_locs.yaml create mode 100644 test/suite/hmg/moon_pearl_locs.yaml create mode 100644 test/suite/hmg/pearlless_sw.yaml create mode 100644 test/suite/hmg/swamp_from_mire.yaml diff --git a/BaseClasses.py b/BaseClasses.py index e8f13ade..3145ee16 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -133,7 +133,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'] + set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'hybridglitches', 'nologic'] or shuffle[player] in ['lean', 'swapped', 'crossed', 'insanity']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) @@ -603,6 +603,42 @@ class CollectionState(object): self.placing_items = None # self.trace = None + def can_reach_from(self, spot, start, player=None): + old_state = self.copy() + # old_state.path = {old_state.world.get_region(start, player)} + old_state.stale[player] = False + old_state.reachable_regions[player] = dict() + old_state.blocked_connections[player] = dict() + rrp = old_state.reachable_regions[player] + bc = old_state.blocked_connections[player] + + # init on first call - this can't be done on construction since the regions don't exist yet + start = self.world.get_region(start, player) + if start in self.reachable_regions[player]: + rrp[start] = self.reachable_regions[player][start] + for conn in start.exits: + bc[conn] = self.blocked_connections[player][conn] + else: + rrp[start] = CrystalBarrier.Orange + for conn in start.exits: + bc[conn] = CrystalBarrier.Orange + + queue = deque(old_state.blocked_connections[player].items()) + + old_state.traverse_world(queue, rrp, bc, player) + if old_state.world.key_logic_algorithm[player] == 'default': + unresolved_events = [x for y in old_state.reachable_regions[player] for x in y.locations + if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) + and x not in old_state.locations_checked and x.can_reach(old_state)] + unresolved_events = old_state._do_not_flood_the_keys(unresolved_events) + if len(unresolved_events) == 0: + old_state.check_key_doors_in_dungeons(rrp, player) + + if self.world.get_region(spot, player) in rrp: + return True + else: + return False + def update_reachable_regions(self, player): self.stale[player] = False rrp = self.reachable_regions[player] @@ -1020,7 +1056,7 @@ class CollectionState(object): # try to resolve a name if resolution_hint == 'Location': spot = self.world.get_location(spot, player) - elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'Ledge', 'Portal', 'Whirlpool', 'Mirror', 'Flute']: + elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'OpenTerrain', 'Ledge', 'OWG', 'Portal', 'Whirlpool', 'Mirror', 'Flute']: spot = self.world.get_entrance(spot, player) else: # default to Region @@ -1168,6 +1204,12 @@ class CollectionState(object): def can_lift_rocks(self, player): return self.has('Power Glove', player) or self.has('Titans Mitts', player) + + def can_bomb_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) and self.can_use_bombs(player) + + def can_dash_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) def has_bottle(self, player): return self.bottle_count(player) > 0 @@ -1347,6 +1389,9 @@ class CollectionState(object): def can_superbunny_mirror_with_sword(self, player): return self.has_Mirror(player) and self.has_sword(player) + + def can_bunny_pocket(self, player): + return self.has_Boots(player) and (self.has_Mirror(player) or self.has_bottle(player)) def collect(self, item, event=False, location=None): if location: @@ -1631,7 +1676,7 @@ class Entrance(object): if region not in explored_regions: explored_regions[region] = path for exit in region.exits: - if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \ + if exit.connected_region and (not ignore_ledges or exit.spot_type not in ['Ledge', 'OWG']) \ and exit.name not in ['Dig Game To Ledge Drop'] \ and exit.access_rule(state): if exit.connected_region == destination: @@ -3422,7 +3467,7 @@ er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10} # byte 1: LLLW WSS? (logic, mode, sword) -logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} +logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5} world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ff875c59..9bae0b11 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -814,7 +814,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, must # Keeps track of entrances that cannot be used to access each exit / cave invalid_cave_connections = defaultdict(set) - # if world.logic[player] in ['owglitches', 'nologic']: + # if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: # import OverworldGlitchRules # for entrance in OverworldGlitchRules.get_non_mandatory_exits(world, player): # if entrance in must_be_exits: diff --git a/Fill.py b/Fill.py index 5c1214a1..6bb233b3 100644 --- a/Fill.py +++ b/Fill.py @@ -175,6 +175,9 @@ def valid_key_placement(item, location, key_pool, collection_state, world): if dungeon: if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True + # Small key and big key in Swamp and Hera are placed without logic + if world.logic[item.player] == 'hybridglitches' and dungeon.name in ['Tower of Hera', 'Swamp Palace'] and dungeon.name in item.name: + return True key_logic = world.key_logic[item.player][dungeon.name] unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None @@ -420,7 +423,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in range(1, world.players + 1): if (not gftower_trash or not world.ganonstower_vanilla[player] - or world.logic[player] in ['owglitches', 'nologic']): + or world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']): continue gt_count, total_count = calc_trash_locations(world, player) scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7) diff --git a/ItemList.py b/ItemList.py index d603ae0e..9af16cf6 100644 --- a/ItemList.py +++ b/ItemList.py @@ -327,6 +327,11 @@ def generate_itempool(world, player): for _ in range(0, amt): pool.append('Rupees (20)') + if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: + # In HMG force swamp smalls in pots to allow getting out of swamp palace + placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' + placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)' + start_inventory = list(world.precollected_items) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -1056,7 +1061,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt return random.choice([True, False]) if progressive == 'random' else progressive == 'on' # provide boots to boots glitch dependent modes - if logic in ['owglitches', 'nologic']: + if logic in ['owglitches', 'hybridglitches', 'nologic']: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.extend(['Rupees (20)']) @@ -1362,12 +1367,12 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer start_inventory = [x for x in world.precollected_items if x.player == player] if not start_inventory: - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and all(x.name != 'Pegasus Boots' for x in start_inventory): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: pool.remove('Pegasus Boots') pool.append('Rupees (20)') - if world.swords[player] == 'assured': + if world.swords[player] == 'assured' and all(' Sword' not in x.name for x in start_inventory): precollected_items.append('Progressive Sword') if 'Progressive Sword' in pool: pool.remove('Progressive Sword') @@ -1503,7 +1508,7 @@ def make_customizer_pool(world, player): sphere_0 = world.customizer.get_start_inventory() no_start_inventory = not sphere_0 or not sphere_0[player] init_equip = [] if no_start_inventory else sphere_0[player] - if (world.logic[player] in ['owglitches', 'nologic'] + if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 73bf24bf..6f0cce5d 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -2106,6 +2106,22 @@ def validate_key_placement(key_layout, world, player): if i.player == player and i.name == smallkey_name: keys_outside += 1 + if world.logic[player] == 'hybridglitches': + # Swamp keylogic + if smallkey_name.endswith('(Swamp Palace)'): + swamp_entrance = world.get_location('Swamp Palace - Entrance', player) + # Swamp small not vanilla + if swamp_entrance.item is None or (swamp_entrance.item.name != smallkey_name or swamp_entrance.item.player != player): + mire_keylayout = world.key_layout[player]['Misery Mire'] + mire_smallkey_name = dungeon_keys[mire_keylayout.sector.name] + # Check if any mire keys are in swamp (excluding entrance), if none then add one to keys_outside + mire_keys_in_swamp = sum([1 if x.item.name == mire_smallkey_name else 0 for x in key_layout.item_locations if x.item is not None and x != swamp_entrance]) + if mire_keys_in_swamp == 0: + keys_outside +=1 + # Mire keylogic + if smallkey_name.endswith('(Tower of Hera)'): + # TODO: Make sure that mire medallion isn't in hera basement, or if it is, the small key is available downstairs + big_key_outside = True for code, counter in key_layout.key_counters.items(): if len(counter.child_doors) == 0: continue diff --git a/Main.py b/Main.py index cd6c80f0..efc97d89 100644 --- a/Main.py +++ b/Main.py @@ -28,6 +28,7 @@ from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dunge from Fill import dungeon_tracking from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, fill_specific_items, create_farm_locations +from UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors from Utils import output_path, parse_player_names from source.item.District import init_districts @@ -78,6 +79,11 @@ def main(args, seed=None, fish=None): seeded = True world.customizer.adjust_args(args) world = init_world(args, fish) + + for i in zip(args.logic.values(), args.door_shuffle.values()): + if i[0] == 'hybridglitches' and i[1] != 'vanilla': + raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) + if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -166,6 +172,8 @@ def main(args, seed=None, fish=None): create_dungeons(world, player) adjust_locations(world, player) place_bosses(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(world, player) if any(world.potshuffle.values()): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) @@ -191,6 +199,8 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_entrances_new(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(world, player) logger.info(world.fish.translate("cli", "cli", "shuffling.prep")) @@ -594,8 +604,10 @@ def copy_world(world): for player in range(1, world.players + 1): create_regions(ret, player) update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) @@ -703,6 +715,8 @@ def copy_world(world): for player in range(1, world.players + 1): categorize_world_regions(ret, player) create_farm_locations(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) set_rules(ret, player) return ret @@ -782,8 +796,10 @@ def copy_world_premature(world, player): create_regions(ret, player) update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) @@ -844,6 +860,9 @@ def copy_world_premature(world, player): for portal in world.dungeon_portals[player]: connect_portal(portal, ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) + set_rules(ret, player) return ret diff --git a/OWEdges.py b/OWEdges.py index 90786d6f..ef9c9182 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -1558,23 +1558,39 @@ OWExitTypes = { 'Ice Lake Southeast Water Drop', 'Bomber Corner Waterfall Water Drop' ], - 'OWTerrain': ['Lost Woods Bush (West)', - 'Lost Woods Bush (East)', - 'Old Man Drop Off', + 'OpenTerrain': ['Old Man Drop Off', 'Spectacle Rock Approach', 'Spectacle Rock Leave', - 'DM Hammer Bridge (West)', - 'DM Hammer Bridge (East)', 'Floating Island Bridge (East)', - 'Fairy Ascension Rocks (Inner)', - 'DM Broken Bridge (West)', - 'DM Broken Bridge (East)', 'Spiral Mimic Bridge (West)', 'Spiral Mimic Bridge (East)', 'Spiral Ledge Approach', 'Mimic Ledge Approach', - 'Fairy Ascension Rocks (Outer)', 'Floating Island Bridge (West)', + 'Graveyard Ladder (Bottom)', + 'Graveyard Ladder (Top)', + 'Hyrule Castle Main Gate (South)', + 'Hyrule Castle Main Gate (North)', + 'Stone Bridge (Northbound)', + 'Stone Bridge (Southbound)', + 'Checkerboard Ledge Approach', + 'Checkerboard Ledge Leave', + 'Cave 45 Approach', + 'Cave 45 Leave', + 'Middle Aged Man', + 'Desert Pass Ladder (South)', + 'Desert Pass Ladder (North)', + 'GT Approach', + 'GT Leave', + ], + 'OWTerrain': ['Lost Woods Bush (West)', + 'Lost Woods Bush (East)', + 'DM Hammer Bridge (West)', + 'DM Hammer Bridge (East)', + 'Fairy Ascension Rocks (Inner)', + 'DM Broken Bridge (West)', + 'DM Broken Bridge (East)', + 'Fairy Ascension Rocks (Outer)', 'TR Pegs Ledge Entry', 'TR Pegs Ledge Leave', 'Mountain Pass Rock (Outer)', @@ -1587,8 +1603,6 @@ OWExitTypes = { 'Lost Woods Pass Rock (North)', 'Lost Woods Pass Rock (South)', 'Kings Grave Rocks (Outer)', - 'Graveyard Ladder (Bottom)', - 'Graveyard Ladder (Top)', 'Kings Grave Rocks (Inner)', 'River Bend Water Drop', 'River Bend West Pier', @@ -1602,12 +1616,10 @@ OWExitTypes = { 'Kakariko Yard Bush (South)', 'Kakariko Southwest Bush (South)', 'Kakariko Yard Bush (North)', - 'Hyrule Castle Main Gate (South)', 'Hyrule Castle East Rock (Inner)', 'Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Courtyard Bush (South)', - 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle East Rock (Outer)', 'Wooden Bridge Bush (South)', @@ -1615,17 +1627,11 @@ OWExitTypes = { 'Blacksmith Ledge Peg (West)', 'Blacksmith Ledge Peg (East)', 'Maze Race Game', - 'Stone Bridge (Northbound)', - 'Stone Bridge (Southbound)', 'Desert Statue Move', - 'Checkerboard Ledge Approach', 'Desert Ledge Rocks (Outer)', 'Desert Ledge Rocks (Inner)', - 'Checkerboard Ledge Leave', 'Flute Boy Bush (South)', - 'Cave 45 Approach', 'Flute Boy Bush (North)', - 'Cave 45 Leave', 'C Whirlpool Rock (Bottom)', 'C Whirlpool Rock (Top)', 'C Whirlpool Pegs (Outer)', @@ -1644,19 +1650,14 @@ OWExitTypes = { 'Lake Hylia Water D Approach', 'Lake Hylia Water D Leave', 'Ice Cave Pier', - 'Desert Pass Ladder (South)', 'Desert Pass Rocks (North)', 'Desert Pass Rocks (South)', - 'Desert Pass Ladder (North)', - 'Middle Aged Man', 'Octoballoon Water Drop', 'Octoballoon Pier', 'Skull Woods Rock (East)', 'Skull Woods Rock (West)', 'Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Bush (East)', - 'GT Approach', - 'GT Leave', 'East Dark Death Mountain Bushes', 'Bumper Cave Rock (Outer)', 'Bumper Cave Rock (Inner)', diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 0e05246d..4f6c2454 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -5,102 +5,86 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. from BaseClasses import Entrance from OWEdges import OWTileRegions +# Cave regions that superbunny can get through - but only with a sword. +sword_required_superbunny_mirror_regions = ["Spiral Cave (Top)"] -def get_sword_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with a sword. - """ - yield 'Spiral Cave (Top)' +# Cave regions that superbunny can get through - but only with boots. +boots_required_superbunny_mirror_regions = ["Two Brothers House"] -def get_boots_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with boots. - """ - yield 'Two Brothers House' +# Cave locations that superbunny can access - but only with boots. +boots_required_superbunny_mirror_locations = [ + "Sahasrahla's Hut - Left", + "Sahasrahla's Hut - Middle", + "Sahasrahla's Hut - Right", +] -def get_boots_required_superbunny_mirror_locations(): - """ - Cave locations that superbunny can access - but only with boots. - """ - yield 'Sahasrahla\'s Hut - Left' - yield 'Sahasrahla\'s Hut - Middle' - yield 'Sahasrahla\'s Hut - Right' +# Entrances that can't be superbunny-mirrored into. +invalid_mirror_bunny_entrances = [ + "Hype Cave", + "Bonk Fairy (Dark)", + "Thieves Town", + "Hammer Peg Cave", + "Brewery", + "Hookshot Cave", + "Dark Lake Hylia Ledge Fairy", + "Dark Lake Hylia Ledge Spike Cave", + "Palace of Darkness", + "Misery Mire", + "Turtle Rock", + "Bonk Rock Cave", + "Bonk Fairy (Light)", + "50 Rupee Cave", + "20 Rupee Cave", + "Checkerboard Cave", + "Light Hype Fairy", + "Waterfall of Wishing", + "Light World Bomb Hut", + "Mini Moldorm Cave", + "Ice Rod Cave", + "Sanctuary Grave", + "Kings Grave", + "Sanctuary Grave", + "Hyrule Castle Secret Entrance Drop", + "Skull Woods Second Section Hole", + "Skull Woods First Section Hole (North)", +] + +# Interior locations that can be accessed with superbunny state. +superbunny_accessible_locations = [ + "Waterfall of Wishing - Left", + "Waterfall of Wishing - Right", + "King's Tomb", + "Floodgate", + "Floodgate Chest", + "Cave 45", + "Bonk Rock Cave", + "Brewery", + "C-Shaped House", + "Chest Game", + "Mire Shed - Left", + "Mire Shed - Right", + "Secret Passage", + "Ice Rod Cave", + "Pyramid Fairy - Left", + "Pyramid Fairy - Right", + "Superbunny Cave - Top", + "Superbunny Cave - Bottom", + "Blind's Hideout - Left", + "Blind's Hideout - Right", + "Blind's Hideout - Far Left", + "Blind's Hideout - Far Right", + "Kakariko Well - Left", + "Kakariko Well - Middle", + "Kakariko Well - Right", + "Kakariko Well - Bottom", + "Kakariko Tavern", + "Library", + "Spiral Cave", +] + boots_required_superbunny_mirror_locations # TODO: Add pottery locations -def get_invalid_mirror_bunny_entrances(): - """ - Entrances that can't be superbunny-mirrored into. - """ - yield 'Skull Woods Final Section' - yield 'Hype Cave' - yield 'Bonk Fairy (Dark)' - yield 'Thieves Town' - yield 'Hammer Peg Cave' - yield 'Brewery' - yield 'Hookshot Cave' - yield 'Dark Lake Hylia Ledge Fairy' - yield 'Dark Lake Hylia Ledge Spike Cave' - yield 'Palace of Darkness' - yield 'Misery Mire' - yield 'Turtle Rock' - yield 'Bonk Rock Cave' - yield 'Bonk Fairy (Light)' - yield '50 Rupee Cave' - yield '20 Rupee Cave' - yield 'Checkerboard Cave' - yield 'Light Hype Fairy' - yield 'Waterfall of Wishing' - yield 'Light World Bomb Hut' - yield 'Mini Moldorm Cave' - yield 'Ice Rod Cave' - yield 'Sanctuary Grave' - yield 'Kings Grave' - yield 'Sanctuary Grave' - yield 'Hyrule Castle Secret Entrance Drop' - yield 'Skull Woods Second Section Hole' - yield 'Skull Woods First Section Hole (North)' - - -def get_superbunny_accessible_locations(): - """ - Interior locations that can be accessed with superbunny state. - """ - - yield 'Waterfall of Wishing - Left' - yield 'Waterfall of Wishing - Right' - yield 'King\'s Tomb' - yield 'Floodgate' - yield 'Floodgate Chest' - yield 'Cave 45' - yield 'Bonk Rock Cave' - yield 'Brewery' - yield 'C-Shaped House' - yield 'Chest Game' - yield 'Mire Shed - Left' - yield 'Mire Shed - Right' - yield 'Secret Passage' - yield 'Ice Rod Cave' - yield 'Pyramid Fairy - Left' - yield 'Pyramid Fairy - Right' - yield 'Superbunny Cave - Top' - yield 'Superbunny Cave - Bottom' - yield 'Blind\'s Hideout - Left' - yield 'Blind\'s Hideout - Right' - yield 'Blind\'s Hideout - Far Left' - yield 'Blind\'s Hideout - Far Right' - yield 'Kakariko Well - Left' - yield 'Kakariko Well - Middle' - yield 'Kakariko Well - Right' - yield 'Kakariko Well - Bottom' - yield 'Kakariko Tavern' - yield 'Library' - yield 'Spiral Cave' - for location in get_boots_required_superbunny_mirror_locations(): - yield location - - def get_non_mandatory_exits(world, player): """ Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit. @@ -295,8 +279,9 @@ def overworld_glitches_rules(world, player): #set_owg_rules(player, world, get_glitched_speed_drops_dw(world, player), lambda state: state.can_get_glitched_speed_dw(player)) # Mirror clip spots. + # TODO: Should this also require can_boots_clip set_owg_rules(player, world, get_mirror_clip_spots(world, player), lambda state: state.has_Mirror(player)) - + # Mirror offset spots. for data in get_mirror_offset_spots(world, player): set_owg_rules(player, world, [data[0:3]], lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player) and state.can_reach(data[3], None, player)) @@ -319,6 +304,18 @@ def overworld_glitches_rules(world, player): add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) add_additional_rule(world.get_entrance('Dark Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) + # Bunny pocket + if not world.is_tile_swapped(0x00, player): + add_alternate_rule(world.get_entrance("Skull Woods Final Section", player), lambda state: state.can_bunny_pocket(player) and state.has("Fire Rod", player)) + if world.is_tile_swapped(0x05, player): + add_alternate_rule(world.get_entrance("DM Hammer Bridge (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + add_alternate_rule(world.get_entrance("DM Hammer Bridge (East)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + if not world.is_tile_swapped(0x18, player): + add_alternate_rule(world.get_entrance("Bush Yard Pegs (Inner)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + add_alternate_rule(world.get_entrance("Bush Yard Pegs (Outer)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + if world.is_tile_swapped(0x22, player): + add_alternate_rule(world.get_entrance("Blacksmith Ledge Peg (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + def add_alternate_rule(entrance, rule): old_rule = entrance.access_rule @@ -335,7 +332,7 @@ def create_no_logic_connections(player, world, connections): parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) connection = Entrance(player, entrance, parent) - connection.spot_type = 'Ledge' + connection.spot_type = 'OWG' parent.exits.append(connection) connection.connect(target) @@ -387,6 +384,8 @@ boots_clips_local = [ # (name, from_region, to_region) ('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize'), + #('Hobo Water Clip', 'Stone Bridge South Area', 'Stone Bridge Water'), # TODO: Doesn't work with OW Free Terrain + ('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water'), #requires flippers ('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water'), #requires flippers diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a7380f47..c3d080c8 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -200,7 +200,7 @@ def link_overworld(world, player): categorize_world_regions(world, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(world, player) # crossed shuffle @@ -1486,7 +1486,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F elif exit.connected_region.name not in explored_regions \ and (exit.connected_region.type == region.type or exit.name in OWExitTypes['OWEdge'] or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \ - and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge']): + and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge', 'OWG']): explore_region(exit.connected_region.name, exit.connected_region) if build_copy_world: diff --git a/Rom.py b/Rom.py index 85a7dd67..2a5c1c38 100644 --- a/Rom.py +++ b/Rom.py @@ -1365,9 +1365,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180211, gametype) # Game type warningflags = 0x00 # none - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: warningflags |= 0x20 - if world.logic[player] in ['minorglitches', 'owglitches', 'nologic']: + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic']: warningflags |= 0x40 rom.write_byte(0x180212, warningflags) # Warning flags @@ -1599,7 +1599,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill - rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'nologic']) else 0x00) + rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']) else 0x00) # remove shield from uncle rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) diff --git a/Rules.py b/Rules.py index 6600d819..ca69aa3b 100644 --- a/Rules.py +++ b/Rules.py @@ -9,6 +9,7 @@ from Dungeons import dungeon_table from RoomData import DoorKind from OWEdges import OWExitTypes from OverworldGlitchRules import overworld_glitches_rules +from UnderworldGlitchRules import underworld_glitches_rules def set_rules(world, player): @@ -32,7 +33,7 @@ def set_rules(world, player): logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') no_glitches_rules(world, player) fake_flipper_rules(world, player) - elif world.logic[player] == 'owglitches': + elif world.logic[player] in ['owglitches', 'hybridglitches']: logging.getLogger('').info('There is a chance OWG has bugged edge case rulesets, especially in inverted. Definitely file a report on GitHub if you see anything strange.') # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. @@ -84,6 +85,9 @@ def set_rules(world, player): if not world.is_copied_world: set_bunny_rules(world, player, world.mode[player] == 'inverted') + # These rules go here because they overwrite/add to some of the above rules + if world.logic[player] == 'hybridglitches': + underworld_glitches_rules(world, player) def mirrorless_path_to_location(world, startName, targetName, player): # If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch. @@ -845,6 +849,9 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Crystal Circles to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_blunt_weapon(player) or state.has('Cane of Byrna', player)) # or state.has_beam_sword(player) add_key_logic_rules(world, player) + + if world.logic[player] == 'hybridglitches': + add_hmg_key_logic_rules(world, player) # End of door rando rules. if world.restrict_boss_items[player] != 'none': @@ -1644,6 +1651,8 @@ def find_rules_for_zelda_delivery(world, player): def set_bunny_rules(world, player, inverted): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. + all_single_exit_dungeons = ['Eastern Palace', 'Tower of Hera', 'Castle Tower', 'Palace of Darkness', 'Swamp Palace', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Ganons Tower'] + hmg_single_exit_dungeons = [d for d in all_single_exit_dungeons if d not in ['Tower of Hera', 'Misery Mire', 'Thieves Town']] bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', @@ -1668,6 +1677,9 @@ def set_bunny_rules(world, player, inverted): 'Take - Any # 3 Item 1', 'Take - Any # 3 Item 2', 'Take - Any # 4 Item 1', 'Take - Any # 4 Item 2', ] + bunny_pocket_entrances = ['Skull Woods Final Section', 'Bush Yard Pegs (Inner)', 'Bush Yard Pegs (Outer)', + 'DM Hammer Bridge (West)', 'DM Hammer Bridge (East)', 'Blacksmith Ledge Peg (West)' + ] def path_to_access_rule(path, entrance): return lambda state: state.can_reach(entrance) and all(rule_func(state) for rule_func in path) @@ -1687,13 +1699,32 @@ def set_bunny_rules(world, player, inverted): return region.is_dark_world else: return region.is_light_world + + # Is it possible to do bunny pocket here + def can_bunny_pocket_to(world, entrance_name, player): + def can_activate_bunny_pocket(region): + explored_regions.append(region.name) + for ent in region.entrances: + if (is_link(ent.parent_region) or + (ent.parent_region.type == RegionType.Dungeon and ent.parent_region.name in bunny_revivable_entrances)): + return True + for ent in region.entrances: + if ent.spot_type in ['OWEdge', 'Ledge', 'OpenTerrain'] and ent.parent_region.name not in explored_regions: + if can_activate_bunny_pocket(ent.parent_region): + return True + return False + + entrance = world.get_entrance(entrance_name, player) + explored_regions = [] + return can_activate_bunny_pocket(entrance.parent_region) + def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type != RegionType.Dungeon \ - and (location is None or location.name not in OverworldGlitchRules.get_superbunny_accessible_locations()) \ + and (location is None or location.name not in OverworldGlitchRules.superbunny_accessible_locations) \ and not is_link(region): return lambda state: state.has_Pearl(player) else: @@ -1723,14 +1754,18 @@ def set_bunny_rules(world, player, inverted): new_path = path + [entrance.access_rule] seen_sets.add(frozenset(new_seen)) if not is_link(new_region): - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: + # Is this a bunny pocketable entrance? if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: + continue + if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player): continue if entrance.name in drop_dungeon_entrances: lobby = entrance.connected_region else: - lobby = next(exit.connected_region for exit in current.exits if exit.connected_region.type == RegionType.Dungeon) + portal_regions = [world.get_region(reg, player) for reg in region.dungeon.regions if reg.endswith('Portal')] + lobby = next(reg.connected_region for portal_reg in portal_regions for reg in portal_reg.exits if reg.name.startswith('Enter ')) if lobby.name in bunny_revivable_entrances: possible_options.append(path_to_access_rule(new_path, entrance)) elif lobby.name in superbunny_revivable_entrances: @@ -1739,21 +1774,23 @@ def set_bunny_rules(world, player, inverted): possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) continue elif region.type == RegionType.Cave and new_region.type != RegionType.Cave: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue - if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): + if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player): + continue + if region.name in OverworldGlitchRules.sword_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) - elif region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions(): + elif region.name in OverworldGlitchRules.boots_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) - elif location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): - if location.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_locations(): + elif location and location.name in OverworldGlitchRules.superbunny_accessible_locations: + if location.name in OverworldGlitchRules.boots_required_superbunny_mirror_locations: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) elif region.name == 'Kakariko Well (top)': possible_options.append(path_to_access_rule(new_path, entrance)) else: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player)], entrance)) continue - elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): + elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.superbunny_accessible_locations: possible_options.append(path_to_access_rule(new_path, entrance)) else: continue @@ -1767,7 +1804,6 @@ def set_bunny_rules(world, player, inverted): # Add requirements for bunny-impassible caves if they occur in the light world for region in [world.get_region(name, player) for name in bunny_impassable_caves]: - if not is_bunny(region): continue rule = get_rule_to_add(region) @@ -1802,6 +1838,11 @@ def set_bunny_rules(world, player, inverted): continue add_rule(location, get_rule_to_add(region, location)) + for ent_name in bunny_pocket_entrances: + bunny_exit = world.get_entrance(ent_name, player) + if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region) and not can_bunny_pocket_to(world, ent_name, player): + add_rule(bunny_exit, lambda state: state.has_Pearl(player)) + drop_dungeon_entrances = { "Sewer Drop", @@ -1929,6 +1970,11 @@ bunny_impassible_if_trapped = { 'GT Speed Torch WN', 'Ice Lobby SE' } +def add_hmg_key_logic_rules(world, player): + for toh_loc in world.key_logic[player]['Tower of Hera'].bk_restricted: + set_always_allow(world.get_location(toh_loc.name, player), allow_big_key_in_big_chest('Big Key (Tower of Hera)', player)) + set_always_allow(world.get_location('Swamp Palace - Entrance', player), allow_big_key_in_big_chest('Big Key (Swamp Palace)', player)) + def add_key_logic_rules(world, player): key_logic = world.key_logic[player] diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py new file mode 100644 index 00000000..c01e242f --- /dev/null +++ b/UnderworldGlitchRules.py @@ -0,0 +1,278 @@ +from BaseClasses import Entrance +import Rules +from OverworldGlitchRules import create_no_logic_connections + +kikiskip_spots = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")] + +mireheraswamp_spots = [ + ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal"), + ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal"), +] + +icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop")] + +thievesdesert_spots = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert West Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert South Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert East Portal"), +] + +specrock_spots = [("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)")] + +paradox_spots = [("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area")] + + +# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions +kikiskip_connectors = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit")] + + +mireheraswamp_connectors = [ + ("Mire to Hera Clip", "Mire Torches Top", "Tower of Hera Exit"), + ("Mire to Hera Clip", "Mire Torches Top", "Swamp Palace Exit"), +] + + +thievesdesert_connectors = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (West)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (South)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (East)"), +] + +specrock_connectors = [ + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit (Top)"), + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit"), +] + + +# Create connections between dungeons/locations +def create_hybridmajor_connections(world, player): + for spots in [ + kikiskip_spots, + mireheraswamp_spots, + icepalace_spots, + thievesdesert_spots, + specrock_spots, + paradox_spots, + ]: + create_no_logic_connections(player, world, spots) + + +# Turn dungeons into connectors +def create_hybridmajor_connectors(world, player): + for connectors in [ + kikiskip_connectors, + mireheraswamp_connectors, + thievesdesert_connectors, + specrock_connectors, + ]: + new_connectors = [(connector[0], connector[1], world.get_entrance(connector[2], player).connected_region) for connector in connectors] + create_no_logic_connections(player, world, new_connectors) + + +# For some entrances, we need to fake having pearl, because we're in fake DW/LW. +# This creates a copy of the input state that has Moon Pearl. +def fake_pearl_state(state, player): + if state.has("Moon Pearl", player): + return state + fake_state = state.copy() + fake_state.prog_items["Moon Pearl", player] += 1 + return fake_state + + +# Sets the rules on where we can actually go using this clip. +# Behavior differs based on what type of ER shuffle we're playing. +def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str): + fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] + fix_fake_worlds = world.fix_fake_world[player] + + dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0] + if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix + # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. + + # entrance doesn't exist until you fire rod it from the other side + if dungeon_entrance.name == "Skull Woods Final Section": + Rules.set_rule(clip, lambda state: False) + + elif dungeon_entrance.name == "Misery Mire": + if world.swords[player] == "swordless": + Rules.add_rule(clip, lambda state: state.has_misery_mire_medallion(player)) + else: + Rules.add_rule(clip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + + elif dungeon_entrance.name == "Agahnims Tower": + Rules.add_rule( + clip, + lambda state: state.has("Cape", player) + or state.has_beam_sword(player) + or state.has("Beat Agahnim 1", player), + ) + + # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + elif not fix_fake_worlds: # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix + # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. + Rules.add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) + # exiting restriction + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + + # Otherwise, the shuffle type is lean, lite, crossed, or insanity; all of these do not need additional rules on where we can go, + # since the clip links directly to the exterior region. + + +def underworld_glitches_rules(world, player): + # Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room + Rules.add_rule( + world.get_entrance("Ice Bomb Drop SE", player), + lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) + and (state.can_use_bombs(player) or state.has("Cane of Somaria", player)), + combine="or", + ) + + # Kiki Skip + kks = world.get_entrance("Kiki Skip", player) + Rules.set_rule(kks, lambda state: state.can_bomb_clip(kks.parent_region, player)) + dungeon_reentry_rules(world, player, kks, "Palace of Darkness Portal", "Palace of Darkness Exit") + + # Mire -> Hera -> Swamp + def mire_clip(state): + return state.can_reach("Mire Torches Top", "Region", player) and state.can_dash_clip( + world.get_region("Mire Torches Top", player), player + ) + + def hera_clip(state): + return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip( + world.get_region("Hera 4F", player), player + ) + + Rules.add_rule( + world.get_entrance("Hera Startile Corner NW", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + Rules.add_rule( + world.get_location("Tower of Hera - Big Chest", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + + mire_to_hera = world.get_entrance("Mire to Hera Clip", player) + mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) + Rules.set_rule(mire_to_hera, mire_clip) + Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) + + # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys + dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") + dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") + # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots + # Flippers required for all of these doors to prevent locks when flooding + for door in [ + "Swamp Trench 1 Approach ES", + "Swamp Hammer Switch SW", + "Swamp Entrance Down Stairs", + "Swamp Pot Row WS", + "Swamp Trench 1 Key Ledge NW", + "Swamp Hub WN", + "Swamp Hub North Ledge N", + "Swamp Crystal Switch EN", + "Swamp Push Statue S", + "Swamp Waterway NW", + "Swamp T SW", + ]: + Rules.add_rule( + world.get_entrance(door, player), + lambda state: mire_clip(state) + and state.has("Small Key (Misery Mire)", player, count=6) + and state.has("Flippers", player), + combine="or", + ) + # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") + + Rules.add_rule( + world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or" + ) + + # Build the rule for SP moat. + # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. + # First we require a certain type of entrance shuffle, then build the rule from its pieces. + if not world.swamp_patch_required[player]: + if world.shuffle[player] in [ + "vanilla", + "dungeonssimple", + "dungeonsfull", + "dungeonscrossed", + ]: + rule_map = { + "Mire Portal": (lambda state: state.can_reach("Mire Torches Top", "Entrance", player)), + "Hera Portal": (lambda state: state.can_reach("Hera Startile Corner NW", "Entrance", player)), + } + inverted = world.mode[player] == "inverted" + + def hera_rule(state): + return (state.has("Moon Pearl", player) or not inverted) and rule_map.get( + world.get_entrance("Tower of Hera", player).connected_region.name, lambda state: False + )(state) + + def gt_rule(state): + return (state.has("Moon Pearl", player) or inverted) and rule_map.get( + world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False + )(state) + + def mirrorless_moat_rule(state): + return ( + state.can_reach("Old Man S&Q", "Entrance", player) + and state.has("Flippers", player) + and mire_clip(state) + and (hera_rule(state) or gt_rule(state)) + ) + + Rules.add_rule( + world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or" + ) + + # Thieves -> Desert + Rules.add_rule( + world.get_entrance("Thieves to Desert Clip", player), + lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player), + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert West Portal", + "Desert Palace Exit (West)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert South Portal", + "Desert Palace Exit (South)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert East Portal", + "Desert Palace Exit (East)", + ) + + # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up + paradox_left_chests = ["Paradox Cave Lower - Far Left", "Paradox Cave Lower - Left", "Paradox Cave Lower - Middle"] + for location in paradox_left_chests: + Rules.add_rule( + world.get_location(location, player), + lambda state: state.can_dash_clip(world.get_location(location, player).parent_region, player), + "or", + ) + + # Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch + paradox_right_chests = ["Paradox Cave Lower - Right", "Paradox Cave Lower - Far Right"] + for location in paradox_right_chests: + Rules.add_rule( + world.get_location(location, player), + lambda state: ( + state.can_dash_clip(world.get_location(location, player).parent_region, player) + and state.can_hit_crystal(player) + ), + "or", + ) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 276b581c..09e897f7 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -23,6 +23,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 87c72e3d..3ad7f887 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -56,7 +56,8 @@ "building.collection.spheres": "Building up collection spheres", "building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.", "building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.", - "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s" + "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s", + "hybridglitches.door.shuffle": "Hybrid Major Glitches is not currently compatible with Door Shuffle." }, "help": { "lang": [ "App Language, if available, defaults to English" ], @@ -64,11 +65,13 @@ "bps": [ "Output BPS patches instead of ROMs"], "logic": [ "Select Enforcement of Item Requirements. (default: %(default)s)", - "No Glitches: No Glitch knowledge required.", - "Minor Glitches: May require Fake Flippers, Bunny Revival", - " and Dark Room Navigation.", - "No Logic: Distribute items without regard for", - " item requirements." + "No Glitches: No Glitch knowledge required.", + "Minor Glitches: May require Fake Flippers, Bunny Revival", + " and Dark Room Navigation.", + "Overworld Glitches: May require overworld clips and teleports.", + "Hybrid Major Glitches: May require underworld clips.", + "No Logic: Distribute items without regard for", + " item requirements." ], "mode": [ "Select game mode. (default: %(default)s)", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 9b4a0690..e23c02cd 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -263,6 +263,7 @@ "randomizer.item.logiclevel.noglitches": "No Glitches", "randomizer.item.logiclevel.minorglitches": "Minor Glitches", "randomizer.item.logiclevel.owglitches": "Overworld Glitches", + "randomizer.item.logiclevel.hybridglitches": "Hybrid Major Glitches", "randomizer.item.logiclevel.nologic": "No Logic", "randomizer.item.goal": "Goal", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 652554ab..0c65b694 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -21,6 +21,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index c909107b..95a3e5dd 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1317,7 +1317,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if avail.world.logic[avail.player] in ['owglitches', 'nologic']: + if avail.world.logic[avail.player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(avail.world, avail.player): invalid_connections[entrance] = set() diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 2b8ba457..ca80dff7 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -46,8 +46,9 @@ def roll_settings(weights): ret.algorithm = get_choice('algorithm') - glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches', - 'owg': 'owglitches', 'minorglitches': 'minorglitches'} + glitch_map = {'none': 'noglitches', 'minorglitches': 'minorglitches', 'no_logic': 'nologic', + 'hmg': 'hybridglitches', 'hybridglitches': 'hybridglitches', + 'owg': 'owglitches', 'owglitches': 'owglitches'} glitches_required = get_choice('glitches_required') if glitches_required is not None: if glitches_required not in glitch_map.keys(): diff --git a/test/customizer/hmg/fireless_ice.yaml b/test/customizer/hmg/fireless_ice.yaml new file mode 100644 index 00000000..86affd94 --- /dev/null +++ b/test/customizer/hmg/fireless_ice.yaml @@ -0,0 +1,35 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Ice Palace - Compass Chest: Fire Rod + Ice Palace - Freezor Chest: Bombos diff --git a/test/customizer/hmg/hammer_in_swamp.yaml b/test/customizer/hmg/hammer_in_swamp.yaml new file mode 100644 index 00000000..f2e3aae7 --- /dev/null +++ b/test/customizer/hmg/hammer_in_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Hammer diff --git a/test/customizer/hmg/mearl_in_pod.yaml b/test/customizer/hmg/mearl_in_pod.yaml new file mode 100644 index 00000000..ec2d5b9a --- /dev/null +++ b/test/customizer/hmg/mearl_in_pod.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Palace of Darkness - Shooter Room: Moon Pearl diff --git a/test/customizer/hmg/mirrorless_swamp.yaml b/test/customizer/hmg/mirrorless_swamp.yaml new file mode 100644 index 00000000..c8a109c3 --- /dev/null +++ b/test/customizer/hmg/mirrorless_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Magic Mirror diff --git a/test/customizer/hmg/pod_as_connector.yaml b/test/customizer/hmg/pod_as_connector.yaml new file mode 100644 index 00000000..48f72d6e --- /dev/null +++ b/test/customizer/hmg/pod_as_connector.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Palace of Darkness Exit + Lake Hylia Fortune Teller: Spectacle Rock Cave Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl diff --git a/test/customizer/hmg/swamp_as_connector.yaml b/test/customizer/hmg/swamp_as_connector.yaml new file mode 100644 index 00000000..ae343937 --- /dev/null +++ b/test/customizer/hmg/swamp_as_connector.yaml @@ -0,0 +1,55 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Swamp Palace Exit + Lake Hylia Fortune Teller: Misery Mire Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl + diff --git a/test/customizer/hmg/swamp_small_in_swamp_back.yaml b/test/customizer/hmg/swamp_small_in_swamp_back.yaml new file mode 100644 index 00000000..b57cc9b6 --- /dev/null +++ b/test/customizer/hmg/swamp_small_in_swamp_back.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +# placements: +# 1: +# Swamp Palace - Entrance: Boss Heart Container diff --git a/test/suite/hmg/entrance_bunny_pocket_sw.yaml b/test/suite/hmg/entrance_bunny_pocket_sw.yaml new file mode 100644 index 00000000..eba7743d --- /dev/null +++ b/test/suite/hmg/entrance_bunny_pocket_sw.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches + shuffle: crossed +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Pyramid Fairy - Left: True +entrances: + 1: + entrances: + Skull Woods Final Section: Pyramid Fairy + two-way: + Chicken House: Two Brothers House Exit (West) + Skull Woods Second Section Door (West): Two Brothers House Exit (East) + diff --git a/test/suite/hmg/fireless_ice.yaml b/test/suite/hmg/fireless_ice.yaml new file mode 100644 index 00000000..79b31f59 --- /dev/null +++ b/test/suite/hmg/fireless_ice.yaml @@ -0,0 +1,17 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove +placements: + 1: + Ice Palace - Map Chest: Bombos + Ice Palace - Iced T Room: Fire Rod diff --git a/test/suite/hmg/flippers_locked_flippers.yaml b/test/suite/hmg/flippers_locked_flippers.yaml new file mode 100644 index 00000000..be71e07b --- /dev/null +++ b/test/suite/hmg/flippers_locked_flippers.yaml @@ -0,0 +1,20 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword +advanced_placements: + 1: + - type: Verification + item: Flippers + locations: + Zora's Ledge: True + Hobo: True + Ice Palace - Boss: True + Swamp Palace - Entrance: False + diff --git a/test/suite/hmg/flippers_wraps.yaml b/test/suite/hmg/flippers_wraps.yaml new file mode 100644 index 00000000..3418b4ec --- /dev/null +++ b/test/suite/hmg/flippers_wraps.yaml @@ -0,0 +1,29 @@ +meta: + players: 1 +settings: + 1: + logic: owglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword + - Flippers +placements: + 1: + Peg Cave: Magic Mirror +advanced_placements: + 1: + - type: Verification + item: Hammer + locations: + Link's House: True + Magic Bat: False +advanced_placements: + 1: + - type: Verification + item: Progressive Glove + locations: + Link's House: True + Ice Palace - Freezor Chest: False + diff --git a/test/suite/hmg/hera_from_mire.yaml b/test/suite/hmg/hera_from_mire.yaml new file mode 100644 index 00000000..3e7d0c49 --- /dev/null +++ b/test/suite/hmg/hera_from_mire.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Big Key (Tower of Hera) + locations: + Tower of Hera - Big Key Chest: True + Tower of Hera - Basement Cage: True + Tower of Hera - Map Chest: True + Tower of Hera - Compass Chest: True + Tower of Hera - Big Chest: True + Tower of Hera - Boss: True \ No newline at end of file diff --git a/test/suite/hmg/inverted_inaccessible_desert.yaml b/test/suite/hmg/inverted_inaccessible_desert.yaml new file mode 100644 index 00000000..bb8ad8aa --- /dev/null +++ b/test/suite/hmg/inverted_inaccessible_desert.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted + shuffle: crossed + +start_inventory: + 1: + - Pegasus Boots + - Progressive Sword + - Hammer + - Fire Rod +placements: + 1: + Desert Palace - Boss: Moon Pearl +entrances: + 1: + two-way: + Skull Woods Final Section: Desert Palace Exit (West) + Skull Woods Second Section Door (West): Desert Palace Exit (East) + Thieves Town: Thieves Town Exit + Hyrule Castle Entrance (East): Desert Palace Exit (South) + Hyrule Castle Entrance (West): Desert Palace Exit (North) + diff --git a/test/suite/hmg/inverted_moon_pearl_locs.yaml b/test/suite/hmg/inverted_moon_pearl_locs.yaml new file mode 100644 index 00000000..9ae1a9e1 --- /dev/null +++ b/test/suite/hmg/inverted_moon_pearl_locs.yaml @@ -0,0 +1,31 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted +start_inventory: + 1: + - Flippers + - Progressive Sword + - Progressive Sword + - Book of Mudora + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Tower of Hera - Big Chest: True + Desert Palace - Big Chest: True + Eastern Palace - Big Chest: True + Bombos Tablet: True + Cave 45: True + + + + diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml new file mode 100644 index 00000000..a18d53d1 --- /dev/null +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -0,0 +1,48 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Flippers + - Fire Rod + - Book of Mudora + - Progressive Sword + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Compass Chest: True + Skull Woods - Bridge Room: True + Palace of Darkness - Shooter Room: True + Palace of Darkness - The Arena - Bridge: True + Palace of Darkness - Stalfos Basement: True + Palace of Darkness - Big Key Chest: True + Palace of Darkness - The Arena - Ledge: True + Palace of Darkness - Map Chest: True + Palace of Darkness - Compass Chest: True + Palace of Darkness - Dark Basement - Left: True + Palace of Darkness - Dark Basement - Right: True + Palace of Darkness - Dark Maze - Top: True + Palace of Darkness - Dark Maze - Bottom: True + Palace of Darkness - Big Chest: True + Palace of Darkness - Harmless Hellway: True + Palace of Darkness - Boss: True + Bombos Tablet: True + C-Shaped House: True + Pyramid Fairy - Left: True + Swamp Palace - Entrance: False + Thieves' Town - Map Chest: False + + + + diff --git a/test/suite/hmg/pearlless_sw.yaml b/test/suite/hmg/pearlless_sw.yaml new file mode 100644 index 00000000..c26ce1d9 --- /dev/null +++ b/test/suite/hmg/pearlless_sw.yaml @@ -0,0 +1,25 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Bridge Room: True diff --git a/test/suite/hmg/swamp_from_mire.yaml b/test/suite/hmg/swamp_from_mire.yaml new file mode 100644 index 00000000..f3873909 --- /dev/null +++ b/test/suite/hmg/swamp_from_mire.yaml @@ -0,0 +1,43 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Small Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True + - type: Verification + item: Big Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True \ No newline at end of file From e246684f3747798b95084b2e403cb002bbf89763 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 5 Jan 2024 13:09:03 -0600 Subject: [PATCH 101/123] Version bump 0.3.4.0 --- CHANGELOG.md | 18 ++++++++++++++++++ OverworldShuffle.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a5a53d..98cb8905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.3.4.0 +- \~Merged in some things from DR v1.4.0.0-v~ + - Improved bunny-walking algorithm + - Improved multiworld balancing +- Implemented Hyrid Major Glitches logic (thanks Muffins/Espeon) +- Added sparkles to Bonk Drop locations for better visibility +- Some tweaks/improvements to Shuffle Song Instruments +- Replaced Save Settings on Exit with Settings on Load +- Added new button in GUI to export a Yaml file based on current settings +- Allow starting Aga-Defeated and Old-Man-Rescued in inventory + +## 0.3.3.2 +- \~Merged in DR v1.2.0.22~ +- Added Shuffle Song Instruments as post-gen option +- Allow user to change and save output directory within the GUI +- Fixed issue with Smith not deleting on S+Q when no path is possible +- Fixed various MSU inaccuracies + ## 0.3.3.1 - \~Merged in DR v1.2.0.21~ - Fixed issue with Old Man death spawning on Pyramid/Castle diff --git a/OverworldShuffle.py b/OverworldShuffle.py index c3d080c8..311b2342 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.3.2' +version_number = '0.3.4.0' # branch indicator is intentionally different across branches version_branch = '-u' From dab1b7a3794d1a64cc7017a6ed0842cd90ab41c6 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 4 Jan 2024 12:56:34 -0700 Subject: [PATCH 102/123] feat: More strict requirment for some bosses on ice --- Bosses.py | 109 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/Bosses.py b/Bosses.py index 7052bb5c..b0ddac3c 100644 --- a/Bosses.py +++ b/Bosses.py @@ -4,11 +4,12 @@ import RaceRandom as random from BaseClasses import Boss, FillError -def BossFactory(boss, player): +def BossFactory(boss, player, on_ice=False): if boss is None: return None if boss in boss_table: - enemizer_name, defeat_rule = boss_table[boss] + enemizer_name, normal_defeat_rule, ice_defeat_rule = boss_table[boss] + defeat_rule = ice_defeat_rule if on_ice else normal_defeat_rule return Boss(boss, enemizer_name, defeat_rule, player) logging.getLogger('').error('Unknown Boss: %s', boss) @@ -41,16 +42,21 @@ def MoldormDefeatRule(state, player): def HelmasaurKingDefeatRule(state, player): return (state.has('Hammer', player) or state.can_use_bombs(player)) and (state.has_sword(player) or state.can_shoot_arrows(player)) + +def IceHelmasaurKingDefeatRule(state, player): + return state.can_use_bombs(player) and (state.has_sword(player) or state.can_shoot_arrows(player)) + + def ArrghusDefeatRule(state, player): if not state.has('Hookshot', player): return False - # TODO: ideally we would have a check for bow and silvers, which combined with the - # hookshot is enough. This is not coded yet because the silvers that only work in pyramid feature - # makes this complicated if state.has_blunt_weapon(player): return True - return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot + if state.can_shoot_arrows(player) and state.has('Silver Arrows', player) and state.world.difficulty_adjustments[player] not in ['hard', 'expert']: + return True + + return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or # assuming mostly getting two puffs with one shot (state.has('Ice Rod', player) and state.can_use_bombs(player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) @@ -64,9 +70,27 @@ def MothulaDefeatRule(state, player): (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) ) + def BlindDefeatRule(state, player): return state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) + +def IceBlindDefeatRule(state, player): + return ( + ( + # weapon + state.has_beam_sword(player) or + state.has('Cane of Somaria', player) or + (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) + ) and + ( + # protection + state.has('Red Shield', player) or + (state.has('Cane of Byrna', player) and state.world.difficulty_adjustments[player] not in ['hard', 'expert']) + ) + ) + + def KholdstareDefeatRule(state, player): return ( ( @@ -90,9 +114,39 @@ def KholdstareDefeatRule(state, player): ) ) + +def IceKholdstareDefeatRule(state, player): + return ( + ( + state.has('Fire Rod', player) or + ( + state.has('Bombos', player) and + # FIXME: the following only actually works for the vanilla location for swordless + (state.has_sword(player) or state.world.swords[player] == 'swordless') + ) + ) and + ( + state.has_beam_sword(player) or + (state.has('Fire Rod', player) and state.can_extend_magic(player, 20)) or + # FIXME: this actually only works for the vanilla location for swordless + ( + state.has('Fire Rod', player) and + state.has('Bombos', player) and + (state.has_sword(player) or state.world.swords[player] == 'swordless') and + state.can_extend_magic(player, 16) + ) + ) + ) + + def VitreousDefeatRule(state, player): return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_blunt_weapon(player) + +def IceVitreousDefeatRule(state, player): + return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_beam_sword(player) + + def TrinexxDefeatRule(state, player): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): return False @@ -102,24 +156,36 @@ def TrinexxDefeatRule(state, player): (state.has('Master Sword', player) and state.can_extend_magic(player, 16)) or (state.has_sword(player) and state.can_extend_magic(player, 32))) + +def IceTrinexxDefeatRule(state, player): + if not (state.has('Fire Rod', player) and state.has('Ice Rod', player) and state.has_Boots(player)): + return False + return (state.has('Golden Sword', player) or + (state.has('Tempered Sword', player) and state.can_extend_magic(player, 16)) or + ((state.has('Hammer', player) or + state.has('Master Sword', player)) and state.can_extend_magic(player, 32))) # rod spam rule + + def AgahnimDefeatRule(state, player): return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player) + boss_table = { - 'Armos Knights': ('Armos', ArmosKnightsDefeatRule), - 'Lanmolas': ('Lanmola', LanmolasDefeatRule), - 'Moldorm': ('Moldorm', MoldormDefeatRule), - 'Helmasaur King': ('Helmasaur', HelmasaurKingDefeatRule), - 'Arrghus': ('Arrghus', ArrghusDefeatRule), - 'Mothula': ('Mothula', MothulaDefeatRule), - 'Blind': ('Blind', BlindDefeatRule), - 'Kholdstare': ('Kholdstare', KholdstareDefeatRule), - 'Vitreous': ('Vitreous', VitreousDefeatRule), - 'Trinexx': ('Trinexx', TrinexxDefeatRule), - 'Agahnim': ('Agahnim', AgahnimDefeatRule), - 'Agahnim2': ('Agahnim2', AgahnimDefeatRule) + 'Armos Knights': ('Armos', ArmosKnightsDefeatRule, ArmosKnightsDefeatRule), + 'Lanmolas': ('Lanmola', LanmolasDefeatRule, LanmolasDefeatRule), + 'Moldorm': ('Moldorm', MoldormDefeatRule, MoldormDefeatRule), + 'Helmasaur King': ('Helmasaur', HelmasaurKingDefeatRule, IceHelmasaurKingDefeatRule), + 'Arrghus': ('Arrghus', ArrghusDefeatRule, ArrghusDefeatRule), + 'Mothula': ('Mothula', MothulaDefeatRule, MothulaDefeatRule), + 'Blind': ('Blind', BlindDefeatRule, IceBlindDefeatRule), + 'Kholdstare': ('Kholdstare', KholdstareDefeatRule, IceKholdstareDefeatRule), + 'Vitreous': ('Vitreous', VitreousDefeatRule, IceVitreousDefeatRule), + 'Trinexx': ('Trinexx', TrinexxDefeatRule, IceTrinexxDefeatRule), + 'Agahnim': ('Agahnim', AgahnimDefeatRule, AgahnimDefeatRule), + 'Agahnim2': ('Agahnim2', AgahnimDefeatRule, AgahnimDefeatRule) } + def can_place_boss(world, player, boss, dungeon_name, level=None): if world.swords[player] in ['swordless'] and boss == 'Kholdstare' and dungeon_name != 'Ice Palace': return False @@ -132,6 +198,11 @@ def can_place_boss(world, player, boss, dungeon_name, level=None): if boss in ["Blind"]: return False + # no Trinexx on Ice in doors without doing some health modelling + if world.doorShuffle[player] != 'vanilla' and boss == 'Trinexx': + if dungeon_name == 'Ganons Tower' and level == 'bottom': + return False + if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: return False @@ -246,4 +317,4 @@ def place_boss(boss, level, loc, loc_text, world, player): loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] loc_text = loc + ' (' + level + ')' logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) - world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) + world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player, level == 'bottom') From a77d3faf7848347cbbdb4791d8afa49d00320a10 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 5 Jan 2024 17:15:48 -0700 Subject: [PATCH 103/123] fix: moon pearl paths respect blocked doors --- Rules.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Rules.py b/Rules.py index ca69aa3b..e3e83ab9 100644 --- a/Rules.py +++ b/Rules.py @@ -1742,16 +1742,19 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - queue = deque([(region, [], {region})]) + queue = deque([(region, [], {region}, [region])]) seen_sets = set([frozenset({region})]) while queue: - (current, path, seen) = queue.popleft() + (current, path, seen, region_path) = queue.popleft() for entrance in current.entrances: + if entrance.door and entrance.door.blocked: + continue new_region = entrance.parent_region new_seen = seen.union({new_region}) if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_seen in seen_sets: continue new_path = path + [entrance.access_rule] + new_region_path = region_path + [new_region] seen_sets.add(frozenset(new_seen)) if not is_link(new_region): if world.logic[player] in ['owglitches', 'hybridglitches']: @@ -1796,7 +1799,7 @@ def set_bunny_rules(world, player, inverted): continue if is_bunny(new_region): # todo: if not owg or hmg and entrance is in bunny_impassible_doors, then skip this nonsense? - queue.append((new_region, new_path, new_seen)) + queue.append((new_region, new_path, new_seen, new_region_path)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) From 20d1398e4d59595678fb84d1459160f5bd459099 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 5 Jan 2024 20:47:41 -0600 Subject: [PATCH 104/123] Only apply bunny pocket rule if glitched logic --- Rules.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Rules.py b/Rules.py index e3e83ab9..36c1f351 100644 --- a/Rules.py +++ b/Rules.py @@ -1841,10 +1841,11 @@ def set_bunny_rules(world, player, inverted): continue add_rule(location, get_rule_to_add(region, location)) - for ent_name in bunny_pocket_entrances: - bunny_exit = world.get_entrance(ent_name, player) - if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region) and not can_bunny_pocket_to(world, ent_name, player): - add_rule(bunny_exit, lambda state: state.has_Pearl(player)) + if world.logic[player] in ['owglitches', 'hybridglitches']: + for ent_name in bunny_pocket_entrances: + bunny_exit = world.get_entrance(ent_name, player) + if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region) and not can_bunny_pocket_to(world, ent_name, player): + add_rule(bunny_exit, lambda state: state.has_Pearl(player)) drop_dungeon_entrances = { From 38a2c693e6764389699e1d19288c3b12efcdbd43 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 7 Jan 2024 08:34:09 -0600 Subject: [PATCH 105/123] Fixed issue with customized OW tile flips --- OverworldShuffle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 311b2342..00c23448 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1044,10 +1044,10 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): raise GenerationException('Could not find valid tile flips') # tile shuffle happens here - removed = copy.deepcopy(nonflipped_groups) + removed = [] if 0 < undefined_chance < 100: - for group in [g for g in groups if g not in nonflipped_groups]: - if group not in flipped_groups and random.randint(1, 100) > undefined_chance: + for group in groups: + if group[0] in nonflipped_groups or (group[0] not in flipped_groups and random.randint(1, 100) > undefined_chance): removed.append(group) # save shuffled tiles to list From 431cb2cdf4e3f89bd00b4e1f6e7584191cf425d6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 9 Jan 2024 07:02:18 -0600 Subject: [PATCH 106/123] Fix for vanilla partial key logic --- DoorShuffle.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 99577f37..3ae1c709 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -262,23 +262,24 @@ def vanilla_key_logic(world, player): world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) # special adjustments for vanilla - if world.mode[player] != 'standard' and world.dropshuffle[player] == 'none': - # adjust hc doors - def adjust_hc_door(door_rule): - if door_rule.new_rules[KeyRuleType.WorstCase] == 3: - door_rule.new_rules[KeyRuleType.WorstCase] = 2 - door_rule.small_key_num = 2 + if world.keyshuffle[player] != 'universal': + if world.mode[player] != 'standard' and not world.dropshuffle[player]: + # adjust hc doors + def adjust_hc_door(door_rule): + if door_rule.new_rules[KeyRuleType.WorstCase] == 3: + door_rule.new_rules[KeyRuleType.WorstCase] = 2 + door_rule.small_key_num = 2 - rules = world.key_logic[player]['Hyrule Castle'].door_rules - adjust_hc_door(rules['Sewers Secret Room Key Door S']) - adjust_hc_door(rules['Hyrule Dungeon Map Room Key Door S']) - adjust_hc_door(rules['Sewers Dark Cross Key Door N']) - # adjust pod front door - pod_front = world.key_logic[player]['Palace of Darkness'].door_rules['PoD Middle Cage N'] - if pod_front.new_rules[KeyRuleType.WorstCase] == 6: - pod_front.new_rules[KeyRuleType.WorstCase] = 1 - pod_front.small_key_num = 1 - # gt logic? I'm unsure it needs adjusting + rules = world.key_logic[player]['Hyrule Castle'].door_rules + adjust_hc_door(rules['Sewers Secret Room Key Door S']) + adjust_hc_door(rules['Hyrule Dungeon Map Room Key Door S']) + adjust_hc_door(rules['Sewers Dark Cross Key Door N']) + # adjust pod front door + pod_front = world.key_logic[player]['Palace of Darkness'].door_rules['PoD Middle Cage N'] + if pod_front.new_rules[KeyRuleType.WorstCase] == 6: + pod_front.new_rules[KeyRuleType.WorstCase] = 1 + pod_front.small_key_num = 1 + # gt logic? I'm unsure it needs adjusting def validate_vanilla_reservation(dungeon, world, player): From dc8ba4b1972b89a35bc75a04777c04f539119c35 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 9 Jan 2024 07:04:14 -0600 Subject: [PATCH 107/123] Fix for generation issue with GT bottom in boss shuffle --- Bosses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bosses.py b/Bosses.py index b0ddac3c..a4886be1 100644 --- a/Bosses.py +++ b/Bosses.py @@ -222,6 +222,7 @@ def place_bosses(world, player): ['Tower of Hera', None], ['Skull Woods', None], ['Ganons Tower', 'middle'], + ['Ganons Tower', 'bottom'], ['Eastern Palace', None], ['Desert Palace', None], ['Palace of Darkness', None], @@ -230,7 +231,6 @@ def place_bosses(world, player): ['Ice Palace', None], ['Misery Mire', None], ['Turtle Rock', None], - ['Ganons Tower', 'bottom'], ] all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons From 9ad5cebddf6a93e67aeac48d3e7f9e74623c15ef Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 9 Jan 2024 07:15:19 -0600 Subject: [PATCH 108/123] Fixed some typos and incorrect references --- Rom.py | 8 ++------ source/item/FillUtil.py | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Rom.py b/Rom.py index 2a5c1c38..f69d9895 100644 --- a/Rom.py +++ b/Rom.py @@ -799,7 +799,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): owFlags |= 0x0200 - # setting spriteID to D8, a placeholder sprite we use to inform ROM to spawn a dynamic item + # setting spriteID to D9, a placeholder sprite we use to inform ROM to spawn a dynamic item #for address in bonk_addresses: for address in [b for b in bonk_addresses if b != 0x4D0AE]: # temp fix for screen 1A murahdahla sprite replacement rom.write_byte(address, 0xD9) @@ -1549,7 +1549,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # b - Big Key # a - Small Key # - enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'none' + enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla' rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] == 'wild' else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00) @@ -2732,10 +2732,6 @@ def set_inverted_mode(world, player, rom, inverted_buffer): write_int16s(rom, snes_to_pc(0x1BB810), [0x00BE, 0x00C0, 0x013E]) # update pyramid hole entrance write_int16s(rom, snes_to_pc(0x1BB836), [0x001B, 0x001B, 0x001B]) - write_int16(rom, snes_to_pc(0x308300), 0x0140) # add extra pyramid hole - write_int16(rom, snes_to_pc(0x308320), 0x001B) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - rom.write_byte(snes_to_pc(0x308340), 0x7B) rom.write_byte(snes_to_pc(0x00DB9D), 0x1A) # make retreat bat gfx available in HC area rom.write_byte(snes_to_pc(0x00DC09), 0x1A) diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 1bdd8950..e30e7141 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -129,11 +129,11 @@ def create_item_pool_config(world): groups = LocationGroup('Major').locs(init_set) if world.bigkeyshuffle[player]: groups.locations.extend(mode_grouping['Big Keys']) - if world.dropshuffle[player] != 'none': + if world.dropshuffle[player]: groups.locations.extend(mode_grouping['Big Key Drops']) if world.keyshuffle[player] != 'none': groups.locations.extend(mode_grouping['Small Keys']) - if world.dropshuffle[player] != 'none': + if world.dropshuffle[player]: groups.locations.extend(mode_grouping['Key Drops']) if world.pottery[player] not in ['none', 'cave']: groups.locations.extend(mode_grouping['Pot Keys']) From 64a27993aa59ccb37a7a280ac1d0fc08cbfd3971 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 9 Jan 2024 20:32:12 -0600 Subject: [PATCH 109/123] Changing Big Bomb Shop start to be independent of ER modes --- BaseClasses.py | 2 +- OverworldShuffle.py | 7 ++++--- source/overworld/EntranceShuffle2.py | 13 ++++++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 3145ee16..da42a4e2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -310,7 +310,7 @@ class World(object): return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player) def is_bombshop_start(self, player): - return self.is_tile_swapped(0x2c, player) and (self.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not self.shufflelinks[player]) + return self.is_tile_swapped(0x2c, player) def is_pyramid_open(self, player): if self.open_pyramid[player] == 'yes': diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 00c23448..31abdc60 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1620,10 +1620,11 @@ def validate_layout(world, player): # check if entrances in region could be used to access region if world.shuffle[player] != 'vanilla': for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']: - if (entrance.name == 'Links House' and (world.mode[player] == 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ - or (entrance.name == 'Big Bomb Shop' and (world.mode[player] != 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ - or (entrance.name == 'Ganons Tower' and (world.mode[player] != 'inverted' and not world.shuffle_ganon[player])) \ or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['insanity']) \ + if (entrance.name == 'Links House' and ((not world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ + or (entrance.name == 'Big Bomb Shop' and ((world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ + or (entrance.name == 'Ganons Tower' and (not world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \ + or (entrance.name == 'Agahnims Tower' and (world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \ or (entrance.name == 'Tavern North' and not world.shuffletavern[player]): continue # these are fixed entrances and cannot be used for gaining access to region if entrance.name not in drop_entrances \ diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 95a3e5dd..63946275 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -129,7 +129,7 @@ def link_entrances_new(world, player): elif special_shuffle == 'skull': entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) rem_ent = None - if avail_pool.world.shuffle[avail_pool.player] in ['dungeons-simple', 'simple', 'restricted'] \ + if avail_pool.world.shuffle[avail_pool.player] in ['dungeonssimple', 'simple', 'restricted'] \ and not avail_pool.world.is_tile_swapped(0x00, avail_pool.player): rem_ent = random.choice(['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']) entrances.remove(rem_ent) @@ -1148,9 +1148,12 @@ def do_fixed_shuffle(avail, entrance_list): new_x = 'Agahnims Tower Exit' elif x == 'Agahnims Tower Exit': new_x = 'Ganons Tower Exit' + if avail.world.is_bombshop_start(avail.player): + if x == 'Links House Exit': + new_x = 'Big Bomb Shop' + elif x == 'Big Bomb Shop': + new_x = 'Links House Exit' lw_exits.add(new_x) - if avail.world.shufflelinks[avail.player] or avail.world.shuffle[avail.player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - lw_exits.update({'Big Bomb Shop'} if avail.world.is_bombshop_start(avail.player) else {'Links House Exit'}) filtered_choices = {i: opt for i, opt in choices.items() if all(t in lw_exits for t in opt[2])} _, choice = random.choice(list(filtered_choices.items())) else: @@ -2125,7 +2128,7 @@ drop_map = { } linked_drop_map = { - 'Hyrule Castle Secret Entrance Drop': 'Hyrule Castle Secret Entrance Stairs', + 'Hyrule Castle Secret Entrance Drop': 'Hyrule Castle Secret Entrance Stairs', 'Kakariko Well Drop': 'Kakariko Well Cave', 'Bat Cave Drop': 'Bat Cave Cave', 'North Fairy Cave Drop': 'North Fairy Cave', @@ -2169,7 +2172,7 @@ entrance_map = { 'Links House': 'Links House Exit', - 'Hyrule Castle Secret Entrance Stairs': 'Hyrule Castle Secret Entrance Exit', + 'Hyrule Castle Secret Entrance Stairs': 'Hyrule Castle Secret Entrance Exit', 'Kakariko Well Cave': 'Kakariko Well Exit', 'Bat Cave Cave': 'Bat Cave Exit', 'North Fairy Cave': 'North Fairy Cave Exit', From 17532160ee9b896a7e0dfef447964beb3f0afdac Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 10 Jan 2024 05:45:27 -0600 Subject: [PATCH 110/123] Allowing Insanity ER to decouple Standard entrances --- source/overworld/EntranceShuffle2.py | 32 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 63946275..caaec8bd 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -13,6 +13,7 @@ class EntrancePool(object): self.inverted = False self.coupled = True self.swapped = False + self.keep_drops_together = True self.default_map = {} self.one_way_map = {} self.skull_handled = False @@ -93,6 +94,8 @@ def link_entrances_new(world, player): raise RuntimeError(f'Shuffle mode {mode} is not yet supported') mode_cfg = copy.deepcopy(modes[mode]) avail_pool.swapped = mode_cfg['undefined'] == 'swap' + avail_pool.keep_drops_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True + avail_pool.coupled = mode_cfg['decoupled'] != 'on' if 'decoupled' in mode_cfg else True if avail_pool.is_standard(): do_standard_connections(avail_pool) pool_list = mode_cfg['pools'] if 'pools' in mode_cfg else {} @@ -106,8 +109,7 @@ def link_entrances_new(world, player): connect_random(holes, targets, avail_pool) elif special_shuffle == 'normal_drops': cross_world = mode_cfg['cross_world'] == 'on' if 'cross_world' in mode_cfg else False - keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True - do_holes_and_linked_drops(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, cross_world, keep_together) + do_holes_and_linked_drops(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, cross_world) elif special_shuffle == 'fixed_shuffle': do_fixed_shuffle(avail_pool, pool['entrances']) elif special_shuffle == 'same_world': @@ -191,10 +193,8 @@ def do_vanilla_connections(avail_pool): def do_main_shuffle(entrances, exits, avail, mode_def): cross_world = mode_def['cross_world'] == 'on' if 'cross_world' in mode_def else False - avail.coupled = mode_def['decoupled'] != 'on' if 'decoupled' in mode_def else True # drops and holes - keep_together = mode_def['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_def else True - do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_together) + do_holes_and_linked_drops(entrances, exits, avail, cross_world) if not avail.coupled: avail.decoupled_entrances.extend(entrances) @@ -454,11 +454,21 @@ def do_blacksmith(entrances, exits, avail): def do_standard_connections(avail): - connect_two_way('Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', avail) - # cannot move uncle cave - connect_two_way('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', avail) - connect_entrance('Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', avail) + std_exits = ['Hyrule Castle Exit (South)', 'Hyrule Castle Secret Entrance Exit'] + if not avail.keep_drops_together: + random.shuffle(std_exits) connect_two_way('Links House', 'Links House Exit', avail) + connect_entrance('Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', avail) + if avail.coupled: + connect_two_way('Hyrule Castle Entrance (South)', std_exits[0], avail) + # cannot move uncle cave + connect_two_way('Hyrule Castle Secret Entrance Stairs', std_exits[1], avail) + else: + connect_entrance('Hyrule Castle Entrance (South)', std_exits[0], avail) + connect_entrance('Hyrule Castle Secret Entrance Stairs', std_exits[1], avail) + random.shuffle(std_exits) + connect_exit(std_exits[0], 'Hyrule Castle Entrance (South)', avail) + connect_exit(std_exits[1], 'Hyrule Castle Secret Entrance Stairs', avail) def remove_from_list(t_list, removals): @@ -466,7 +476,7 @@ def remove_from_list(t_list, removals): t_list.remove(r) -def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_together): +def do_holes_and_linked_drops(entrances, exits, avail, cross_world): holes_to_shuffle = [x for x in entrances if x in drop_map] if not avail.world.shuffle_ganon: @@ -483,7 +493,7 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe remove_from_list(entrances, ['Pyramid Hole', 'Pyramid Entrance']) remove_from_list(exits, ['Pyramid', 'Pyramid Exit']) - if not keep_together: + if not avail.keep_drops_together: targets = [avail.one_way_map[x] for x in holes_to_shuffle] connect_random(holes_to_shuffle, targets, avail) remove_from_list(entrances, holes_to_shuffle) From 2bec50b26bcbc698fb38babe313a27332052b6db Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 10 Jan 2024 05:57:39 -0600 Subject: [PATCH 111/123] Implemented District ER --- BaseClasses.py | 4 +- ItemList.py | 3 +- OverworldShuffle.py | 36 +-- Rom.py | 18 +- Rules.py | 2 +- TestSuite.py | 1 + TestSuiteStat.py | 4 +- resources/app/cli/args.json | 1 + resources/app/cli/lang/en.json | 1 + resources/app/gui/lang/en.json | 1 + .../app/gui/randomize/entrando/widgets.json | 1 + source/overworld/EntranceShuffle2.py | 232 ++++++++++++++++-- 12 files changed, 255 insertions(+), 49 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index da42a4e2..404428a1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -318,7 +318,7 @@ class World(object): elif self.open_pyramid[player] == 'no': return False else: - if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']: return False elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']: return True @@ -3464,7 +3464,7 @@ class Pot(object): # byte 0: DDDE EEEE (DR, ER) dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4} er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8, - 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10} + 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10, "district": 11} # byte 1: LLLW WSS? (logic, mode, sword) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5} diff --git a/ItemList.py b/ItemList.py index 9af16cf6..fa658559 100644 --- a/ItemList.py +++ b/ItemList.py @@ -986,7 +986,8 @@ def balance_prices(world, player): def check_hints(world, player): - if world.shuffle[player] in ['simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity']: + if (world.shuffle[player] in ['simple', 'restricted', 'full', 'district', 'swapped', 'crossed', 'insanity'] + or (world.shuffle[player] in ['lite', 'lean'] and world.shopsanity[player])): for shop, location_list in shop_to_location_table.items(): if shop in ['Capacity Upgrade', 'Paradox Shop', 'Potion Shop']: continue # near the queen, near potions, and near 7 chests are fine diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 31abdc60..cf280b75 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1072,7 +1072,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): 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', 'swapped', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): + if not do_grouped and world.shuffle[player] in ['simple', 'restricted', 'full', 'district'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): free_dw_drops = parity[5] + (1 if world.shuffle_ganon[player] else 0) free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon[player] else 0) if free_dw_drops == free_drops: @@ -1124,7 +1124,7 @@ def define_tile_groups(world, do_grouped, player): return False # sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen - if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \ + if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \ and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \ or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): return False @@ -1138,24 +1138,31 @@ def define_tile_groups(world, do_grouped, player): 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']): + # hyrule castle and sanctuary connector + if world.shuffle[player] in ['vanilla', 'district'] or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']): merge_groups([[0x13, 0x14, 0x1b]]) + # sanctuary and grave connector + if world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']: + merge_groups([[0x13, 0x14]]) + + # cross-screen connector + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'district']: + merge_groups([[0x03, 0x0a], [0x28, 0x29]]) + + # turtle rock connector + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']: + merge_groups([[0x05, 0x07]]) + + # all non-parallel screens 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]]) + # special case: non-parallel keep similar if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x28, 0x29]]) + # whirlpool screens if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) @@ -1562,7 +1569,7 @@ def validate_layout(world, player): for dest_region in entrance_connectors[region_name]: if dest_region not in explored_regions: explore_region(dest_region) - if world.shuffle[player] not in ['insanity'] and region_name in sane_connectors: + if world.shuffle[player] not in ['district', 'insanity'] and region_name in sane_connectors: for dest_region in sane_connectors[region_name]: if dest_region not in explored_regions: explore_region(dest_region) @@ -1619,12 +1626,13 @@ def validate_layout(world, player): break # check if entrances in region could be used to access region if world.shuffle[player] != 'vanilla': + # TODO: For District ER, we need to check if there is a dropdown or connector that is able to connect for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']: - or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['insanity']) \ if (entrance.name == 'Links House' and ((not world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ or (entrance.name == 'Big Bomb Shop' and ((world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ or (entrance.name == 'Ganons Tower' and (not world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \ or (entrance.name == 'Agahnims Tower' and (world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \ + or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['district', 'insanity']) \ or (entrance.name == 'Tavern North' and not world.shuffletavern[player]): continue # these are fixed entrances and cannot be used for gaining access to region if entrance.name not in drop_entrances \ diff --git a/Rom.py b/Rom.py index f69d9895..d47fa98b 100644 --- a/Rom.py +++ b/Rom.py @@ -1667,7 +1667,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # rom.write_byte(snes_to_pc(0x0DB730), 0x08) # allows chickens to travel across water # allow smith into multi-entrance caves in appropriate shuffles - if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): + if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -2142,7 +2142,7 @@ def write_strings(rom, world, player, team): tt.removeUnwantedText() # Let's keep this guy's text accurate to the shuffle setting. - if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']: + if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'lite', 'lean']: tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' @@ -2193,7 +2193,7 @@ def write_strings(rom, world, player, team): # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. if world.shuffle[player] not in ['lite', 'lean']: entrances_to_hint.update(InconvenientOtherEntrances) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'swapped']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']: hint_count = 0 elif world.shuffle[player] in ['simple', 'restricted']: hint_count = 2 @@ -2227,7 +2227,7 @@ def write_strings(rom, world, player, team): entrances_to_hint.update(OtherEntrances) if world.mode[player] != 'inverted': entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'}) - if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean']: + if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']: if world.shufflelinks[player]: entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'}) entrances_to_hint.update({'Links House': 'The hero\'s old residence'}) @@ -2244,7 +2244,7 @@ def write_strings(rom, world, player, team): entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) - hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 0 + hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 0 hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: @@ -2263,7 +2263,7 @@ def write_strings(rom, world, player, team): if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: locations_to_hint.extend(InconvenientVanillaLocations) random.shuffle(locations_to_hint) - hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 5 + hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 5 hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 del locations_to_hint[hint_count:] for location in locations_to_hint: @@ -2338,7 +2338,7 @@ def write_strings(rom, world, player, team): if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) random.shuffle(items_to_hint) - hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 8 + hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8 hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0 while hint_count > 0 and len(items_to_hint) > 0: @@ -2382,11 +2382,11 @@ def write_strings(rom, world, player, team): choices.clear() choices.append(location_item) if hint_type == 'foolish': - if district.dungeons and world.shuffle[player] != 'vanilla': + if district.dungeons and world.shuffle[player] not in ['vanilla', 'district']: choices.extend(district.dungeons) hint_type = 'dungeon_path' elif district.access_points and world.shuffle[player] not in ['vanilla', 'dungeonssimple', - 'dungeonsfull']: + 'dungeonsfull', 'district']: choices.extend([x.hint_text for x in district.access_points]) hint_type = 'connector' if hint_type == 'foolish': diff --git a/Rules.py b/Rules.py index 36c1f351..10a6860b 100644 --- a/Rules.py +++ b/Rules.py @@ -1067,7 +1067,7 @@ def ow_inverted_rules(world, player): else: set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player)) # barrier gets removed after killing agahnim, rule for that added later set_rule(world.get_entrance('GT Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) - set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity')) + set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity')) if world.is_tile_swapped(0x03, player): set_rule(world.get_entrance('Spectacle Rock Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'] and state.has_Pearl(player)) diff --git a/TestSuite.py b/TestSuite.py index 9c2f29d0..a1b18ab3 100644 --- a/TestSuite.py +++ b/TestSuite.py @@ -51,6 +51,7 @@ def main(args=None): test("Full ", "--shuffle full") test("Lite ", "--shuffle lite") test("Lean ", "--shuffle lean") + test("District ", "--shuffle district") test("Swapped ", "--shuffle swapped") test("Crossed ", "--shuffle crossed") test("Insanity ", "--shuffle insanity") diff --git a/TestSuiteStat.py b/TestSuiteStat.py index 882f9ffb..574e3fb4 100644 --- a/TestSuiteStat.py +++ b/TestSuiteStat.py @@ -14,7 +14,7 @@ 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','swapped','crossed','insanity'], + 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','district','swapped','crossed','insanity'], 'shufflelinks': [True, False], 'shuffleganon': [True, False], 'door_shuffle': ['vanilla', 'basic', 'crossed'], @@ -39,7 +39,7 @@ SETTINGS = { 'goal': ['ganon'], 'swords': ['random'], 'shuffle': ['vanilla', - 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity' + 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity' ], 'shufflelinks': [True, False], 'shuffleganon': [True, False], diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 09e897f7..ebe1181b 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -209,6 +209,7 @@ "full", "lite", "lean", + "district", "swapped", "crossed", "insanity", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 3ad7f887..db988393 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -212,6 +212,7 @@ " item locations are shuffled in separate pools. Non-item", " locations remain vanilla. Connectors are same-world.", "Lean: Same as Lite, except connectors can travel cross worlds.", + "District: Entrances are shuffled within their overworld districts.", "Crossed: Mix cave and dungeon entrances freely while allowing", " caves to cross between worlds.", "Swapped: Same as Crossed, but entrances switch places in pairs.", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index e23c02cd..02bb4490 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -181,6 +181,7 @@ "randomizer.entrance.entranceshuffle.restricted": "Restricted", "randomizer.entrance.entranceshuffle.full": "Full", "randomizer.entrance.entranceshuffle.lean": "Lean", + "randomizer.entrance.entranceshuffle.district": "District", "randomizer.entrance.entranceshuffle.swapped": "Swapped", "randomizer.entrance.entranceshuffle.crossed": "Crossed", "randomizer.entrance.entranceshuffle.insanity": "Insanity", diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index c325ea26..809dbac5 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -9,6 +9,7 @@ "full", "lite", "lean", + "district", "swapped", "crossed", "insanity", diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index caaec8bd..e9c8f8d0 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -13,6 +13,7 @@ class EntrancePool(object): self.inverted = False self.coupled = True self.swapped = False + self.assumed_loose_caves = False self.keep_drops_together = True self.default_map = {} self.one_way_map = {} @@ -55,6 +56,7 @@ def link_entrances_new(world, player): avail_pool.entrances = set(i_drop_map.keys()).union(i_entrance_map.keys()).union(i_single_ent_map.keys()) avail_pool.exits = set(i_entrance_map.values()).union(i_drop_map.values()).union(i_single_ent_map.values()) avail_pool.inverted = world.mode[player] == 'inverted' + avail_pool.assumed_loose_caves = world.shuffle[player] == 'district' inverted_substitution(avail_pool, avail_pool.entrances, True, True) inverted_substitution(avail_pool, avail_pool.exits, False, True) avail_pool.original_entrances.update(avail_pool.entrances) @@ -128,6 +130,14 @@ def link_entrances_new(world, player): do_limited_shuffle_exclude_drops(pool, avail_pool, False) elif special_shuffle == 'vanilla': do_vanilla_connect(pool, avail_pool) + elif special_shuffle == 'district': + drops = [] + world_limiter = LW_Entrances if pool['condition'] == 'lightworld' else DW_Entrances + entrances = [e for e in pool['entrances'] if e in world_limiter] + if 'drops' in pool: + drops = [e for e in pool['drops'] if combine_linked_drop_map[e] in world_limiter] + entrances, exits = find_entrances_and_exits(avail_pool, entrances+drops) + do_main_shuffle(entrances, exits, avail_pool, mode_cfg) elif special_shuffle == 'skull': entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) rem_ent = None @@ -151,6 +161,8 @@ def link_entrances_new(world, player): do_vanilla_connections(avail_pool) elif undefined_behavior in ['shuffle', 'swap']: do_main_shuffle(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, mode_cfg) + elif undefined_behavior == 'error': + assert len(avail_pool.entrances)+len(avail_pool.exits) == 0, 'Not all entrances were placed in their districts' # afterward @@ -310,7 +322,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def): return not avail.is_standard() or x != 'Bonk Fairy (Light)' # old man S&Q cave - if not cross_world: + if not cross_world and not avail.assumed_loose_caves: #TODO: Add Swapped ER support for this # OM Cave entrance in lw/dw if cross_world off if 'Old Man Cave Exit (West)' in rem_exits: @@ -382,7 +394,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def): def do_old_man_cave_exit(entrances, exits, avail, cross_world): if 'Old Man Cave Exit (East)' in exits: from EntranceShuffle import build_accessible_region_list - if not avail.world.is_tile_swapped(0x03, avail.player): + if not avail.world.is_tile_swapped(0x03, avail.player) or avail.world.shuffle[avail.player] == 'district': region_name = 'West Death Mountain (Top)' else: region_name = 'West Dark Death Mountain (Top)' @@ -420,11 +432,14 @@ def do_blacksmith(entrances, exits, avail): if avail.world.logic[avail.player] in ['noglitches', 'minorglitches'] and (avail.world.is_tile_swapped(0x29, avail.player) == avail.inverted): assumed_inventory.append('Titans Mitts') + blacksmith_options = list() if not avail.world.is_bombshop_start(avail.player): - links_region = avail.world.get_entrance('Links House Exit', avail.player).connected_region.name + links_region = avail.world.get_entrance('Links House Exit', avail.player).connected_region else: - links_region = avail.world.get_entrance('Big Bomb Shop Exit', avail.player).connected_region.name - blacksmith_options = list(get_accessible_entrances(links_region, avail, assumed_inventory, False, True, True)) + links_region = avail.world.get_entrance('Big Bomb Shop Exit', avail.player).connected_region + if links_region is not None: + links_region = links_region.name + blacksmith_options = list(get_accessible_entrances(links_region, avail, assumed_inventory, False, True, True)) if avail.inverted: dark_sanc = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name @@ -440,6 +455,9 @@ def do_blacksmith(entrances, exits, avail): blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances] blacksmith_options = [x for x in blacksmith_options if x in entrances] + if avail.world.shuffle[avail.player] == 'district' and not len(blacksmith_options): + blacksmith_options = [e for e in entrances if e not in Forbidden_Swap_Entrances or not avail.swapped] + assert len(blacksmith_options), 'No available entrances left to place Blacksmith' blacksmith_choice = random.choice(blacksmith_options) connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail) @@ -577,6 +595,8 @@ def do_dark_sanc(entrances, exits, avail): forbidden.extend(Forbidden_Swap_Entrances) if not avail.world.is_bombshop_start(avail.player): forbidden.append('Links House') + else: + forbidden.append('Big Bomb Shop') if avail.world.owShuffle[avail.player] == 'vanilla': choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances] else: @@ -612,7 +632,7 @@ def do_links_house(entrances, exits, avail, cross_world): forbidden.append('Mimic Cave') if avail.world.is_bombshop_start(avail.player) and (avail.inverted == avail.world.is_tile_swapped(0x03, avail.player)): forbidden.extend(['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']) - if avail.inverted: + if avail.inverted and avail.world.shuffle[avail.player] != 'district': dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name forbidden.extend(get_nearby_entrances(avail, dark_sanc_region)) else: @@ -654,7 +674,7 @@ def do_links_house(entrances, exits, avail, cross_world): sanc_spawn_can_be_dark = (not avail.inverted and avail.world.doorShuffle[avail.player] in ['partitioned', 'crossed'] and avail.world.intensity[avail.player] >= 3) - if cross_world and not sanc_spawn_can_be_dark: + if (cross_world and not sanc_spawn_can_be_dark) or avail.world.shuffle[avail.player] == 'district': possible = [e for e in entrance_pool if e not in forbidden] else: world_list = LW_Entrances if not avail.inverted else DW_Entrances @@ -686,7 +706,7 @@ def do_links_house(entrances, exits, avail, cross_world): return if avail.world.shuffle[avail.player] in ['lite', 'lean']: rem_exits = [e for e in avail.exits if e in Connector_Exit_Set and e not in Dungeon_Exit_Set] - multi_exit_caves = figure_out_connectors(rem_exits) + multi_exit_caves = figure_out_connectors(rem_exits, avail) if cross_world: possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List] possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots] @@ -695,7 +715,7 @@ def do_links_house(entrances, exits, avail, cross_world): possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List and e in world_list] possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots and e in world_list] else: - multi_exit_caves = figure_out_connectors(exits) + multi_exit_caves = figure_out_connectors(exits, avail) entrance_pool = entrances if avail.coupled else avail.decoupled_entrances if cross_world: possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List] @@ -842,12 +862,22 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo return found_entrances -def figure_out_connectors(exits): +def figure_out_connectors(exits, avail): multi_exit_caves = [] - for item in Connector_List: + cave_list = list(Connector_List) + if avail.assumed_loose_caves: + sw_list = ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'] + random.shuffle(sw_list) + cave_list.extend([sw_list]) + cave_list.extend([[entrance_map[e]] for e in linked_drop_map.values() if 'Inverted ' not in e]) + for item in cave_list: if all(x in exits for x in item): remove_from_list(exits, item) multi_exit_caves.append(list(item)) + elif avail.assumed_loose_caves and any(x in exits for x in item): + remaining = [i for i in item if i in exits] + remove_from_list(exits, remaining) + multi_exit_caves.append(list(remaining)) return multi_exit_caves @@ -1025,7 +1055,7 @@ def figure_out_must_exits_same_world(entrances, exits, avail): for x in entrances: lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x) - multi_exit_caves = figure_out_connectors(exits) + multi_exit_caves = figure_out_connectors(exits, avail) must_exit_lw, must_exit_dw = must_exits_helper(avail) must_exit_lw = must_exit_filter(avail, must_exit_lw, lw_entrances) @@ -1035,7 +1065,7 @@ def figure_out_must_exits_same_world(entrances, exits, avail): def figure_out_must_exits_cross_world(entrances, exits, avail): - multi_exit_caves = figure_out_connectors(exits) + multi_exit_caves = figure_out_connectors(exits, avail) must_exit_lw, must_exit_dw = must_exits_helper(avail) must_exit = must_exit_filter(avail, must_exit_lw + must_exit_dw, entrances) @@ -1374,7 +1404,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): # find multi exit cave candidates = [] for candidate in cave_options: - if not isinstance(candidate, str) and len(candidate) > 1 and (candidate in used_caves + allow_single = avail.assumed_loose_caves or len(candidate) > 1 + if not isinstance(candidate, str) and allow_single and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances): if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped candidates.append(candidate) @@ -1408,6 +1439,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if entrance in invalid_connections: for exit2 in invalid_connections[entrance]: invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]).union(invalid_cave_connections[tuple(cave)]) + elif len(cave) == 1 and avail.assumed_loose_caves: + #TODO: keep track of caves we use for must exits that are unaccounted here + # the other exits of the cave should NOT be used to satisfy must-exit later + pass elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit cave_entrances = [] for cave_exit in rnd_cave[:-1]: @@ -1532,14 +1567,12 @@ def find_entrances_and_exits(avail_pool, entrance_pool): entrances, targets = [], [] inverted_substitution(avail_pool, entrance_pool, True) for item in entrance_pool: - if item == 'Ganons Tower' and not avail_pool.world.shuffle_ganon[avail_pool.player]: - continue if item in avail_pool.entrances: entrances.append(item) - if item in entrance_map and entrance_map[item] in avail_pool.exits: - targets.append(entrance_map[item]) - elif item in single_entrance_map and single_entrance_map[item] in avail_pool.exits: - targets.append(single_entrance_map[item]) + if item in avail_pool.default_map and avail_pool.default_map[item] in avail_pool.exits: + targets.append(avail_pool.default_map[item]) + elif item in avail_pool.one_way_map and avail_pool.one_way_map[item] in avail_pool.exits: + targets.append(avail_pool.one_way_map[item]) return entrances, targets @@ -2077,6 +2110,157 @@ modes = { }, } }, + 'district': { + 'undefined': 'error', + 'keep_drops_together': 'off', + 'cross_world': 'off', + 'pools': { + 'northwest_hyrule': { + 'special': 'district', + 'condition': 'lightworld', + 'drops': ['Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'North Fairy Cave Drop', + + 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'], + 'entrances': ['Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', 'North Fairy Cave', + 'Lost Woods Gamble', 'Lumberjack House', 'Old Man Cave (West)', 'Death Mountain Return Cave (West)', + 'Fortune Teller (Light)', 'Bonk Rock Cave', 'Graveyard Cave', 'Kings Grave', + + 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Dark Lumberjack Shop', + 'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', + 'Red Shield Shop'] + }, + 'northwest_dark_world': { + 'special': 'district', + 'condition': 'darkworld', + 'drops': ['Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole', + + 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'North Fairy Cave Drop', + 'Kakariko Well Drop', 'Bat Cave Drop'], + 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Dark Lumberjack Shop', + 'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', + 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Dark World Shop', 'Brewery', + 'Red Shield Shop', 'Hammer Peg Cave', + + 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', 'North Fairy Cave', + 'Kakariko Well Cave', 'Bat Cave Cave', 'Lost Woods Gamble', 'Lumberjack House', 'Fortune Teller (Light)', + 'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Bonk Rock Cave', 'Graveyard Cave', + 'Kings Grave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)', 'Snitch Lady (West)', + 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Bush Covered House', 'Light World Bomb Hut', + 'Kakariko Shop', 'Tavern North', 'Tavern (Front)', 'Blacksmiths Hut'] + }, + 'central_hyrule': { + 'special': 'district', + 'condition': 'lightworld', + 'drops': ['Hyrule Castle Secret Entrance Drop', 'Inverted Pyramid Hole', + + 'Pyramid Hole'], + 'entrances': ['Hyrule Castle Secret Entrance Stairs', 'Inverted Pyramid Entrance', 'Agahnims Tower', + 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (South)', + 'Bonk Fairy (Light)', 'Links House', 'Cave 45', 'Light Hype Fairy', 'Dam', + + 'Pyramid Entrance', 'Pyramid Fairy', 'Bonk Fairy (Dark)', 'Big Bomb Shop', 'Hype Cave', 'Swamp Palace'] + }, + 'kakariko': { + 'special': 'district', + 'condition': 'lightworld', + 'drops': ['Kakariko Well Drop', 'Bat Cave Drop'], + 'entrances': ['Kakariko Well Cave', 'Bat Cave Cave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)', + 'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Bush Covered House', + 'Light World Bomb Hut', 'Kakariko Shop', 'Tavern North', 'Tavern (Front)', 'Blacksmiths Hut', + 'Two Brothers House (West)', 'Two Brothers House (East)', 'Library', 'Kakariko Gamble Game', + + 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Dark World Shop', 'Brewery', + 'Hammer Peg Cave', 'Archery Game'] + }, + 'eastern_hyrule': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Waterfall of Wishing', 'Potion Shop', 'Sahasrahlas Hut', 'Eastern Palace', 'Lake Hylia Fairy', + 'Long Fairy Cave', + + 'Dark Potion Shop', 'Palace of Darkness Hint', 'Palace of Darkness', 'Dark Lake Hylia Fairy', + 'East Dark World Hint'] + }, + 'lake_hylia': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Capacity Upgrade', 'Mini Moldorm Cave', + 'Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave', + + 'Dark Lake Hylia Shop', 'Ice Palace', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', + 'Dark Lake Hylia Ledge Spike Cave'] + }, + 'desert': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Desert Palace Entrance (North)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (South)', + 'Desert Palace Entrance (East)', 'Checkerboard Cave', 'Aginahs Cave', 'Desert Fairy', '50 Rupee Cave', + + 'Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint'] + }, + 'death_mountain': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave', + 'Death Mountain Return Cave (East)', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', + 'Spiral Cave', 'Spiral Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Bottom)', + 'Mimic Cave', 'Hookshot Fairy', 'Paradox Cave (Top)', 'Paradox Cave (Middle)', 'Paradox Cave (Bottom)', + + 'Ganons Tower', 'Dark Death Mountain Fairy', 'Spike Cave', 'Superbunny Cave (Bottom)', 'Superbunny Cave (Top)', + 'Dark Death Mountain Shop', 'Hookshot Cave', 'Hookshot Cave Back Entrance', + 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock'] + }, + 'dark_death_mountain': { + 'special': 'district', + 'condition': 'darkworld', + 'entrances': ['Ganons Tower', 'Dark Death Mountain Fairy', 'Spike Cave', 'Superbunny Cave (Bottom)', 'Superbunny Cave (Top)', + 'Dark Death Mountain Shop', 'Hookshot Cave', 'Hookshot Cave Back Entrance', + 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock', + + 'Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave', + 'Death Mountain Return Cave (East)', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', + 'Spiral Cave', 'Spiral Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Bottom)', + 'Mimic Cave', 'Hookshot Fairy', 'Paradox Cave (Top)', 'Paradox Cave (Middle)', 'Paradox Cave (Bottom)'] + }, + 'south_dark_world': { + 'special': 'district', + 'condition': 'darkworld', + 'entrances': ['Archery Game', 'Bonk Fairy (Dark)', 'Big Bomb Shop', 'Hype Cave', 'Dark Lake Hylia Shop', 'Ice Palace', + 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', + 'Swamp Palace', + + 'Two Brothers House (West)', 'Two Brothers House (East)', 'Library', 'Kakariko Gamble Game', + 'Bonk Fairy (Light)', 'Links House', 'Cave 45', 'Desert Fairy', '50 Rupee Cave', 'Dam', + 'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Capacity Upgrade', + 'Mini Moldorm Cave', 'Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave'] + }, + 'east_dark_world': { + 'special': 'district', + 'condition': 'darkworld', + 'drops': ['Pyramid Hole', + + 'Hyrule Castle Secret Entrance Drop', 'Inverted Pyramid Hole'], + 'entrances': ['Pyramid Entrance', 'Pyramid Fairy', 'Dark Potion Shop', 'Palace of Darkness Hint', 'Palace of Darkness', + 'Dark Lake Hylia Fairy', 'East Dark World Hint', + + 'Hyrule Castle Secret Entrance Stairs', 'Inverted Pyramid Entrance', 'Waterfall of Wishing', 'Potion Shop', + 'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', + 'Hyrule Castle Entrance (South)', 'Sahasrahlas Hut', 'Eastern Palace', 'Lake Hylia Fairy', 'Long Fairy Cave'] + }, + 'mire': { + 'special': 'district', + 'condition': 'darkworld', + 'entrances': ['Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint', + + 'Desert Palace Entrance (North)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (South)', + 'Desert Palace Entrance (East)', 'Checkerboard Cave', 'Aginahs Cave'] + } + } + }, 'swapped': { 'undefined': 'swap', 'keep_drops_together': 'on', @@ -2149,6 +2333,13 @@ linked_drop_map = { 'Inverted Pyramid Hole': 'Inverted Pyramid Entrance' } +sw_linked_drop_map = { + 'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (West)', + 'Skull Woods First Section Hole (North)': 'Skull Woods First Section Door', + 'Skull Woods First Section Hole (West)': 'Skull Woods First Section Door', + 'Skull Woods First Section Hole (East)': 'Skull Woods First Section Door' +} + entrance_map = { 'Desert Palace Entrance (South)': 'Desert Palace Exit (South)', 'Desert Palace Entrance (West)': 'Desert Palace Exit (West)', @@ -2257,6 +2448,7 @@ single_entrance_map = { } combine_map = {**entrance_map, **single_entrance_map, **drop_map} +combine_linked_drop_map = {**linked_drop_map, **sw_linked_drop_map} LW_Entrances = [] DW_Entrances = [] From 2b461f2dfb3cde7b9d6a7a1cd1fc5fe62cd89d90 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 10 Jan 2024 06:09:11 -0600 Subject: [PATCH 112/123] Version bump 0.3.4.1 --- CHANGELOG.md | 12 ++++++++++-- OverworldShuffle.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98cb8905..552be77a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,18 @@ # Changelog +## 0.3.4.1 +- Implemented new District ER mode option +- Added alternate boss logic when in GT Ice Basement +- Updated Inverted 2.0 to start in Bomb Shop regardless of ER mode +- Fixed broken customizer features with OWR Tile Flip +- Allowing Insanity ER + Standard to decouple standard entrances + ## 0.3.4.0 -- \~Merged in some things from DR v1.4.0.0-v~ +- \~Merged in DR v1.2.0.23~ - Improved bunny-walking algorithm - Improved multiworld balancing -- Implemented Hyrid Major Glitches logic (thanks Muffins/Espeon) +- \~Merged in some things from DR v1.4.0.0-v~ +- Implemented Hybrid Major Glitches logic (thanks Muffins/Espeon) - Added sparkles to Bonk Drop locations for better visibility - Some tweaks/improvements to Shuffle Song Instruments - Replaced Save Settings on Exit with Settings on Load diff --git a/OverworldShuffle.py b/OverworldShuffle.py index cf280b75..45da3e4d 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.4.0' +version_number = '0.3.4.1' # branch indicator is intentionally different across branches version_branch = '-u' From 041c7fae5df809efc8ae17f6c0eb09449df18e98 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 14 Jan 2024 09:08:21 -0600 Subject: [PATCH 113/123] Fix for Old Man Cave running out of choices in Swapped ER --- source/overworld/EntranceShuffle2.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index e9c8f8d0..4703e1a3 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1410,6 +1410,15 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped candidates.append(candidate) cave = random.choice(candidates) + + if avail.swapped and len(candidates) > 1 and not avail.world.is_tile_swapped(0x03, avail.player): + DM_Connector_Prefixes = ['Spectacle Rock Cave', 'Old Man House', 'Death Mountain Return'] + if any(p for p in DM_Connector_Prefixes if p in cave[0]): # if chosen cave is a DM connector + remain = [p for p in DM_Connector_Prefixes if len([e for e in entrances if p in e]) > 0] # gets remaining DM caves left in pool + if len(remain) == 1: # guarantee that old man rescue cave can still be placed + candidates.remove(cave) + cave = random.choice(candidates) + if cave is None: raise RuntimeError('No more caves left. Should not happen!') From 121726b095e632cf5ee2e4428c97a41388105b32 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 18 Jan 2024 09:13:48 -0600 Subject: [PATCH 114/123] Small adjustment to hints in Swapped and Lite/Lean ER --- Rom.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Rom.py b/Rom.py index d47fa98b..1669aa6a 100644 --- a/Rom.py +++ b/Rom.py @@ -2193,9 +2193,9 @@ def write_strings(rom, world, player, team): # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. if world.shuffle[player] not in ['lite', 'lean']: entrances_to_hint.update(InconvenientOtherEntrances) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped']: hint_count = 0 - elif world.shuffle[player] in ['simple', 'restricted']: + elif world.shuffle[player] in ['simple', 'restricted', 'lite', 'lean']: hint_count = 2 else: hint_count = 4 @@ -2247,14 +2247,14 @@ def write_strings(rom, world, player, team): hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 0 hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0 for entrance in all_entrances: - if entrance.name in entrances_to_hint: - if hint_count > 0: + if hint_count > 0: + if entrance.name in entrances_to_hint: this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' tt[hint_locations.pop(0)] = this_hint entrances_to_hint.pop(entrance.name) hint_count -= 1 - else: - break + else: + break # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() From 87e4e95cd390c699b9ce3ca66ac1702d7384fa93 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 18 Jan 2024 09:18:53 -0600 Subject: [PATCH 115/123] Changing spawn prompt to show Bomb Shop for Inverted 2.0 --- Rom.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index 1669aa6a..cfa3fdcd 100644 --- a/Rom.py +++ b/Rom.py @@ -2542,9 +2542,15 @@ def write_strings(rom, world, player, team): rom.write_byte(0x04a52e, 0x06) # follower set to blind maiden # inverted spawn menu changes + lh_text = "House" + if world.is_tile_swapped(0x2c, player): + lh_text = "Bomb Shop" + sanc_text = "Sanctuary" + if world.mode[player] == 'inverted': + sanc_text = "Dark Chapel" + tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n{CHOICE3}" + tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n Mountain Cave\n{CHOICE2}" if world.mode[player] == 'inverted': - tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s House\n Dark Chapel\n{CHOICE3}" - tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s House\n Dark Chapel\n Mountain Cave\n{CHOICE2}" tt['intro_main'] = CompressedTextMapper.convert( "{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n" + "{PAUSE3}\nInverted\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n" From f7705c3ec922286ccf902b3a34b9a39a10a637b9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 18 Jan 2024 09:20:49 -0600 Subject: [PATCH 116/123] Fixed issue with AT/GT in Inverted Swapped ER --- source/overworld/EntranceShuffle2.py | 37 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 4703e1a3..be26d772 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -17,6 +17,7 @@ class EntrancePool(object): self.keep_drops_together = True self.default_map = {} self.one_way_map = {} + self.combine_map = {} self.skull_handled = False self.links_on_mountain = False self.decoupled_entrances = [] @@ -71,6 +72,7 @@ def link_entrances_new(world, player): default_map['Agahnims Tower'] = 'Ganons Tower Exit' avail_pool.default_map = default_map avail_pool.one_way_map = one_way_map + avail_pool.combine_map = {**default_map, **one_way_map} global LW_Entrances, DW_Entrances LW_Entrances = [] @@ -1129,7 +1131,7 @@ def do_cross_world_connectors(entrances, caves, avail): avail.decoupled_entrances.remove(choice) else: if avail.swapped and len(entrances) > 1: - chosen_entrance = next(e for e in entrances if combine_map[e] != ext) + chosen_entrance = next(e for e in entrances if avail.combine_map[e] != ext) entrances.remove(chosen_entrance) else: chosen_entrance = entrances.pop() @@ -1178,7 +1180,7 @@ def do_fixed_shuffle(avail, entrance_list): choice = choices[i] elif rules.must_exit_to_lw: lw_exits = set() - for e, x in combine_map.items(): + for e, x in avail.combine_map.items(): if x in avail.exits: region = avail.world.get_entrance(e, avail.player).parent_region if region.type == RegionType.LightWorld: @@ -1369,7 +1371,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if entrance not in entrances: entrances.append(entrance) if avail.swapped: - swap_forbidden = [e for e in entrances if combine_map[e] in must_exit] + swap_forbidden = [e for e in entrances if avail.combine_map[e] in must_exit] for e in swap_forbidden: entrances.remove(e) entrances.sort() # sort these for consistency @@ -1407,7 +1409,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): allow_single = avail.assumed_loose_caves or len(candidate) > 1 if not isinstance(candidate, str) and allow_single and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances): - if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped + if not avail.swapped or (avail.combine_map[exit] not in candidate and not any(e for e in must_exit if avail.combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped candidates.append(candidate) cave = random.choice(candidates) @@ -1436,10 +1438,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if len(cave) == 2: entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit - and (not avail.swapped or rnd_cave[0] != combine_map[e])) + and (not avail.swapped or rnd_cave[0] != avail.combine_map[e])) entrances.remove(entrance) connect_two_way(entrance, rnd_cave[0], avail) - if avail.swapped and combine_map[entrance] != rnd_cave[0]: + if avail.swapped and avail.combine_map[entrance] != rnd_cave[0]: swap_ent, _ = connect_cave_swap(entrance, rnd_cave[0], cave) entrances.remove(swap_ent) if cave in used_caves: @@ -1460,11 +1462,11 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): cave_entrances.append(entrance) else: entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit - and (not avail.swapped or cave_exit != combine_map[e])) + and (not avail.swapped or cave_exit != avail.combine_map[e])) cave_entrances.append(entrance) entrances.remove(entrance) connect_two_way(entrance, cave_exit, avail) - if avail.swapped and combine_map[entrance] != cave_exit: + if avail.swapped and avail.combine_map[entrance] != cave_exit: swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) entrances.remove(swap_ent) if entrance not in invalid_connections: @@ -1491,11 +1493,11 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): continue else: entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)] - and (not avail.swapped or cave_exit != combine_map[e])) + and (not avail.swapped or cave_exit != avail.combine_map[e])) invalid_cave_connections[tuple(cave)] = set() entrances.remove(entrance) connect_two_way(entrance, cave_exit, avail) - if avail.swapped and combine_map[entrance] != cave_exit: + if avail.swapped and avail.combine_map[entrance] != cave_exit: swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) entrances.remove(swap_ent) cave_options.remove(cave) @@ -1613,11 +1615,11 @@ def connect_swapped(entrancelist, targetlist, avail, two_way=False): random.shuffle(entrancelist) sorted_targets = list() for ent in entrancelist: - if ent in combine_map: - if combine_map[ent] not in targetlist: - logging.getLogger('').error(f'{combine_map[ent]} not in target list, cannot swap entrance') - raise Exception(f'{combine_map[ent]} not in target list, cannot swap entrance') - sorted_targets.append(combine_map[ent]) + if ent in avail.combine_map: + if avail.combine_map[ent] not in targetlist: + logging.getLogger('').error(f'{avail.combine_map[ent]} not in target list, cannot swap entrance') + raise Exception(f'{avail.combine_map[ent]} not in target list, cannot swap entrance') + sorted_targets.append(avail.combine_map[ent]) if len(sorted_targets): targetlist = list(sorted_targets) else: @@ -1638,9 +1640,9 @@ def connect_swapped(entrancelist, targetlist, avail, two_way=False): def connect_swap(entrance, exit, avail): - swap_exit = combine_map[entrance] + swap_exit = avail.combine_map[entrance] if swap_exit != exit: - swap_entrance = next(e for e, x in combine_map.items() if x == exit) + swap_entrance = next(e for e, x in avail.combine_map.items() if x == exit) if swap_entrance in ['Pyramid Entrance', 'Pyramid Hole'] and avail.world.is_tile_swapped(0x1b, avail.player): swap_entrance = 'Inverted ' + swap_entrance if entrance in entrance_map: @@ -2456,7 +2458,6 @@ single_entrance_map = { 'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing' } -combine_map = {**entrance_map, **single_entrance_map, **drop_map} combine_linked_drop_map = {**linked_drop_map, **sw_linked_drop_map} LW_Entrances = [] From 893947f89b730c329ec99e3af013ac4da347e94b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 26 Jan 2024 22:36:30 -0600 Subject: [PATCH 117/123] Added new SFX instrument shuffle option --- Adjuster.py | 5 +- AdjusterMain.py | 4 +- CLI.py | 5 +- Main.py | 3 +- Rom.py | 7 +- mystery_example.yml | 3 + mystery_testsuite.yml | 3 + resources/app/cli/args.json | 4 + resources/app/cli/lang/en.json | 1 + .../app/gui/adjust/overview/widgets.json | 1 + resources/app/gui/lang/en.json | 2 + .../gui/randomize/gameoptions/widgets.json | 1 + source/classes/CustomSettings.py | 1 + source/classes/SFX.py | 1483 ++++++++++------- source/classes/constants.py | 1 + source/gui/adjust/overview.py | 2 + source/gui/bottom.py | 1 + source/gui/loadcliargs.py | 1 + source/tools/MysteryUtils.py | 1 + 19 files changed, 882 insertions(+), 647 deletions(-) diff --git a/Adjuster.py b/Adjuster.py index 45cf37d3..68ac73ad 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -36,8 +36,9 @@ def main(): parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true') - parser.add_argument('--shuffle_sfx', help='Shuffles sfx instruments', action='store_true') - parser.add_argument('--shuffle_songinstruments', help='Shuffles sound sfx', action='store_true') + parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true') + parser.add_argument('--shuffle_sfxinstruments', help='Shuffles sound instruments', action='store_true') + parser.add_argument('--shuffle_songinstruments', help='Shuffles song instruments', action='store_true') parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in diff --git a/AdjusterMain.py b/AdjusterMain.py index 2bb071f3..803bed24 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -33,7 +33,7 @@ def adjust(args): apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, - args.shuffle_songinstruments, args.msu_resume) + args.shuffle_sfxinstruments, args.shuffle_songinstruments, args.msu_resume) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) @@ -68,7 +68,7 @@ def patch(args): apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, - args.shuffle_songinstruments, args.msu_resume) + args.shuffle_sfxinstruments, args.shuffle_songinstruments, args.msu_resume) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfile_base)) diff --git a/CLI.py b/CLI.py index 92decc8e..b54c667a 100644 --- a/CLI.py +++ b/CLI.py @@ -143,8 +143,8 @@ def parse_cli(argv, no_defaults=False): 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', - 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_songinstruments', - 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', + 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_sfxinstruments', + 'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'aga_randomness']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: @@ -262,6 +262,7 @@ def parse_settings(): "uw_palettes": "default", "reduce_flashing": False, "shuffle_sfx": False, + "shuffle_sfxinstruments": False, "shuffle_songinstruments": False, "msu_resume": False, "collection_rate": False, diff --git a/Main.py b/Main.py index ba575006..ae47b522 100644 --- a/Main.py +++ b/Main.py @@ -338,7 +338,8 @@ def main(args, seed=None, fish=None): apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player], - args.shuffle_sfx[player], args.shuffle_songinstruments[player], args.msu_resume[player]) + args.shuffle_sfx[player], args.shuffle_sfxinstruments[player], args.shuffle_songinstruments[player], + args.msu_resume[player]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches diff --git a/Rom.py b/Rom.py index cfa3fdcd..ec8dfb47 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from EntranceShuffle import door_addresses, exit_ids, ow_prize_table from OverworldShuffle import default_flute_connections, flute_data from InitialSram import InitialSram -from source.classes.SFX import randomize_sfx, randomize_songinstruments +from source.classes.SFX import randomize_sfx, randomize_sfxinstruments, randomize_songinstruments from source.item.FillUtil import valid_pot_items from source.dungeon.RoomList import Room0127 @@ -1830,7 +1830,8 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, - ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, shuffle_songinstruments, msu_resume): + ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, + shuffle_sfxinstruments, shuffle_songinstruments, msu_resume): if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer: dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380], @@ -1936,6 +1937,8 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr if shuffle_sfx: randomize_sfx(rom) + if shuffle_sfxinstruments: + randomize_sfxinstruments(rom) if shuffle_songinstruments: randomize_songinstruments(rom) diff --git a/mystery_example.yml b/mystery_example.yml index 4cf785b0..9fc7951e 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -269,6 +269,9 @@ shuffle_sfx: on: 1 off: 1 + shuffle_sfxinstruments: + on: 1 + off: 1 shuffle_songinstruments: on: 1 off: 1 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index 733adb6e..7a2ad91c 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -199,6 +199,9 @@ rom: shuffle_sfx: on: 1 off: 1 + shuffle_sfxinstruments: + on: 1 + off: 1 shuffle_songinstruments: on: 1 off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index ebe1181b..85d8851b 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -349,6 +349,10 @@ "action": "store_true", "type": "bool" }, + "shuffle_sfxinstruments": { + "action": "store_true", + "type": "bool" + }, "shuffle_songinstruments": { "action": "store_true", "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index db988393..eec33d7e 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -429,6 +429,7 @@ ], "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], + "shuffle_sfxinstruments": [ "Shuffle sound instruments (default: %(default)s)" ], "shuffle_songinstruments": [ "Shuffle song instruments (default: %(default)s)" ], "msu_resume": [ "Enable MSU Resume (default: %(default)s)" ], "create_rom": [ "Create an output rom file. (default: %(default)s)" ], diff --git a/resources/app/gui/adjust/overview/widgets.json b/resources/app/gui/adjust/overview/widgets.json index 8489bcda..bb4512c2 100644 --- a/resources/app/gui/adjust/overview/widgets.json +++ b/resources/app/gui/adjust/overview/widgets.json @@ -5,6 +5,7 @@ "quickswap": { "type": "checkbox" }, "reduce_flashing": {"type": "checkbox" }, "shuffle_sfx": {"type": "checkbox" }, + "shuffle_sfxinstruments": {"type": "checkbox" }, "shuffle_songinstruments": {"type": "checkbox" } }, "leftAdjustFrame": { diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 02bb4490..b069e129 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -4,6 +4,7 @@ "adjust.quickswap": "L/R Quickswapping", "adjust.reduce_flashing": "Reduce Flashing", "adjust.shuffle_sfx": "Shuffle Sound Effects", + "adjust.shuffle_sfxinstruments": "Shuffle Sound Instruments", "adjust.shuffle_songinstruments": "Shuffle Song Instruments", "adjust.msu_resume": "MSU Resume", @@ -192,6 +193,7 @@ "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", "randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects", + "randomizer.gameoptions.shuffle_sfxinstruments": "Shuffle Sound Instruments", "randomizer.gameoptions.shuffle_songinstruments": "Shuffle Song Instruments", "randomizer.gameoptions.msu_resume": "MSU Resume", diff --git a/resources/app/gui/randomize/gameoptions/widgets.json b/resources/app/gui/randomize/gameoptions/widgets.json index d6f4e76c..a35d1e4f 100644 --- a/resources/app/gui/randomize/gameoptions/widgets.json +++ b/resources/app/gui/randomize/gameoptions/widgets.json @@ -5,6 +5,7 @@ "quickswap": { "type": "checkbox" }, "reduce_flashing": { "type": "checkbox" }, "shuffle_sfx": { "type": "checkbox" }, + "shuffle_sfxinstruments": { "type": "checkbox" }, "shuffle_songinstruments": { "type": "checkbox" } }, "leftRomOptionsFrame": { diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 0cf93eb3..c38ae024 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -186,6 +186,7 @@ class CustomSettings(object): args.ow_palettes[p] = get_setting(settings['ow_palettes'], args.ow_palettes[p]) args.uw_palettes[p] = get_setting(settings['uw_palettes'], args.uw_palettes[p]) args.shuffle_sfx[p] = get_setting(settings['shuffle_sfx'], args.shuffle_sfx[p]) + args.shuffle_sfxinstruments[p] = get_setting(settings['shuffle_sfxinstruments'], args.shuffle_sfxinstruments[p]) args.shuffle_songinstruments[p] = get_setting(settings['shuffle_songinstruments'], args.shuffle_songinstruments[p]) args.msu_resume[p] = get_setting(settings['msu_resume'], args.msu_resume[p]) diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 825adef4..2998aacc 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -16,13 +16,24 @@ class SFX(object): self.target_id = None self.target_chain = None -class MusicType(IntEnum): +class SPCMusicType(IntEnum): NONE = 0x00, Ambient = 0x01, Melody = 0x02, Rhythm = 0x04, Beat = 0x08 +class SFXType(IntEnum): + NONE = 0x00, + Ambient = 0x01, + Melody = 0x02, + Beat = 0x04, + Short = 0x08, + Long = 0x10, + Soft = 0x20, + Hard = 0x40, + ALL = 0xFF + class Instrument(object): def __init__(self, name, id, srcn, adsr, gain, mult): self.name = name @@ -32,51 +43,63 @@ class Instrument(object): self.gain = gain self.mult = mult + self.primary_type = 0 + self.target_id = None + self.replacements = [] + + def add_type(self, type, force_primary): + if force_primary or self.type == 0: + self.primary_type |= type + self.type |= type + return self class SFXInstrument(Instrument): - pass + def __init__(self, name, id, srcn, adsr, gain, mult): + Instrument.__init__(self, name, id, srcn, adsr, gain, mult) + + self.type = SFXType.NONE + + def amb(self, force_primary=False): return self.add_type(SFXType.Ambient, force_primary) + def mel(self, force_primary=False): return self.add_type(SFXType.Melody, force_primary) + def beat(self, force_primary=False): return self.add_type(SFXType.Beat, force_primary) + def short(self, force_primary=False): return self.add_type(SFXType.Short, force_primary) + def long(self, force_primary=False): return self.add_type(SFXType.Long, force_primary) + def soft(self, force_primary=False): return self.add_type(SFXType.Soft, force_primary) + def hard(self, force_primary=False): return self.add_type(SFXType.Hard, force_primary) class SPCInstrument(Instrument): def __init__(self, name, srcn, adsr, gain, mult, min=9999, max=0): Instrument.__init__(self, name, srcn, srcn, adsr, gain, mult) - self.type = MusicType.NONE - self.primary_type = MusicType.NONE - self.replacements = [] + self.type = SPCMusicType.NONE - def add_type(self, type, force_primary): - if force_primary or self.type == MusicType.NONE: - self.primary_type |= type - self.type |= type - - def amb(self, force_primary=False): - self.add_type(MusicType.Ambient, force_primary) - return self - - def mel(self, force_primary=False): - self.add_type(MusicType.Melody, force_primary) - return self - - def bass(self, force_primary=False): - self.add_type(MusicType.Rhythm, force_primary) - return self - - def beat(self, force_primary=False): - self.add_type(MusicType.Beat, force_primary) - return self + def amb(self, force_primary=False): return self.add_type(SPCMusicType.Ambient, force_primary) + def mel(self, force_primary=False): return self.add_type(SPCMusicType.Melody, force_primary) + def bass(self, force_primary=False): return self.add_type(SPCMusicType.Rhythm, force_primary) + def beat(self, force_primary=False): return self.add_type(SPCMusicType.Beat, force_primary) class InstrumentChange(object): - def __init__(self, song_id, segment_id, tracks, orig_instrument, type=MusicType.NONE, ban=[]): + def __init__(self, orig_instrument, type, ban=[], inc=[]): + self.orig_instrument = orig_instrument + self.type = type + self.banned_list = ban + self.include_list = inc + self.target_instrument = None + +class SFXInstrumentChange(InstrumentChange): + def __init__(self, sfx_set, sfx_id, orig_instrument, addresses, type=SFXType.Ambient | SFXType.Melody | SFXType.Beat, ban=[], inc=[]): + InstrumentChange.__init__(self, orig_instrument, type, ban, inc) + self.sfx_set = sfx_set + self.sfx_id = sfx_id + self.addresses = addresses + +class SPCInstrumentChange(InstrumentChange): + def __init__(self, song_id, segment_id, tracks, orig_instrument, type=SPCMusicType.NONE, ban=[], inc=[]): + InstrumentChange.__init__(self, orig_instrument, type, ban, inc) self.song_id = song_id self.segment_id = segment_id self.tracks = tracks - self.orig_instrument = orig_instrument - - self.type = type - self.banned_list = ban - - self.target_instrument = None def init_sfx_data(): @@ -246,56 +269,99 @@ def output_song_data(rom, filepath, outfilebase): last_song = 0 last_segment = -1 outfile.write(f'{outfilebase}\n') - for change in spc_instrument_changes: - if last_song != change.song_id: - last_song = change.song_id - last_segment = change.segment_id - outfile.write(f'\nSong{change.song_id:02X}.{change.segment_id:02X}.') - elif last_segment != change.segment_id: - last_segment = change.segment_id - outfile.write(f'\n {change.segment_id:02X}.') - else: - outfile.write(f'\n ') - tracks = 8 - for track_id in change.tracks.keys(): - outfile.write(f'{track_id:01X}') - tracks -= 1 - if tracks > 0: - outfile.write(' ' * tracks) - outfile.write(f' = {rom.read_byte(snes_to_pc(next(iter(change.tracks.values()))[0])):02X}') + if "SFX" in filepath: + for change in sfx_instrument_changes: + outfile.write(f'\nSFX{change.sfx_set:1d}.{change.sfx_id:02X} = {rom.read_byte(snes_to_pc(change.addresses[0])):02X}') + else: + for change in spc_instrument_changes: + if last_song != change.song_id: + last_song = change.song_id + last_segment = change.segment_id + outfile.write(f'\nSong{change.song_id:02X}.{change.segment_id:02X}.') + elif last_segment != change.segment_id: + last_segment = change.segment_id + outfile.write(f'\n {change.segment_id:02X}.') + else: + outfile.write(f'\n ') + tracks = 8 + for track_id in change.tracks.keys(): + outfile.write(f'{track_id:01X}') + tracks -= 1 + if tracks > 0: + outfile.write(' ' * tracks) + outfile.write(f' = {rom.read_byte(snes_to_pc(next(iter(change.tracks.values()))[0])):02X}') +def prep_instruments(instruments, types): + for instrument in instruments.values(): + for ins, lst in types.items(): + if instrument.type & ins > 0: + lst.append(instrument) + if hasattr(instruments[0], 'primary_type'): + for instrument in instruments.values(): + for ins, lst in types.items(): + if instrument.primary_type & ins > 0: + instrument.replacements += [i for i in lst if i not in instrument.replacements] + +def shuffle_instruments(instrument_changes, instruments, types, match_patterns=[0xFF]): + # randomize each instrument change + for change in instrument_changes: + if type(change.type) is list: + candidates = [instruments[i] for i in change.type] + elif change.type != 0: + candidates = [] + for n, match in enumerate(match_patterns): + cand = [] + for ins, lst in types.items(): + if match & ins > 0 and (change.type & ins > 0 or (n == 0 and change.type & match == 0)): + cand += [i for i in lst if i not in cand] + if n == 0: + if not len(cand): + for ins, lst in types.items(): + if match & ins > 0: + cand += [i for i in lst if i not in cand] + candidates = cand.copy() + elif len(cand): + candidates = [i for i in cand if i in candidates] + else: + candidates = instruments[change.orig_instrument].replacements + candidates += [instruments[i] for i in change.include_list if instruments[i] not in candidates] + candidates = [i for i in candidates if i.id not in change.banned_list] + change.target_instrument = random.choice(candidates).id + +def randomize_sfxinstruments(rom): + # categorize instruments in pools + ambients, melodies, beats, shorts, longs, softs, hards = [], [], [], [], [], [], [] + inst_lists = { + SFXType.Ambient: ambients, + SFXType.Melody: melodies, + SFXType.Beat: beats, + SFXType.Short: shorts, + SFXType.Long: longs, + SFXType.Soft: softs, + SFXType.Hard: hards, + } + match_patterns = [SFXType.Ambient | SFXType.Melody | SFXType.Beat, + SFXType.Short | SFXType.Long, + SFXType.Soft | SFXType.Hard] + prep_instruments(sfx_instruments, inst_lists) + shuffle_instruments(sfx_instrument_changes, sfx_instruments, inst_lists, match_patterns) + + for change in sfx_instrument_changes: + for addr in change.addresses: + rom.write_byte(snes_to_pc(addr), change.target_instrument) + def randomize_songinstruments(rom): # categorize instruments in pools ambients, melodies, rhythms, beats = [], [], [], [] inst_lists = { - MusicType.Ambient: ambients, - MusicType.Melody: melodies, - MusicType.Rhythm: rhythms, - MusicType.Beat: beats + SPCMusicType.Ambient: ambients, + SPCMusicType.Melody: melodies, + SPCMusicType.Rhythm: rhythms, + SPCMusicType.Beat: beats } - for instrument in spc_instruments.values(): - for ins, lst in inst_lists.items(): - if instrument.type & ins > 0: - lst.append(instrument) - for instrument in spc_instruments.values(): - for ins, lst in inst_lists.items(): - if instrument.primary_type & ins > 0: - instrument.replacements += [i for i in lst if i not in instrument.replacements] - - # randomize each instrument change - for change in spc_instrument_changes: - if type(change.type) is list: - candidates = [spc_instruments[i] for i in change.type] - elif change.type != MusicType.NONE: - candidates = [] - for ins, lst in inst_lists.items(): - if change.type & ins > 0: - candidates += [i for i in lst if i not in candidates] - else: - candidates = spc_instruments[change.orig_instrument].replacements - candidates = [i for i in candidates if i.id not in change.banned_list] - change.target_instrument = random.choice(candidates).id + prep_instruments(spc_instruments, inst_lists) + shuffle_instruments(spc_instrument_changes, spc_instruments, inst_lists) for change in spc_instrument_changes: for track_addresses in change.tracks.values(): @@ -304,31 +370,31 @@ def randomize_songinstruments(rom): sfx_instruments = { # table @ $1A9C04 - 0x00: SFXInstrument("Fwoosh", 0x00, 0x00, [0xF6, 0x6A], 0xB8, 0x03), - 0x01: SFXInstrument("Swish", 0x01, 0x01, [0x8E, 0xE0], 0xB8, 0x02), - 0x02: SFXInstrument("Bomp", 0x02, 0x14, [0xFE, 0x6A], 0xB8, 0x02), - 0x03: SFXInstrument("Ting", 0x03, 0x03, [0xFE, 0xF8], 0xB8, 0x0D), - 0x04: SFXInstrument("Rrrrr", 0x04, 0x04, [0xFE, 0x6A], 0x7F, 0x03), - 0x05: SFXInstrument("Clunk", 0x05, 0x02, [0xFE, 0x6A], 0x7F, 0x03), - 0x06: SFXInstrument("Ching", 0x06, 0x05, [0xFE, 0x6A], 0x70, 0x03), - 0x07: SFXInstrument("Fwomp", 0x07, 0x06, [0xFE, 0x6A], 0x70, 0x03), - 0x08: SFXInstrument("Squee", 0x08, 0x08, [0xFA, 0x6A], 0x70, 0x03), - 0x09: SFXInstrument("Unused", 0x09, 0x06, [0xFE, 0x6A], 0x70, 0x01), - 0x0A: SFXInstrument("Bzzzrt", 0x0A, 0x07, [0xFE, 0x6A], 0x70, 0x05), - 0x0B: SFXInstrument("Brrfft", 0x0B, 0x0B, [0xFE, 0x6A], 0xB8, 0x03), - 0x0C: SFXInstrument("Brrwwww", 0x0C, 0x0C, [0xFE, 0xE0], 0xB8, 0x02), - 0x0D: SFXInstrument("Twee", 0x0D, 0x0D, [0xF9, 0x6E], 0xB8, 0x03), - 0x0E: SFXInstrument("Pwing", 0x0E, 0x0E, [0xFE, 0xF5], 0xB8, 0x07), - 0x0F: SFXInstrument("Pling", 0x0F, 0x0F, [0xFE, 0xF5], 0xB8, 0x06), - 0x10: SFXInstrument("Chshtsh", 0x10, 0x01, [0xFE, 0xFC], 0xB8, 0x03), - 0x11: SFXInstrument("Splssh", 0x11, 0x10, [0x8E, 0xE0], 0xB8, 0x03), - 0x12: SFXInstrument("Weewoo", 0x12, 0x08, [0x8E, 0xE0], 0xB8, 0x02), - 0x13: SFXInstrument("Brbrbrb", 0x13, 0x14, [0x8E, 0xE0], 0xB8, 0x02), - 0x14: SFXInstrument("Bwow", 0x14, 0x0A, [0x88, 0xE0], 0xB8, 0x02), - 0x15: SFXInstrument("Uughf", 0x15, 0x17, [0x8E, 0xE0], 0xB8, 0x02), - 0x16: SFXInstrument("Aaaaaa", 0x16, 0x15, [0xFF, 0xE0], 0xB8, 0x04), - 0x17: SFXInstrument("Twing", 0x17, 0x03, [0xDF, 0x11], 0xB8, 0x0F), - 0x18: SFXInstrument("Whooo", 0x18, 0x01, [0x88, 0xE0], 0xB8, 0x01) + 0x00: SFXInstrument("Fwoosh", 0x00, 0x00, [0xF6, 0x6A], 0xB8, 0x03).amb().long().soft(), + 0x01: SFXInstrument("Swish", 0x01, 0x01, [0x8E, 0xE0], 0xB8, 0x02).amb().long().soft(), + 0x02: SFXInstrument("Bomp", 0x02, 0x14, [0xFE, 0x6A], 0xB8, 0x02).beat().short().hard(), + 0x03: SFXInstrument("Ting", 0x03, 0x03, [0xFE, 0xF8], 0xB8, 0x0D).beat().short().hard(), + 0x04: SFXInstrument("Rrrrr", 0x04, 0x04, [0xFE, 0x6A], 0x7F, 0x03).mel().long().hard(), + 0x05: SFXInstrument("Clunk", 0x05, 0x02, [0xFE, 0x6A], 0x7F, 0x03).beat().short().hard(), + 0x06: SFXInstrument("Ching", 0x06, 0x05, [0xFE, 0x6A], 0x70, 0x03).amb().long().hard(), + 0x07: SFXInstrument("Fwomp", 0x07, 0x06, [0xFE, 0x6A], 0x70, 0x03).mel().long().hard(), + 0x08: SFXInstrument("Squee", 0x08, 0x08, [0xFA, 0x6A], 0x70, 0x03).mel().short().soft(), + 0x09: SFXInstrument("Unused", 0x09, 0x06, [0xFE, 0x6A], 0x70, 0x01).mel().long().hard(), + 0x0A: SFXInstrument("Bzzzrt", 0x0A, 0x07, [0xFE, 0x6A], 0x70, 0x05).mel().long().hard(), + 0x0B: SFXInstrument("Brrfft", 0x0B, 0x0B, [0xFE, 0x6A], 0xB8, 0x03).mel().long().hard(), + 0x0C: SFXInstrument("Brrwwww", 0x0C, 0x0C, [0xFE, 0xE0], 0xB8, 0x02).beat().short().hard(), + 0x0D: SFXInstrument("Twee", 0x0D, 0x0D, [0xF9, 0x6E], 0xB8, 0x03).mel().long().hard(), + 0x0E: SFXInstrument("Pwing", 0x0E, 0x0E, [0xFE, 0xF5], 0xB8, 0x07).mel().short().hard(),#?on melody since flute song has a bad note + 0x0F: SFXInstrument("Pling", 0x0F, 0x0F, [0xFE, 0xF5], 0xB8, 0x06).mel().short().hard(), + 0x10: SFXInstrument("Chshtsh", 0x10, 0x01, [0xFE, 0xFC], 0xB8, 0x03).beat().short().soft(), + 0x11: SFXInstrument("Splssh", 0x11, 0x10, [0x8E, 0xE0], 0xB8, 0x03).amb().short().soft(), + 0x12: SFXInstrument("Weewoo", 0x12, 0x08, [0x8E, 0xE0], 0xB8, 0x02).mel().short().soft(), + 0x13: SFXInstrument("Brbrbrb", 0x13, 0x14, [0x8E, 0xE0], 0xB8, 0x02).amb().beat().short().hard(), + 0x14: SFXInstrument("Bwow", 0x14, 0x0A, [0x88, 0xE0], 0xB8, 0x02).mel().long().hard(), + 0x15: SFXInstrument("Uughf", 0x15, 0x17, [0x8E, 0xE0], 0xB8, 0x02).mel().short().hard(), + 0x16: SFXInstrument("Aaaaaa", 0x16, 0x15, [0xFF, 0xE0], 0xB8, 0x04).mel().long().hard(), + 0x17: SFXInstrument("Twing", 0x17, 0x03, [0xDF, 0x11], 0xB8, 0x0F).beat().short().hard(), + 0x18: SFXInstrument("Whooo", 0x18, 0x01, [0x88, 0xE0], 0xB8, 0x01).amb().long().soft() } spc_instruments = { # table @ $19FB1C @@ -359,575 +425,716 @@ spc_instruments = { # table @ $19FB1C 0x18: SPCInstrument("Piano", 0x18, [0xFE, 0x8F], 0xB8, 0x06F0).mel().bass(True) } -Me = MusicType.Melody -Rh = MusicType.Rhythm -Be = MusicType.Beat -Am = MusicType.Ambient +Am = SFXType.Ambient +Me = SFXType.Melody +Be = SFXType.Beat +Sf = SFXType.Soft +Hd = SFXType.Hard +Sh = SFXType.Short +Lg = SFXType.Long + +sfx_instrument_changes = [ + SFXInstrumentChange(0x01, 0x01, 0x01, [0x1A9A03, 0x1A9A13], type=Am, ban=[0x00, 0x06, 0x11]), # accompanied $01+02 + SFXInstrumentChange(0x01, 0x03, 0x01, [0x1A9A28, 0x1A9A38], type=Am, ban=[0x00, 0x06, 0x11]), # accompanied $03+04 + SFXInstrumentChange(0x01, 0x07, 0x13, [0x1A9AEA], type=Am|Be|Hd, inc=[0x08, 0x12]), + SFXInstrumentChange(0x01, 0x09, 0x18, [0x1A903F, 0x1A906D], type=Am, ban=[0x00, 0x06, 0x11]), # accompanied $09+0A + SFXInstrumentChange(0x01, 0x0B, 0x0D, [0x1A8F54, 0x1A8F13], type=Me, ban=[0x0F]), # accompanied $0B+0C + SFXInstrumentChange(0x01, 0x0D, 0x0E, [0x1A8EBF, 0x1A8ECE], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), # accompanied $0D+0E + SFXInstrumentChange(0x01, 0x0F, 0x0E, [0x1A8EDD, 0x1A8EEF], ban=[0x06, 0x0C]), # accompanied $0F+10 + SFXInstrumentChange(0x01, 0x11, 0x16, [0x1A925D, 0x1A9279], type=Me|Be, ban=[0x02, 0x09, 0x0C, 0x13, 0x15]), # accompanied $11+12 + SFXInstrumentChange(0x01, 0x13, 0x00, [0x1A8E83, 0x1A8E92], ban=[0x10]), # accompanied $13+14 + SFXInstrumentChange(0x01, 0x15, 0x0A, [0x1A8EA1, 0x1A8EB0], ban=[0x09, 0x10, 0x15]), # accompanied $15+16 + SFXInstrumentChange(0x01, 0x17, 0x0D, [0x1A8FD5, 0x1A8F94], type=Me, ban=[0x0F]), # accompanied $17+18 + + SFXInstrumentChange(0x02, 0x01, 0x01, [0x1A99C5], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x02, 0x01, [0x1A99D6], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x03, 0x02, [0x1A99E5], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x04, 0x02, [0x1A99F4], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x05, 0x06, [0x1A998E], ban=[0x01, 0x03, 0x16, 0x17]), + SFXInstrumentChange(0x02, 0x06, 0x03, [0x1A9986], type=Hd, ban=[0x06, 0x09], inc=[0x12]), + SFXInstrumentChange(0x02, 0x08, 0x02, [0x1A9994], ban=[0x10, 0x18]), + SFXInstrumentChange(0x02, 0x08, 0x02, [0x1A999D], type=Me|Lg, inc=[0x02, 0x0C, 0x13]), + SFXInstrumentChange(0x02, 0x09, 0x00, [0x1A995E], ban=[0x08, 0x13]), + SFXInstrumentChange(0x02, 0x0A, 0x06, [0x1A9978], ban=[0x00, 0x03, 0x0C, 0x0E, 0x13, 0x14, 0x17, 0x18]), + SFXInstrumentChange(0x02, 0x0B, 0x02, [0x1A9829], type=Hd, ban=[0x09], inc=[0x12]), + SFXInstrumentChange(0x02, 0x0C, 0x13, [0x1A9A4D], type=Sh, ban=[0x10]), + SFXInstrumentChange(0x02, 0x0D, 0x00, [0x1A97C5], type=Am, ban=[0x13], inc=[0x10]), # accompanied $0D+3F + SFXInstrumentChange(0x02, 0x0E, 0x00, [0x1A97B5], type=Lg, ban=[0x09, 0x18], inc=[0x13]), + SFXInstrumentChange(0x02, 0x0F, 0x06, [0x1A9874], type=Lg, ban=[0x18]), + SFXInstrumentChange(0x02, 0x10, 0x05, [0x1A97AB], ban=[0x00, 0x09, 0x14, 0x18]), + SFXInstrumentChange(0x02, 0x11, 0x05, [0x1A97A1], ban=[0x00, 0x08, 0x10, 0x18]), + SFXInstrumentChange(0x02, 0x12, 0x02, [0x1A977E], ban=[0x00, 0x10]), + SFXInstrumentChange(0x02, 0x12, 0x01, [0x1A9787], ban=[0x10]), + SFXInstrumentChange(0x02, 0x13, 0x0D, [0x1A9751, 0x1A9766], type=Me, ban=[0x0F]), # accompanied $13+3E + SFXInstrumentChange(0x02, 0x14, 0x07, [0x1A9731], type=Hd, ban=[0x09]), + SFXInstrumentChange(0x02, 0x15, 0x07, [0x1A9741], type=Hd, ban=[0x09]), + SFXInstrumentChange(0x02, 0x16, 0x10, [0x1A96DD], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x17, 0x10, [0x1A96F5], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x18, 0x10, [0x1A9707], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x19, 0x10, [0x1A971F], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x1A, 0x01, [0x1A96C7], type=Am, ban=[0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1B, 0x11, [0x1A96B8], type=Am, ban=[0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1C, 0x11, [0x1A96B2], type=Am, ban=[0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1D, 0x16, [0x1A966C], ban=[0x09, 0x10, 0x11, 0x13, 0x15, 0x18]), + SFXInstrumentChange(0x02, 0x1E, 0x01, [0x1A9928], type=Lg, ban=[0x06]), + SFXInstrumentChange(0x02, 0x1F, 0x02, [0x1A969A], type=Me|Be|Hd, ban=[0x09]), + SFXInstrumentChange(0x02, 0x1F, 0x01, [0x1A96A0], ban=[0x00, 0x06, 0x18]), + SFXInstrumentChange(0x02, 0x20, 0x0D, [0x1A968B], type=Me|Lg, ban=[0x09]), + SFXInstrumentChange(0x02, 0x21, 0x02, [0x1A9680], type=Be|Hd), + SFXInstrumentChange(0x02, 0x21, 0x01, [0x1A9685], type=Sf, ban=[0x18]), + SFXInstrumentChange(0x02, 0x22, 0x05, [0x1A948E, 0x1A94B8], type=[0x01, 0x05, 0x0E, 0x0F, 0x10, 0x17]), # chained $3-18->22 + SFXInstrumentChange(0x02, 0x23, 0x01, [0x1A9662], ban=[0x06, 0x0C, 0x0E, 0x13]), + SFXInstrumentChange(0x02, 0x24, 0x11, [0x1A9656, 0x1A965C], type=Me|Lg, ban=[0x09, 0x0D, 0x16], inc=[0x11]), # accompanied $24+3D + SFXInstrumentChange(0x02, 0x25, 0x11, [0x1A9647], ban=[0x00, 0x01, 0x06, 0x0C, 0x10]), + SFXInstrumentChange(0x02, 0x26, 0x15, [0x1A9BF5], type=Me|Hd, inc=[0x11]), + SFXInstrumentChange(0x02, 0x27, 0x0B, [0x1A9603], type=Me|Lg, inc=[0x0E, 0x17]), + SFXInstrumentChange(0x02, 0x27, 0x13, [0x1A9631], type=Hd, inc=[0x08, 0x11, 0x12]), + SFXInstrumentChange(0x02, 0x28, 0x11, [0x1A9638], type=[0x10, 0x11, 0x12]), + SFXInstrumentChange(0x02, 0x29, 0x01, [0x1A97F0], type=Sf), # accompanied $29+3B + SFXInstrumentChange(0x02, 0x2A, 0x0C, [0x1A93E4], type=Be, inc=[0x08, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x16]), + SFXInstrumentChange(0x02, 0x2B, 0x0E, [0x1A93A3], type=Me, ban=[0x08, 0x09, 0x0D], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x02, 0x2C, 0x00, [0x1A938A, 0x1A9398], type=Sh, ban=[0x13]), # chained $2C->3A + SFXInstrumentChange(0x02, 0x2C, 0x17, [0x1A9393, 0x1A939D], type=Me|Sh, ban=[0x0E], inc=[0x03, 0x11, 0x17]), # chained $2C->3A + SFXInstrumentChange(0x02, 0x2D, 0x0F, [0x1A9457], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x02, 0x2E, 0x11, [0x1A937B], type=Sh, ban=[0x03, 0x10, 0x15, 0x17]), # accompanied $2E+39 + SFXInstrumentChange(0x02, 0x2F, 0x01, [0x1A92F8], type=Lg|Sf, ban=[0x00]), # accompanied $2F+38 + SFXInstrumentChange(0x02, 0x30, 0x15, [0x1A92A2], type=Me, inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x02, 0x31, 0x0D, [0x1A947F], type=Me, ban=[0x0E], inc=[0x17]), + SFXInstrumentChange(0x02, 0x32, 0x01, [0x1A90F8], type=Lg, ban=[0x00, 0x06]), + SFXInstrumentChange(0x02, 0x33, 0x07, [0x1A908D], type=Me|Lg), + SFXInstrumentChange(0x02, 0x35, 0x13, [0x1A9018], type=Am|Be, ban=[0x00, 0x06, 0x18], inc=[0x08, 0x12]), + SFXInstrumentChange(0x02, 0x37, 0x0E, [0x1A8DF4], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x02, 0x38, 0x11, [0x1A9320], type=Me|Am, ban=[0x00, 0x09], inc=[0x17]), # accompanied $2F+38 + SFXInstrumentChange(0x02, 0x39, 0x01, [0x1A934D], type=Am|Lg, ban=[0x00]), # accompanied $2E+39 + SFXInstrumentChange(0x02, 0x3B, 0x06, [0x1A9813], type=Hd, ban=[0x0C, 0x13, 0x14]), # accompanied $29+3B + SFXInstrumentChange(0x02, 0x3C, 0x04, [0x1A8DE8], type=Me, ban=[0x09], inc=[0x17]), + SFXInstrumentChange(0x02, 0x3F, 0x0D, [0x1A97E6], type=Me|Be|Hd, inc=[0x12]), # accompanied $0D+3F + + SFXInstrumentChange(0x03, 0x01, 0x07, [0x1A8DC9], type=Me, ban=[0x0D, 0x0E, 0x14, 0x16], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x02, 0x01, [0x1A98FF], ban=[0x03, 0x06, 0x09, 0x17]), + SFXInstrumentChange(0x03, 0x03, 0x13, [0x1A95FB], type=Me|Be, ban=[0x03, 0x09, 0x10, 0x14, 0x16, 0x17]), + SFXInstrumentChange(0x03, 0x04, 0x12, [0x1A95BF], type=Me, ban=[0x09]), + SFXInstrumentChange(0x03, 0x04, 0x06, [0x1A95C7], ban=[0x00, 0x18]), + SFXInstrumentChange(0x03, 0x05, 0x00, [0x1A9968], ban=[0x09, 0x10, 0x18]), + SFXInstrumentChange(0x03, 0x06, 0x02, [0x1A95A6], type=Sh, ban=[0x0C, 0x0E, 0x10, 0x11]), + SFXInstrumentChange(0x03, 0x07, 0x0C, [0x1A95EE], type=Me|Be, ban=[0x02, 0x09, 0x10, 0x13]), + SFXInstrumentChange(0x03, 0x08, 0x0C, [0x1A9597], type=Me|Be, ban=[0x02, 0x09, 0x10, 0x13]), + SFXInstrumentChange(0x03, 0x09, 0x0C, [0x1A9572], type=Me|Be, ban=[0x09, 0x10]), + SFXInstrumentChange(0x03, 0x0A, 0x03, [0x1A955A], type=Me|Be, ban=[0x10, 0x17], inc=[0x11, 0x13]), + SFXInstrumentChange(0x03, 0x0B, 0x0B, [0x1A9549], type=Me|Be, ban=[0x02, 0x13], inc=[0x11]), + SFXInstrumentChange(0x03, 0x0C, 0x0D, [0x1A953F], type=Me, ban=[0x09, 0x0A, 0x0B, 0x14, 0x15], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x03, 0x0D, 0x0E, [0x1A9566], type=Me, ban=[0x14], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x03, 0x0E, 0x02, [0x1A9533], ban=[0x00, 0x08, 0x0C, 0x0D, 0x0E, 0x13, 0x14, 0x18]), + SFXInstrumentChange(0x03, 0x0F, 0x0B, [0x1A986A, 0x1A983B, 0x1A9845, 0x1A9831], type=[0x03, 0x04, 0x08, 0x0B, 0x0D, 0x0F, 0x12, 0x17]), # accompanied $0F+3C+3D+3E+3F + SFXInstrumentChange(0x03, 0x10, 0x0E, [0x1A951E, 0x1A9527], type=Sh|Hd, ban=[0x03]), # accompanied $10+3B + SFXInstrumentChange(0x03, 0x11, 0x07, [0x1A9500], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x12, 0x07, [0x1A950F], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x13, 0x0D, [0x1A94EC], type=Me, ban=[0x09, 0x0B], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x03, 0x14, 0x06, [0x1A981D], type=Hd, ban=[0x03, 0x09, 0x14], inc=[0x12]), + SFXInstrumentChange(0x03, 0x15, 0x13, [0x1A94E0], type=Hd, ban=[0x09, 0x14], inc=[0x12]), + SFXInstrumentChange(0x03, 0x16, 0x13, [0x1A94D4], type=Be, ban=[0x0C, 0x17]), + SFXInstrumentChange(0x03, 0x17, 0x08, [0x1A9957], type=Me, ban=[0x09, 0x0A, 0x0E]), + SFXInstrumentChange(0x03, 0x19, 0x07, [0x1A98BB], type=Me, ban=[0x08, 0x09], inc=[0x03, 0x05, 0x17]), + SFXInstrumentChange(0x03, 0x1A, 0x0F, [0x1A923B], type=Me|Lg, inc=[0x0F]), # accompanied $1A+38, but OWR + SFXInstrumentChange(0x03, 0x1B, 0x0E, [0x1A9467, 0x1A9471], type=Me, ban=[0x08]), # accompanied $1B+3A + SFXInstrumentChange(0x03, 0x1C, 0x0A, [0x1A8E13], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x1E, 0x01, [0x1A9442], ban=[0x03, 0x06, 0x0C, 0x0E]), + SFXInstrumentChange(0x03, 0x1F, 0x16, [0x1A93FC], type=Me|Lg, ban=[0x09]), + SFXInstrumentChange(0x03, 0x20, 0x0F, [0x1A9B1D], type=Me|Be|Hd), + SFXInstrumentChange(0x03, 0x21, 0x14, [0x1A9B93], type=Me, ban=[0x09, 0x0B, 0x15], inc=[0x17]), + SFXInstrumentChange(0x03, 0x22, 0x14, [0x1A9A80], type=Me|Lg, ban=[0x09, 0x0A]), + SFXInstrumentChange(0x03, 0x23, 0x01, [0x1A93B2], type=Am|Me|Lg, ban=[0x00], inc=[0x13]), # accompanied $23+39 + SFXInstrumentChange(0x03, 0x24, 0x0D, [0x1A93F4], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x25, 0x13, [0x1A924E], type=Am|Be, ban=[0x00, 0x06, 0x18], inc=[0x09]), + SFXInstrumentChange(0x03, 0x25, 0x06, [0x1A9254], type=Hd, ban=[0x02, 0x05, 0x09, 0x0C, 0x13, 0x14, 0x15]), + SFXInstrumentChange(0x03, 0x26, 0x00, [0x1A922C], type=Lg, ban=[0x06]), + SFXInstrumentChange(0x03, 0x27, 0x07, [0x1A91F1], type=Me|Hd), + SFXInstrumentChange(0x03, 0x28, 0x07, [0x1A9AA8], type=Me|Lg|Hd), + SFXInstrumentChange(0x03, 0x29, 0x07, [0x1A91D2], type=Me|Hd), + SFXInstrumentChange(0x03, 0x2A, 0x0A, [0x1A91C3], type=Me|Lg, inc=[0x03, 0x0E, 0x0F, 0x12, 0x17]), + SFXInstrumentChange(0x03, 0x2B, 0x0A, [0x1A91A4], ban=[0x06, 0x09, 0x18]), + SFXInstrumentChange(0x03, 0x2C, 0x0A, [0x1A9171], type=Me, ban=[0x09], inc=[0x17]), + SFXInstrumentChange(0x03, 0x2D, 0x0F, [0x1A915A, 0x1A9165], type=Me, ban=[0x08, 0x0E, 0x12]), # accompanied $2D+37 + SFXInstrumentChange(0x03, 0x2E, 0x0B, [0x1A910E, 0x1A9124, 0x1A9117], type=Me|Lg, ban=[0x14, 0x16]), # accompanied $2E+35+34 + SFXInstrumentChange(0x03, 0x2F, 0x0E, [0x1A9131, 0x1A9144], type=Me|Be|Hd, inc=[0x13]), # accompanied $2F+33 + SFXInstrumentChange(0x03, 0x30, 0x03, [0x1A8F04], type=Me|Be, ban=[0x0C, 0x13], inc=[0x11]), + SFXInstrumentChange(0x03, 0x31, 0x01, [0x1A8E7B], ban=[0x03, 0x06, 0x09, 0x0E, 0x17, 0x18]), + SFXInstrumentChange(0x03, 0x32, 0x04, [0x1A8E29], type=Me, ban=[0x08], inc=[0x03, 0x05, 0x17]), + SFXInstrumentChange(0x03, 0x36, 0x07, [0x1A8E58], type=Me|Lg, inc=[0x03, 0x0F, 0x17]), + # SFXInstrumentChange(0x03, 0x38, 0x0F, [0x1A9244]), # sound used for OWR function + SFXInstrumentChange(0x03, 0x39, 0x07, [0x1A93C8], type=Me|Be), # accompanied $23+39 + SFXInstrumentChange(0x03, 0x3E, 0x05, [0x1A984F], type=Be, ban=[0x02, 0x0C], inc=[0x0D, 0x0E, 0x0F]) +] + +Me = SPCMusicType.Melody +Rh = SPCMusicType.Rhythm +Be = SPCMusicType.Beat +Am = SPCMusicType.Ambient spc_instrument_changes = [ - InstrumentChange(0x01, 0x00, {0x00: [0x1A9F5B], - 0x01: [0x1A9F9D], - 0x02: [0x1A9FBB], - 0x03: [0x1A9FDA], - 0x04: [0x1A9FE8]}, 0x0F), - InstrumentChange(0x01, 0x01, {0x00: [0x1ACA1A], - 0x01: [0x1ACA39], - 0x02: [0x1ACA5E], - 0x07: [0x1ACC01]}, 0x0B), - InstrumentChange(0x01, 0x01, {0x03: [0x1ACAA3], - 0x04: [0x1ACAE2]}, 0x11), - InstrumentChange(0x01, 0x01, {0x05: [0x1ACB25, 0x1ACC78]}, 0x02), - InstrumentChange(0x01, 0x01, {0x05: [0x1ACB3A, 0x1ACC51], - 0x06: [0x1ACBA9, 0x1ACC7D]}, 0x13), - InstrumentChange(0x01, 0x01, {0x06: [0x1ACB94, 0x1ACCA3]}, 0x0C), + SPCInstrumentChange(0x01, 0x00, {0x00: [0x1A9F5B], + 0x01: [0x1A9F9D], + 0x02: [0x1A9FBB], + 0x03: [0x1A9FDA], + 0x04: [0x1A9FE8]}, 0x0F), + SPCInstrumentChange(0x01, 0x01, {0x00: [0x1ACA1A], + 0x01: [0x1ACA39], + 0x02: [0x1ACA5E], + 0x07: [0x1ACC01]}, 0x0B), + SPCInstrumentChange(0x01, 0x01, {0x03: [0x1ACAA3], + 0x04: [0x1ACAE2]}, 0x11), + SPCInstrumentChange(0x01, 0x01, {0x05: [0x1ACB25, 0x1ACC78]}, 0x02), + SPCInstrumentChange(0x01, 0x01, {0x05: [0x1ACB3A, 0x1ACC51], + 0x06: [0x1ACBA9, 0x1ACC7D]}, 0x13), + SPCInstrumentChange(0x01, 0x01, {0x06: [0x1ACB94, 0x1ACCA3]}, 0x0C), - InstrumentChange(0x02, 0x00, {0x00: [0x1AA04B], - 0x03: [0x1AA10E], - 0x04: [0x1AA143], - 0x07: [0x1AA1D1]}, 0x0B), - InstrumentChange(0x02, 0x00, {0x01: [0x1AA087], - 0x05: [0x1AA176]}, 0x11), - InstrumentChange(0x02, 0x00, {0x02: [0x1AA0CC], - 0x06: [0x1AA1BF]}, 0x13), - InstrumentChange(0x02, 0x00, {0x06: [0x1AA1C7]}, 0x0C), - InstrumentChange(0x02, 0x01, {0x02: [0x1AA27B]}, 0x13), - InstrumentChange(0x02, 0x01, {0x03: [0x1AA2A2]}, 0x0A), - InstrumentChange(0x02, 0x01, {0x04: [0x1AA2CD]}, 0x02), - InstrumentChange(0x02, 0x01, {0x05: [0x1AA2E0], - 0x07: [0x1AA34D]}, 0x0B), - InstrumentChange(0x02, 0x02, {0x00: [0x1AA5A8], - 0x05: [0x1AA449]}, 0x0B), - InstrumentChange(0x02, 0x02, {0x03: [0x1AA3FF], - 0x04: [0x1AA42A]}, 0x0A), - InstrumentChange(0x02, 0x02, {0x06: [0x1AA49E, 0x1AA4CB]}, 0x13), - InstrumentChange(0x02, 0x02, {0x06: [0x1AA4C0, 0x1AA4EA]}, 0x0C), - InstrumentChange(0x02, 0x02, {0x06: [0x1AA752]}, 0x02), + SPCInstrumentChange(0x02, 0x00, {0x00: [0x1AA04B], + 0x03: [0x1AA10E], + 0x04: [0x1AA143], + 0x07: [0x1AA1D1]}, 0x0B), + SPCInstrumentChange(0x02, 0x00, {0x01: [0x1AA087], + 0x05: [0x1AA176]}, 0x11), + SPCInstrumentChange(0x02, 0x00, {0x02: [0x1AA0CC], + 0x06: [0x1AA1BF]}, 0x13), + SPCInstrumentChange(0x02, 0x00, {0x06: [0x1AA1C7]}, 0x0C), + SPCInstrumentChange(0x02, 0x01, {0x02: [0x1AA27B]}, 0x13), + SPCInstrumentChange(0x02, 0x01, {0x03: [0x1AA2A2]}, 0x0A), + SPCInstrumentChange(0x02, 0x01, {0x04: [0x1AA2CD]}, 0x02), + SPCInstrumentChange(0x02, 0x01, {0x05: [0x1AA2E0], + 0x07: [0x1AA34D]}, 0x0B), + SPCInstrumentChange(0x02, 0x02, {0x00: [0x1AA5A8], + 0x05: [0x1AA449]}, 0x0B), + SPCInstrumentChange(0x02, 0x02, {0x03: [0x1AA3FF], + 0x04: [0x1AA42A]}, 0x0A), + SPCInstrumentChange(0x02, 0x02, {0x06: [0x1AA49E, 0x1AA4CB]}, 0x13), + SPCInstrumentChange(0x02, 0x02, {0x06: [0x1AA4C0, 0x1AA4EA]}, 0x0C), + SPCInstrumentChange(0x02, 0x02, {0x06: [0x1AA752]}, 0x02), - InstrumentChange(0x03, 0x00, {0x00: [0x1AA84A], - 0x01: [0x1AA864], - 0x03: [0x1AA885]}, 0x0A), - InstrumentChange(0x03, 0x01, {0x00: [0x1AA89E], - 0x01: [0x1AA8B8], - 0x03: [0x1AA8D9]}, 0x12), - InstrumentChange(0x03, 0x01, {0x02: [0x1AAB86], - 0x04: [0x1AA8F2], - 0x05: [0x1AA93C]}, 0x0A), + SPCInstrumentChange(0x03, 0x00, {0x00: [0x1AA84A], + 0x01: [0x1AA864], + 0x03: [0x1AA885]}, 0x0A), + SPCInstrumentChange(0x03, 0x01, {0x00: [0x1AA89E], + 0x01: [0x1AA8B8], + 0x03: [0x1AA8D9]}, 0x12), + SPCInstrumentChange(0x03, 0x01, {0x02: [0x1AAB86], + 0x04: [0x1AA8F2], + 0x05: [0x1AA93C]}, 0x0A), - InstrumentChange(0x04, 0x00, {0x00: [0x1AACA3], - 0x01: [0x1AACB1], - 0x02: [0x1AACC7]}, 0x12), - InstrumentChange(0x04, 0x01, {0x00: [0x1AABF5], - 0x01: [0x1AAC0B]}, 0x12), - InstrumentChange(0x04, 0x01, {0x02: [0x1AAC21]}, 0x12), - InstrumentChange(0x04, 0x02, {0x01: [0x1AAC55], - 0x02: [0x1AAC6B]}, 0x12), - InstrumentChange(0x04, 0x03, {0x00: [0x1AAD93], - 0x01: [0x1AACED], - 0x02: [0x1AAD07], - 0x03: [0x1AAD75], - 0x04: [0x1AADB1]}, 0x12), + SPCInstrumentChange(0x04, 0x00, {0x00: [0x1AACA3], + 0x01: [0x1AACB1], + 0x02: [0x1AACC7]}, 0x12), + SPCInstrumentChange(0x04, 0x01, {0x00: [0x1AABF5], + 0x01: [0x1AAC0B]}, 0x12), + SPCInstrumentChange(0x04, 0x01, {0x02: [0x1AAC21]}, 0x12), + SPCInstrumentChange(0x04, 0x02, {0x01: [0x1AAC55], + 0x02: [0x1AAC6B]}, 0x12), + SPCInstrumentChange(0x04, 0x03, {0x00: [0x1AAD93], + 0x01: [0x1AACED], + 0x02: [0x1AAD07], + 0x03: [0x1AAD75], + 0x04: [0x1AADB1]}, 0x12), - InstrumentChange(0x05, 0x00, {0x00: [0x1AAE3F], - 0x02: [0x1AAE72], - 0x03: [0x1AAEAD], - 0x07: [0x1AAF02]}, 0x0A), - InstrumentChange(0x05, 0x00, {0x01: [0x1AAE64]}, 0x09), - InstrumentChange(0x05, 0x00, {0x04: [0x1AAEE8]}, 0x16), - InstrumentChange(0x05, 0x01, {0x00: [0x1AB156], - 0x03: [0x1AAF48], - 0x04: [0x1AAF71], - 0x07: [0x1AB1D3]}, 0x0A), - InstrumentChange(0x05, 0x01, {0x02: [0x1AB186]}, 0x16), - InstrumentChange(0x05, 0x02, {0x00: [0x1AB088], - 0x03: [0x1AB0CF], - 0x04: [0x1AB0F8], - 0x07: [0x1AB11F]}, 0x0A), - InstrumentChange(0x05, 0x02, {0x02: [0x1AB0A7]}, 0x16), + SPCInstrumentChange(0x05, 0x00, {0x00: [0x1AAE3F], + 0x02: [0x1AAE72], + 0x03: [0x1AAEAD], + 0x07: [0x1AAF02]}, 0x0A), + SPCInstrumentChange(0x05, 0x00, {0x01: [0x1AAE64]}, 0x09), + SPCInstrumentChange(0x05, 0x00, {0x04: [0x1AAEE8]}, 0x16), + SPCInstrumentChange(0x05, 0x01, {0x00: [0x1AB156], + 0x03: [0x1AAF48], + 0x04: [0x1AAF71], + 0x07: [0x1AB1D3]}, 0x0A), + SPCInstrumentChange(0x05, 0x01, {0x02: [0x1AB186]}, 0x16), + SPCInstrumentChange(0x05, 0x02, {0x00: [0x1AB088], + 0x03: [0x1AB0CF], + 0x04: [0x1AB0F8], + 0x07: [0x1AB11F]}, 0x0A), + SPCInstrumentChange(0x05, 0x02, {0x02: [0x1AB0A7]}, 0x16), - InstrumentChange(0x06, 0x00, {0x00: [0x1AB338]}, 0x0A), - InstrumentChange(0x06, 0x01, {0x01: [0x1AB68F], - 0x02: [0x1AB69D], - 0x03: [0x1AB6B2], - 0x05: [0x1AB3D7]}, 0x0A), - InstrumentChange(0x06, 0x02, {0x00: [0x1AB622], - 0x01: [0x1AB63A], - 0x02: [0x1AB648], - 0x03: [0x1AB670], - 0x04: [0x1AB6C4], - 0x05: [0x1AB49D]}, 0x0A), - InstrumentChange(0x06, 0x03, {0x05: [0x1AB548]}, 0x0A), - InstrumentChange(0x06, 0x04, {0x00: [0x1AB722], - 0x01: [0x1AB739], - 0x02: [0x1AB745], - 0x03: [0x1AB759], - 0x04: [0x1AB765], - 0x05: [0x1AB5E3]}, 0x0A), + SPCInstrumentChange(0x06, 0x00, {0x00: [0x1AB338]}, 0x0A), + SPCInstrumentChange(0x06, 0x01, {0x01: [0x1AB68F], + 0x02: [0x1AB69D], + 0x03: [0x1AB6B2], + 0x05: [0x1AB3D7]}, 0x0A), + SPCInstrumentChange(0x06, 0x02, {0x00: [0x1AB622], + 0x01: [0x1AB63A], + 0x02: [0x1AB648], + 0x03: [0x1AB670], + 0x04: [0x1AB6C4], + 0x05: [0x1AB49D]}, 0x0A), + SPCInstrumentChange(0x06, 0x03, {0x05: [0x1AB548]}, 0x0A), + SPCInstrumentChange(0x06, 0x04, {0x00: [0x1AB722], + 0x01: [0x1AB739], + 0x02: [0x1AB745], + 0x03: [0x1AB759], + 0x04: [0x1AB765], + 0x05: [0x1AB5E3]}, 0x0A), - InstrumentChange(0x07, 0x00, {0x00: [0x1ABB1F]}, 0x0A), - InstrumentChange(0x07, 0x00, {0x01: [0x1ABB31], - 0x02: [0x1ABBE6], - 0x03: [0x1ABC0B]}, 0x09), - InstrumentChange(0x07, 0x00, {0x04: [0x1ABB53, 0x1AB8C9]}, 0x16), - InstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x01, 0x05, 0x06, 0x17]), - InstrumentChange(0x07, 0x01, {0x05: [0x1AB8EB]}, 0x0A), - InstrumentChange(0x07, 0x02, {0x04: [0x1AB981]}, 0x16), - InstrumentChange(0x07, 0x03, {0x02: [0x1ABC37], - 0x03: [0x1ABC66]}, 0x09), - InstrumentChange(0x07, 0x03, {0x04: [0x1ABA09]}, 0x16), - InstrumentChange(0x07, 0x03, {0x05: [0x1ABC8F]}, 0x0A), - InstrumentChange(0x07, 0x05, {0x02: [0x1ABC9D], - 0x03: [0x1ABCBA]}, 0x09), - InstrumentChange(0x07, 0x05, {0x05: [0x1ABCD1]}, 0x0A), - InstrumentChange(0x07, 0x06, {0x00: [0x1ABB72]}, 0x0A), - InstrumentChange(0x07, 0x06, {0x01: [0x1ABB83], - 0x02: [0x1ABB95], - 0x03: [0x1ABBAA]}, 0x09), - InstrumentChange(0x07, 0x06, {0x04: [0x1ABBBD]}, 0x16), - InstrumentChange(0x07, 0x06, {0x05: [0x1ABCE6]}, 0x0A), + SPCInstrumentChange(0x07, 0x00, {0x00: [0x1ABB1F]}, 0x0A), + SPCInstrumentChange(0x07, 0x00, {0x01: [0x1ABB31], + 0x02: [0x1ABBE6], + 0x03: [0x1ABC0B]}, 0x09), + SPCInstrumentChange(0x07, 0x00, {0x04: [0x1ABB53, 0x1AB8C9]}, 0x16), + SPCInstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x01, 0x05, 0x06, 0x17]), + SPCInstrumentChange(0x07, 0x01, {0x05: [0x1AB8EB]}, 0x0A), + SPCInstrumentChange(0x07, 0x02, {0x04: [0x1AB981]}, 0x16), + SPCInstrumentChange(0x07, 0x03, {0x02: [0x1ABC37], + 0x03: [0x1ABC66]}, 0x09), + SPCInstrumentChange(0x07, 0x03, {0x04: [0x1ABA09]}, 0x16), + SPCInstrumentChange(0x07, 0x03, {0x05: [0x1ABC8F]}, 0x0A), + SPCInstrumentChange(0x07, 0x05, {0x02: [0x1ABC9D], + 0x03: [0x1ABCBA]}, 0x09), + SPCInstrumentChange(0x07, 0x05, {0x05: [0x1ABCD1]}, 0x0A), + SPCInstrumentChange(0x07, 0x06, {0x00: [0x1ABB72]}, 0x0A), + SPCInstrumentChange(0x07, 0x06, {0x01: [0x1ABB83], + 0x02: [0x1ABB95], + 0x03: [0x1ABBAA]}, 0x09), + SPCInstrumentChange(0x07, 0x06, {0x04: [0x1ABBBD]}, 0x16), + SPCInstrumentChange(0x07, 0x06, {0x05: [0x1ABCE6]}, 0x0A), - InstrumentChange(0x08, 0x00, {0x00: [0x1ABD3A], - 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am, ban=[0x05]), - InstrumentChange(0x08, 0x00, {0x02: [0x1ABD70], - 0x05: [0x1ABE06]}, 0x0F), - InstrumentChange(0x08, 0x00, {0x03: [0x1ABDAC]}, 0x0A), - InstrumentChange(0x08, 0x00, {0x04: [0x1ABDC8]}, 0x01), - InstrumentChange(0x08, 0x00, {0x06: [0x1ABE3A]}, 0x09), + SPCInstrumentChange(0x08, 0x00, {0x00: [0x1ABD3A], + 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am, ban=[0x05]), + SPCInstrumentChange(0x08, 0x00, {0x02: [0x1ABD70], + 0x05: [0x1ABE06]}, 0x0F), + SPCInstrumentChange(0x08, 0x00, {0x03: [0x1ABDAC]}, 0x0A), + SPCInstrumentChange(0x08, 0x00, {0x04: [0x1ABDC8]}, 0x01), + SPCInstrumentChange(0x08, 0x00, {0x06: [0x1ABE3A]}, 0x09), - InstrumentChange(0x09, 0x00, {0x00: [0x1AC25A], - 0x05: [0x1AC28E]}, 0x0A), - InstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x01, {0x06: [0x1ABF43]}, 0x09), - InstrumentChange(0x09, 0x02, {0x00: [0x1AC450], - 0x05: [0x1AC56D], - 0x07: [0x1AC595]}, 0x0A), - InstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x02, {0x03: [0x1AC4B3], - 0x04: [0x1AC510]}, 0x11), - InstrumentChange(0x09, 0x02, {0x06: [0x1AC2E9]}, 0x09), - InstrumentChange(0x09, 0x03, {0x00: [0x1ABF63]}, 0x0A), - InstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x03, {0x03: [0x1ABFA4]}, 0x11), - InstrumentChange(0x09, 0x03, {0x05: [0x1AC01C]}, 0x16), - InstrumentChange(0x09, 0x04, {0x00: [0x1AC04D]}, 0x0A), - InstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x04, {0x02: [0x1AC5CF]}, 0x18), - InstrumentChange(0x09, 0x04, {0x03: [0x1AC085], - 0x04: [0x1AC5ED]}, 0x11), - InstrumentChange(0x09, 0x04, {0x05: [0x1AC137]}, 0x16), - InstrumentChange(0x09, 0x04, {0x06: [0x1AC146]}, 0x12), - InstrumentChange(0x09, 0x05, {0x00: [0x1AC178], - 0x07: [0x1AC229]}, 0x0A), - InstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x05, {0x02: [0x1AC19E]}, 0x18), - InstrumentChange(0x09, 0x05, {0x03: [0x1AC1D3], - 0x04: [0x1AC1F4]}, 0x12), - InstrumentChange(0x09, 0x06, {0x00: [0x1AC317], - 0x07: [0x1AC3ED]}, 0x0A), - InstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x06, {0x02: [0x1AC33A]}, 0x18), - InstrumentChange(0x09, 0x06, {0x03: [0x1AC36F], - 0x04: [0x1AC3A4], - 0x05: [0x1AC3D9]}, 0x12), - InstrumentChange(0x09, 0x07, {0x00: [0x1AC40A], - 0x05: [0x1AC43C]}, 0x0A), - InstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), - InstrumentChange(0x09, 0x07, {0x02: [0x1AC492]}, 0x18), - InstrumentChange(0x09, 0x07, {0x03: [0x1AC680], - 0x04: [0x1AC6C1]}, 0x11), + SPCInstrumentChange(0x09, 0x00, {0x00: [0x1AC25A], + 0x05: [0x1AC28E]}, 0x0A), + SPCInstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x01, {0x06: [0x1ABF43]}, 0x09), + SPCInstrumentChange(0x09, 0x02, {0x00: [0x1AC450], + 0x05: [0x1AC56D], + 0x07: [0x1AC595]}, 0x0A), + SPCInstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x02, {0x03: [0x1AC4B3], + 0x04: [0x1AC510]}, 0x11), + SPCInstrumentChange(0x09, 0x02, {0x06: [0x1AC2E9]}, 0x09), + SPCInstrumentChange(0x09, 0x03, {0x00: [0x1ABF63]}, 0x0A), + SPCInstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x03, {0x03: [0x1ABFA4]}, 0x11), + SPCInstrumentChange(0x09, 0x03, {0x05: [0x1AC01C]}, 0x16), + SPCInstrumentChange(0x09, 0x04, {0x00: [0x1AC04D]}, 0x0A), + SPCInstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x04, {0x02: [0x1AC5CF]}, 0x18), + SPCInstrumentChange(0x09, 0x04, {0x03: [0x1AC085], + 0x04: [0x1AC5ED]}, 0x11), + SPCInstrumentChange(0x09, 0x04, {0x05: [0x1AC137]}, 0x16), + SPCInstrumentChange(0x09, 0x04, {0x06: [0x1AC146]}, 0x12), + SPCInstrumentChange(0x09, 0x05, {0x00: [0x1AC178], + 0x07: [0x1AC229]}, 0x0A), + SPCInstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x05, {0x02: [0x1AC19E]}, 0x18), + SPCInstrumentChange(0x09, 0x05, {0x03: [0x1AC1D3], + 0x04: [0x1AC1F4]}, 0x12), + SPCInstrumentChange(0x09, 0x06, {0x00: [0x1AC317], + 0x07: [0x1AC3ED]}, 0x0A), + SPCInstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x06, {0x02: [0x1AC33A]}, 0x18), + SPCInstrumentChange(0x09, 0x06, {0x03: [0x1AC36F], + 0x04: [0x1AC3A4], + 0x05: [0x1AC3D9]}, 0x12), + SPCInstrumentChange(0x09, 0x07, {0x00: [0x1AC40A], + 0x05: [0x1AC43C]}, 0x0A), + SPCInstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x07, {0x02: [0x1AC492]}, 0x18), + SPCInstrumentChange(0x09, 0x07, {0x03: [0x1AC680], + 0x04: [0x1AC6C1]}, 0x11), - InstrumentChange(0x0A, 0x00, {0x00: [0x1AC72F], - 0x01: [0x1AC751], - 0x02: [0x1AC772], - 0x03: [0x1AC793]}, 0x0F), - InstrumentChange(0x0A, 0x00, {0x00: [0x1AC740], - 0x01: [0x1AC765], - 0x02: [0x1AC786], - 0x03: [0x1AC7A7], - 0x04: [0x1AC7B4, 0x1AC7C9], - 0x05: [0x1AC7D1]}, 0x09), + SPCInstrumentChange(0x0A, 0x00, {0x00: [0x1AC72F], + 0x01: [0x1AC751], + 0x02: [0x1AC772], + 0x03: [0x1AC793]}, 0x0F), + SPCInstrumentChange(0x0A, 0x00, {0x00: [0x1AC740], + 0x01: [0x1AC765], + 0x02: [0x1AC786], + 0x03: [0x1AC7A7], + 0x04: [0x1AC7B4, 0x1AC7C9], + 0x05: [0x1AC7D1]}, 0x09), - InstrumentChange(0x0B, 0x00, {0x00: [0x1A9EEC, 0x1A9D26]}, 0x0F, ban=[0x0B]), - InstrumentChange(0x0B, 0x01, {0x01: [0x1A9D3F], - 0x02: [0x1A9D5A], - 0x03: [0x1A9D75], - 0x04: [0x1A9D90], - 0x05: [0x1A9DBB], - 0x06: [0x1A9DE9]}, 0x0F), + SPCInstrumentChange(0x0B, 0x00, {0x00: [0x1A9EEC, 0x1A9D26]}, 0x0F, ban=[0x0B]), + SPCInstrumentChange(0x0B, 0x01, {0x01: [0x1A9D3F], + 0x02: [0x1A9D5A], + 0x03: [0x1A9D75], + 0x04: [0x1A9D90], + 0x05: [0x1A9DBB], + 0x06: [0x1A9DE9]}, 0x0F), - InstrumentChange(0x0C, 0x00, {0x00: [0x1AC83A], - 0x01: [0x1AC84A], - 0x02: [0x1AC857], - 0x03: [0x1AC864], - 0x04: [0x1AC871], - 0x05: [0x1AC87E]}, 0x0B), - InstrumentChange(0x0C, 0x01, {0x02: [0x1AC89A], - 0x03: [0x1AC8AD]}, 0x11), - InstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E, type=Rh|Am, ban=[0x05]), - InstrumentChange(0x0C, 0x01, {0x05: [0x1AC8C3]}, 0x02), - InstrumentChange(0x0C, 0x02, {0x02: [0x1AC8E0], - 0x03: [0x1AC8F3]}, 0x11), - InstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E, type=Rh|Am, ban=[0x05]), - InstrumentChange(0x0C, 0x02, {0x05: [0x1AC909]}, 0x02), + SPCInstrumentChange(0x0C, 0x00, {0x00: [0x1AC83A], + 0x01: [0x1AC84A], + 0x02: [0x1AC857], + 0x03: [0x1AC864], + 0x04: [0x1AC871], + 0x05: [0x1AC87E]}, 0x0B), + SPCInstrumentChange(0x0C, 0x01, {0x02: [0x1AC89A], + 0x03: [0x1AC8AD]}, 0x11), + SPCInstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E, type=Rh|Am, ban=[0x05]), + SPCInstrumentChange(0x0C, 0x01, {0x05: [0x1AC8C3]}, 0x02), + SPCInstrumentChange(0x0C, 0x02, {0x02: [0x1AC8E0], + 0x03: [0x1AC8F3]}, 0x11), + SPCInstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E, type=Rh|Am, ban=[0x05]), + SPCInstrumentChange(0x0C, 0x02, {0x05: [0x1AC909]}, 0x02), - InstrumentChange(0x0D, 0x00, {0x00: [0x1AD003], - 0x03: [0x1AD02C], - 0x04: [0x1AD03A]}, 0x11), - InstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), - InstrumentChange(0x0D, 0x00, {0x02: [0x1AD07F]}, 0x14), - InstrumentChange(0x0D, 0x01, {0x00: [0x1ACD10], - 0x04: [0x1ACD9A]}, 0x0B), - InstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), - InstrumentChange(0x0D, 0x01, {0x03: [0x1ACD7F], - 0x05: [0x1ACDCA]}, 0x11), - InstrumentChange(0x0D, 0x03, {0x00: [0x1ACE8E], - 0x01: [0x1ACEB8]}, 0x0A), - InstrumentChange(0x0D, 0x03, {0x02: [0x1ACED4]}, 0x14), - InstrumentChange(0x0D, 0x03, {0x03: [0x1ACEE0], - 0x04: [0x1ACF07]}, 0x11), - InstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + SPCInstrumentChange(0x0D, 0x00, {0x00: [0x1AD003], + 0x03: [0x1AD02C], + 0x04: [0x1AD03A]}, 0x11), + SPCInstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + SPCInstrumentChange(0x0D, 0x00, {0x02: [0x1AD07F]}, 0x14), + SPCInstrumentChange(0x0D, 0x01, {0x00: [0x1ACD10], + 0x04: [0x1ACD9A]}, 0x0B), + SPCInstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + SPCInstrumentChange(0x0D, 0x01, {0x03: [0x1ACD7F], + 0x05: [0x1ACDCA]}, 0x11), + SPCInstrumentChange(0x0D, 0x03, {0x00: [0x1ACE8E], + 0x01: [0x1ACEB8]}, 0x0A), + SPCInstrumentChange(0x0D, 0x03, {0x02: [0x1ACED4]}, 0x14), + SPCInstrumentChange(0x0D, 0x03, {0x03: [0x1ACEE0], + 0x04: [0x1ACF07]}, 0x11), + SPCInstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), - InstrumentChange(0x0E, 0x00, {0x00: [0x1AD29C]}, 0x16), - InstrumentChange(0x0E, 0x00, {0x01: [0x1AD2AD], - 0x03: [0x1AD2CB], - 0x04: [0x1AD2D9]}, 0x18), - InstrumentChange(0x0E, 0x00, {0x02: [0x1AD2BB]}, 0x12), - InstrumentChange(0x0E, 0x01, {0x00: [0x1AD1A6]}, 0x16), - InstrumentChange(0x0E, 0x01, {0x01: [0x1AD1AF], - 0x03: [0x1AD1CD]}, 0x18), - InstrumentChange(0x0E, 0x01, {0x02: [0x1AD1C5]}, 0x0A), - InstrumentChange(0x0E, 0x02, {0x00: [0x1AD24C]}, 0x16), - InstrumentChange(0x0E, 0x02, {0x01: [0x1AD255], - 0x03: [0x1AD273]}, 0x18), - InstrumentChange(0x0E, 0x02, {0x02: [0x1AD26B]}, 0x0A), - InstrumentChange(0x0E, 0x03, {0x00: [0x1AD1E4], - 0x01: [0x1AD1ED], - 0x03: [0x1AD212], - 0x04: [0x1AD22F]}, 0x18), - InstrumentChange(0x0E, 0x03, {0x02: [0x1AD20A]}, 0x12), + SPCInstrumentChange(0x0E, 0x00, {0x00: [0x1AD29C]}, 0x16), + SPCInstrumentChange(0x0E, 0x00, {0x01: [0x1AD2AD], + 0x03: [0x1AD2CB], + 0x04: [0x1AD2D9]}, 0x18), + SPCInstrumentChange(0x0E, 0x00, {0x02: [0x1AD2BB]}, 0x12), + SPCInstrumentChange(0x0E, 0x01, {0x00: [0x1AD1A6]}, 0x16), + SPCInstrumentChange(0x0E, 0x01, {0x01: [0x1AD1AF], + 0x03: [0x1AD1CD]}, 0x18), + SPCInstrumentChange(0x0E, 0x01, {0x02: [0x1AD1C5]}, 0x0A), + SPCInstrumentChange(0x0E, 0x02, {0x00: [0x1AD24C]}, 0x16), + SPCInstrumentChange(0x0E, 0x02, {0x01: [0x1AD255], + 0x03: [0x1AD273]}, 0x18), + SPCInstrumentChange(0x0E, 0x02, {0x02: [0x1AD26B]}, 0x0A), + SPCInstrumentChange(0x0E, 0x03, {0x00: [0x1AD1E4], + 0x01: [0x1AD1ED], + 0x03: [0x1AD212], + 0x04: [0x1AD22F]}, 0x18), + SPCInstrumentChange(0x0E, 0x03, {0x02: [0x1AD20A]}, 0x12), - InstrumentChange(0x10, 0x00, {0x00: [0x1B816D], - 0x02: [0x1B81A0], - 0x03: [0x1B81C0], - 0x07: [0x1B827F]}, 0x0B), - InstrumentChange(0x10, 0x00, {0x01: [0x1B818C]}, 0x11), - InstrumentChange(0x10, 0x00, {0x04: [0x1B81E0], - 0x05: [0x1B8220, 0x1B8229]}, 0x02), - InstrumentChange(0x10, 0x00, {0x05: [0x1B8224]}, 0x0C), - InstrumentChange(0x10, 0x00, {0x06: [0x1B825F]}, 0x16), - InstrumentChange(0x10, 0x01, {0x00: [0x1B811F]}, 0x0B), - InstrumentChange(0x10, 0x01, {0x03: [0x1B813B], - 0x05: [0x1B814E]}, 0x0A), - InstrumentChange(0x10, 0x02, {0x00: [0x1B829E], - 0x05: [0x1B8310]}, 0x0B), - InstrumentChange(0x10, 0x02, {0x01: [0x1B82C0], - 0x03: [0x1B82E3], - 0x06: [0x1B8342]}, 0x0A), - InstrumentChange(0x10, 0x02, {0x04: [0x1B8308], - 0x04: [0x1B83F2]}, 0x02), - InstrumentChange(0x10, 0x03, {0x00: [0x1B8360], - 0x02: [0x1B83AD], - 0x05: [0x1B83FA]}, 0x0B), - InstrumentChange(0x10, 0x03, {0x01: [0x1B8396], - 0x03: [0x1B83CC], - 0x06: [0x1B842E]}, 0x0A), - InstrumentChange(0x10, 0x04, {0x00: [0x1B844D], - 0x02: [0x1B84A0]}, 0x0B), - InstrumentChange(0x10, 0x04, {0x01: [0x1B847B], - 0x03: [0x1B84CD]}, 0x0A), - InstrumentChange(0x10, 0x04, {0x04: [0x1B84ED], - 0x05: [0x1B84F5]}, 0x02), - InstrumentChange(0x10, 0x05, {0x00: [0x1B8548], - 0x01: [0x1B8575], - 0x03: [0x1B85B6]}, 0x0A), - InstrumentChange(0x10, 0x05, {0x02: [0x1B859B], - 0x07: [0x1B867F]}, 0x0B), - InstrumentChange(0x10, 0x05, {0x04: [0x1B85D0], - 0x05: [0x1B862C]}, 0x02), - InstrumentChange(0x10, 0x06, {0x02: [0x1B8726], - 0x06: [0x1B8745]}, 0x0A), - InstrumentChange(0x10, 0x07, {0x00: [0x1B8768]}, 0x0B), - InstrumentChange(0x10, 0x07, {0x00: [0x1B8B04], - 0x01: [0x1B8B10], - 0x02: [0x1B8775], - 0x06: [0x1B87AD]}, 0x0A), - InstrumentChange(0x10, 0x07, {0x05: [0x1B8792]}, 0x16), - InstrumentChange(0x10, 0x08, {0x02: [0x1B86B3]}, 0x0A), - InstrumentChange(0x10, 0x09, {0x00: [0x1B8A63], - 0x05: [0x1B8879], - 0x06: [0x1B8AED]}, 0x0B), - InstrumentChange(0x10, 0x09, {0x01: [0x1B8A87], - 0x02: [0x1B87E5]}, 0x0A), - InstrumentChange(0x10, 0x0A, {0x00: [0x1B88C8], - 0x02: [0x1B8946], - 0x03: [0x1B88E5]}, 0x0B), - InstrumentChange(0x10, 0x0A, {0x01: [0x1B892C], - 0x07: [0x1B8905]}, 0x0A), - InstrumentChange(0x10, 0x0A, {0x04: [0x1B897D]}, 0x02), + SPCInstrumentChange(0x10, 0x00, {0x00: [0x1B816D], + 0x02: [0x1B81A0], + 0x03: [0x1B81C0], + 0x07: [0x1B827F]}, 0x0B), + SPCInstrumentChange(0x10, 0x00, {0x01: [0x1B818C]}, 0x11), + SPCInstrumentChange(0x10, 0x00, {0x04: [0x1B81E0], + 0x05: [0x1B8220, 0x1B8229]}, 0x02), + SPCInstrumentChange(0x10, 0x00, {0x05: [0x1B8224]}, 0x0C), + SPCInstrumentChange(0x10, 0x00, {0x06: [0x1B825F]}, 0x16), + SPCInstrumentChange(0x10, 0x01, {0x00: [0x1B811F]}, 0x0B), + SPCInstrumentChange(0x10, 0x01, {0x03: [0x1B813B], + 0x05: [0x1B814E]}, 0x0A), + SPCInstrumentChange(0x10, 0x02, {0x00: [0x1B829E], + 0x05: [0x1B8310]}, 0x0B), + SPCInstrumentChange(0x10, 0x02, {0x01: [0x1B82C0], + 0x03: [0x1B82E3], + 0x06: [0x1B8342]}, 0x0A), + SPCInstrumentChange(0x10, 0x02, {0x04: [0x1B8308], + 0x04: [0x1B83F2]}, 0x02), + SPCInstrumentChange(0x10, 0x03, {0x00: [0x1B8360], + 0x02: [0x1B83AD], + 0x05: [0x1B83FA]}, 0x0B), + SPCInstrumentChange(0x10, 0x03, {0x01: [0x1B8396], + 0x03: [0x1B83CC], + 0x06: [0x1B842E]}, 0x0A), + SPCInstrumentChange(0x10, 0x04, {0x00: [0x1B844D], + 0x02: [0x1B84A0]}, 0x0B), + SPCInstrumentChange(0x10, 0x04, {0x01: [0x1B847B], + 0x03: [0x1B84CD]}, 0x0A), + SPCInstrumentChange(0x10, 0x04, {0x04: [0x1B84ED], + 0x05: [0x1B84F5]}, 0x02), + SPCInstrumentChange(0x10, 0x05, {0x00: [0x1B8548], + 0x01: [0x1B8575], + 0x03: [0x1B85B6]}, 0x0A), + SPCInstrumentChange(0x10, 0x05, {0x02: [0x1B859B], + 0x07: [0x1B867F]}, 0x0B), + SPCInstrumentChange(0x10, 0x05, {0x04: [0x1B85D0], + 0x05: [0x1B862C]}, 0x02), + SPCInstrumentChange(0x10, 0x06, {0x02: [0x1B8726], + 0x06: [0x1B8745]}, 0x0A), + SPCInstrumentChange(0x10, 0x07, {0x00: [0x1B8768]}, 0x0B), + SPCInstrumentChange(0x10, 0x07, {0x00: [0x1B8B04], + 0x01: [0x1B8B10], + 0x02: [0x1B8775], + 0x06: [0x1B87AD]}, 0x0A), + SPCInstrumentChange(0x10, 0x07, {0x05: [0x1B8792]}, 0x16), + SPCInstrumentChange(0x10, 0x08, {0x02: [0x1B86B3]}, 0x0A), + SPCInstrumentChange(0x10, 0x09, {0x00: [0x1B8A63], + 0x05: [0x1B8879], + 0x06: [0x1B8AED]}, 0x0B), + SPCInstrumentChange(0x10, 0x09, {0x01: [0x1B8A87], + 0x02: [0x1B87E5]}, 0x0A), + SPCInstrumentChange(0x10, 0x0A, {0x00: [0x1B88C8], + 0x02: [0x1B8946], + 0x03: [0x1B88E5]}, 0x0B), + SPCInstrumentChange(0x10, 0x0A, {0x01: [0x1B892C], + 0x07: [0x1B8905]}, 0x0A), + SPCInstrumentChange(0x10, 0x0A, {0x04: [0x1B897D]}, 0x02), - InstrumentChange(0x11, 0x00, {0x00: [0x1B8C95], - 0x01: [0x1B8CA2], - 0x02: [0x1B8CB1], - 0x03: [0x1B8CC0], - 0x04: [0x1B8CCF]}, 0x0A, type=Me, ban=[0x0D]), - InstrumentChange(0x11, 0x01, {0x05: [0x1B8CFF]}, 0x0A, ban=[0x04]), - InstrumentChange(0x11, 0x02, {0x03: [0x1B8D6B]}, 0x09), - InstrumentChange(0x11, 0x04, {0x04: [0x1B8E2B]}, 0x11), - InstrumentChange(0x11, 0x05, {0x05: [0x1B90F6]}, 0x11), + SPCInstrumentChange(0x11, 0x00, {0x00: [0x1B8C95], + 0x01: [0x1B8CA2], + 0x02: [0x1B8CB1], + 0x03: [0x1B8CC0], + 0x04: [0x1B8CCF]}, 0x0A, type=Me, ban=[0x0D]), + SPCInstrumentChange(0x11, 0x01, {0x05: [0x1B8CFF]}, 0x0A, ban=[0x04]), + SPCInstrumentChange(0x11, 0x02, {0x03: [0x1B8D6B]}, 0x09), + SPCInstrumentChange(0x11, 0x04, {0x04: [0x1B8E2B]}, 0x11), + SPCInstrumentChange(0x11, 0x05, {0x05: [0x1B90F6]}, 0x11), - InstrumentChange(0x12, 0x00, {0x00: [0x1B9275], - 0x01: [0x1B9282], - 0x05: [0x1B92DE]}, 0x0A), - InstrumentChange(0x12, 0x00, {0x02: [0x1B9290], - 0x03: [0x1B92AB]}, 0x11), - InstrumentChange(0x12, 0x00, {0x04: [0x1B92C6]}, 0x02), - InstrumentChange(0x12, 0x00, {0x05: [0x1B92D3]}, 0x10, type=[0x08, 0x10, 0x17]), - InstrumentChange(0x12, 0x01, {0x00: [0x1B917D], - 0x01: [0x1B918A], - 0x05: [0x1B91E6], - 0x06: [0x1B9229]}, 0x0A), - InstrumentChange(0x12, 0x01, {0x02: [0x1B9198], - 0x03: [0x1B91B3]}, 0x11), - InstrumentChange(0x12, 0x01, {0x04: [0x1B91CE]}, 0x02), - InstrumentChange(0x12, 0x01, {0x05: [0x1B91DB], - 0x06: [0x1B921E]}, 0x10, type=[0x08, 0x10, 0x17]), - InstrumentChange(0x12, 0x02, {0x00: [0x1B9313], - 0x01: [0x1B9320], - 0x05: [0x1B937C], - 0x06: [0x1B93BF]}, 0x0A), - InstrumentChange(0x12, 0x02, {0x02: [0x1B932E], - 0x03: [0x1B9349]}, 0x11), - InstrumentChange(0x12, 0x02, {0x04: [0x1B9364]}, 0x02), - InstrumentChange(0x12, 0x02, {0x05: [0x1B9371], - 0x06: [0x1B93B4]}, 0x10, type=[0x08, 0x10, 0x17]), + SPCInstrumentChange(0x12, 0x00, {0x00: [0x1B9275], + 0x01: [0x1B9282], + 0x05: [0x1B92DE]}, 0x0A), + SPCInstrumentChange(0x12, 0x00, {0x02: [0x1B9290], + 0x03: [0x1B92AB]}, 0x11), + SPCInstrumentChange(0x12, 0x00, {0x04: [0x1B92C6]}, 0x02), + SPCInstrumentChange(0x12, 0x00, {0x05: [0x1B92D3]}, 0x10, type=[0x08, 0x10, 0x17]), + SPCInstrumentChange(0x12, 0x01, {0x00: [0x1B917D], + 0x01: [0x1B918A], + 0x05: [0x1B91E6], + 0x06: [0x1B9229]}, 0x0A), + SPCInstrumentChange(0x12, 0x01, {0x02: [0x1B9198], + 0x03: [0x1B91B3]}, 0x11), + SPCInstrumentChange(0x12, 0x01, {0x04: [0x1B91CE]}, 0x02), + SPCInstrumentChange(0x12, 0x01, {0x05: [0x1B91DB], + 0x06: [0x1B921E]}, 0x10, type=[0x08, 0x10, 0x17]), + SPCInstrumentChange(0x12, 0x02, {0x00: [0x1B9313], + 0x01: [0x1B9320], + 0x05: [0x1B937C], + 0x06: [0x1B93BF]}, 0x0A), + SPCInstrumentChange(0x12, 0x02, {0x02: [0x1B932E], + 0x03: [0x1B9349]}, 0x11), + SPCInstrumentChange(0x12, 0x02, {0x04: [0x1B9364]}, 0x02), + SPCInstrumentChange(0x12, 0x02, {0x05: [0x1B9371], + 0x06: [0x1B93B4]}, 0x10, type=[0x08, 0x10, 0x17]), - InstrumentChange(0x13, 0x00, {0x00: [0x1B9458], - 0x02: [0x1B94DA], - 0x03: [0x1B953E], - 0x04: [0x1B95A2], - 0x05: [0x1B9606]}, 0x0B), - InstrumentChange(0x13, 0x00, {0x01: [0x1B94A4]}, 0x11), - InstrumentChange(0x13, 0x00, {0x06: [0x1B9650], - 0x07: [0x1B9696]}, 0x0F), - InstrumentChange(0x13, 0x00, {0x06: [0x1B967B], - 0x07: [0x1B96C0]}, 0x02, ban=[0x0B]), + SPCInstrumentChange(0x13, 0x00, {0x00: [0x1B9458], + 0x02: [0x1B94DA], + 0x03: [0x1B953E], + 0x04: [0x1B95A2], + 0x05: [0x1B9606]}, 0x0B), + SPCInstrumentChange(0x13, 0x00, {0x01: [0x1B94A4]}, 0x11), + SPCInstrumentChange(0x13, 0x00, {0x06: [0x1B9650], + 0x07: [0x1B9696]}, 0x0F), + SPCInstrumentChange(0x13, 0x00, {0x06: [0x1B967B], + 0x07: [0x1B96C0]}, 0x02, ban=[0x0B]), - InstrumentChange(0x14, 0x00, {0x00: [0x1B9901, 0x1B97A8]}, 0x15), - InstrumentChange(0x14, 0x01, {0x01: [0x1B97C4], - 0x02: [0x1B97DE], - 0x03: [0x1B97FD], - 0x04: [0x1B9813], - 0x05: [0x1B982A]}, 0x15), + SPCInstrumentChange(0x14, 0x00, {0x00: [0x1B9901, 0x1B97A8]}, 0x15), + SPCInstrumentChange(0x14, 0x01, {0x01: [0x1B97C4], + 0x02: [0x1B97DE], + 0x03: [0x1B97FD], + 0x04: [0x1B9813], + 0x05: [0x1B982A]}, 0x15), - InstrumentChange(0x15, 0x00, {0x00: [0x1B9A32], - 0x01: [0x1B9A50], - 0x02: [0x1B9A6D], - 0x03: [0x1B9A8A]}, 0x0B), - InstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), - InstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), - InstrumentChange(0x15, 0x01, {0x01: [0x1B9984], - 0x02: [0x1B99AA], - 0x03: [0x1B99D7]}, 0x0B), - InstrumentChange(0x15, 0x01, {0x04: [0x1B9A04]}, 0x14), - InstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), - InstrumentChange(0x15, 0x02, {0x01: [0x1B9B58], - 0x02: [0x1B9B7E], - 0x03: [0x1B9BAB]}, 0x0B), - InstrumentChange(0x15, 0x02, {0x04: [0x1B9BD8]}, 0x14), + SPCInstrumentChange(0x15, 0x00, {0x00: [0x1B9A32], + 0x01: [0x1B9A50], + 0x02: [0x1B9A6D], + 0x03: [0x1B9A8A]}, 0x0B), + SPCInstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + SPCInstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + SPCInstrumentChange(0x15, 0x01, {0x01: [0x1B9984], + 0x02: [0x1B99AA], + 0x03: [0x1B99D7]}, 0x0B), + SPCInstrumentChange(0x15, 0x01, {0x04: [0x1B9A04]}, 0x14), + SPCInstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + SPCInstrumentChange(0x15, 0x02, {0x01: [0x1B9B58], + 0x02: [0x1B9B7E], + 0x03: [0x1B9BAB]}, 0x0B), + SPCInstrumentChange(0x15, 0x02, {0x04: [0x1B9BD8]}, 0x14), - InstrumentChange(0x16, 0x00, {0x00: [0x1B9CE6], - 0x01: [0x1B9CF1]}, 0x09), - InstrumentChange(0x16, 0x01, {0x00: [0x1B9C82], - 0x01: [0x1B9C8B], - 0x02: [0x1B9C93], - 0x03: [0x1B9CAA]}, 0x09), - InstrumentChange(0x16, 0x01, {0x04: [0x1B9CBA]}, 0x09), - InstrumentChange(0x16, 0x02, {0x00: [0x1B9CFB], - 0x01: [0x1B9D44]}, 0x09), - InstrumentChange(0x16, 0x03, {0x00: [0x1B9DBE], - 0x01: [0x1B9E47], - 0x05: [0x1B9EE4]}, 0x09), + SPCInstrumentChange(0x16, 0x00, {0x00: [0x1B9CE6], + 0x01: [0x1B9CF1]}, 0x09), + SPCInstrumentChange(0x16, 0x01, {0x00: [0x1B9C82], + 0x01: [0x1B9C8B], + 0x02: [0x1B9C93], + 0x03: [0x1B9CAA]}, 0x09), + SPCInstrumentChange(0x16, 0x01, {0x04: [0x1B9CBA]}, 0x09), + SPCInstrumentChange(0x16, 0x02, {0x00: [0x1B9CFB], + 0x01: [0x1B9D44]}, 0x09), + SPCInstrumentChange(0x16, 0x03, {0x00: [0x1B9DBE], + 0x01: [0x1B9E47], + 0x05: [0x1B9EE4]}, 0x09), - InstrumentChange(0x17, 0x00, {0x00: [0x1BA287], - 0x03: [0x1BA26A]}, 0x0E, type=[0x0E, 0x0F, 0x18]), - InstrumentChange(0x17, 0x01, {0x01: [0x1BA20F], - 0x03: [0x1BA24F]}, 0x0E, type=Rh, ban=[0x0B, 0x0D, 0x16]), - InstrumentChange(0x17, 0x01, {0x02: [0x1BA231]}, 0x0F), + SPCInstrumentChange(0x17, 0x00, {0x00: [0x1BA287], + 0x03: [0x1BA26A]}, 0x0E, type=[0x0E, 0x0F, 0x18]), + SPCInstrumentChange(0x17, 0x01, {0x01: [0x1BA20F], + 0x03: [0x1BA24F]}, 0x0E, type=Rh, ban=[0x0B, 0x0D, 0x16]), + SPCInstrumentChange(0x17, 0x01, {0x02: [0x1BA231]}, 0x0F), - InstrumentChange(0x19, 0x00, {0x00: [0x1BA476], - 0x01: [0x1BA49C], - 0x02: [0x1BA4C1], - 0x03: [0x1BA4D5], - 0x04: [0x1BA4F4], - 0x05: [0x1BA513]}, 0x0A), - InstrumentChange(0x19, 0x01, {0x00: [0x1BA357], - 0x01: [0x1BA379], - 0x03: [0x1BA38C], - 0x04: [0x1BA39D]}, 0x0A), - InstrumentChange(0x19, 0x02, {0x02: [0x1BA3D3]}, 0x15), - InstrumentChange(0x19, 0x03, {0x00: [0x1BA3F8]}, 0x16), - InstrumentChange(0x19, 0x03, {0x02: [0x1BA40D]}, 0x0A), + SPCInstrumentChange(0x19, 0x00, {0x00: [0x1BA476], + 0x01: [0x1BA49C], + 0x02: [0x1BA4C1], + 0x03: [0x1BA4D5], + 0x04: [0x1BA4F4], + 0x05: [0x1BA513]}, 0x0A), + SPCInstrumentChange(0x19, 0x01, {0x00: [0x1BA357], + 0x01: [0x1BA379], + 0x03: [0x1BA38C], + 0x04: [0x1BA39D]}, 0x0A), + SPCInstrumentChange(0x19, 0x02, {0x02: [0x1BA3D3]}, 0x15), + SPCInstrumentChange(0x19, 0x03, {0x00: [0x1BA3F8]}, 0x16), + SPCInstrumentChange(0x19, 0x03, {0x02: [0x1BA40D]}, 0x0A), - InstrumentChange(0x1A, 0x00, {0x00: [0x1BA70E, 0x1BA71A], - 0x01: [0x1BA729], - 0x02: [0x1BA743], - 0x03: [0x1BA763], - 0x04: [0x1BA783], - 0x05: [0x1BA7A2], - 0x06: [0x1BA7B9], - 0x07: [0x1BA7D0]}, 0x0E, type=Rh|Am, ban=[0x01, 0x05, 0x06, 0x10]), - InstrumentChange(0x1A, 0x01, {0x00: [0x1BA5C1], - 0x05: [0x1BA68F], - 0x06: [0x1BA6A6], - 0x07: [0x1BA6DD]}, 0x0A), + SPCInstrumentChange(0x1A, 0x00, {0x00: [0x1BA70E, 0x1BA71A], + 0x01: [0x1BA729], + 0x02: [0x1BA743], + 0x03: [0x1BA763], + 0x04: [0x1BA783], + 0x05: [0x1BA7A2], + 0x06: [0x1BA7B9], + 0x07: [0x1BA7D0]}, 0x0E, type=Rh|Am, ban=[0x01, 0x05, 0x06, 0x10]), + SPCInstrumentChange(0x1A, 0x01, {0x00: [0x1BA5C1], + 0x05: [0x1BA68F], + 0x06: [0x1BA6A6], + 0x07: [0x1BA6DD]}, 0x0A), - InstrumentChange(0x1B, 0x00, {0x00: [0x1BAA55], - 0x01: [0x1BAA66], - 0x02: [0x1BAA75], - 0x03: [0x1BAA86], - 0x04: [0x1BAA97]}, 0x0F), - InstrumentChange(0x1B, 0x01, {0x00: [0x1BA956], - 0x01: [0x1BA96F], - 0x02: [0x1BA98A], - 0x03: [0x1BA9A5], - 0x04: [0x1BA9C0], - 0x05: [0x1BA9EB], - 0x06: [0x1BAA19]}, 0x0F), + SPCInstrumentChange(0x1B, 0x00, {0x00: [0x1BAA55], + 0x01: [0x1BAA66], + 0x02: [0x1BAA75], + 0x03: [0x1BAA86], + 0x04: [0x1BAA97]}, 0x0F), + SPCInstrumentChange(0x1B, 0x01, {0x00: [0x1BA956], + 0x01: [0x1BA96F], + 0x02: [0x1BA98A], + 0x03: [0x1BA9A5], + 0x04: [0x1BA9C0], + 0x05: [0x1BA9EB], + 0x06: [0x1BAA19]}, 0x0F), - InstrumentChange(0x1C, 0x00, {0x00: [0x1BACB6, 0x1BABAD]}, 0x09), - InstrumentChange(0x1C, 0x01, {0x01: [0x1BABC7], - 0x02: [0x1BABE0], - 0x03: [0x1BABF4], - 0x04: [0x1BAC0B]}, 0x09), - InstrumentChange(0x1C, 0x02, {0x00: [0x1BAC26], - 0x01: [0x1BAC44], - 0x02: [0x1BAC61]}, 0x09), + SPCInstrumentChange(0x1C, 0x00, {0x00: [0x1BACB6, 0x1BABAD]}, 0x09), + SPCInstrumentChange(0x1C, 0x01, {0x01: [0x1BABC7], + 0x02: [0x1BABE0], + 0x03: [0x1BABF4], + 0x04: [0x1BAC0B]}, 0x09), + SPCInstrumentChange(0x1C, 0x02, {0x00: [0x1BAC26], + 0x01: [0x1BAC44], + 0x02: [0x1BAC61]}, 0x09), - InstrumentChange(0x1D, 0x00, {0x00: [0x1BACEA], - 0x01: [0x1BAD06], - 0x02: [0x1BAD23], - 0x03: [0x1BAD40], - 0x04: [0x1BAD5D]}, 0x0B), + SPCInstrumentChange(0x1D, 0x00, {0x00: [0x1BACEA], + 0x01: [0x1BAD06], + 0x02: [0x1BAD23], + 0x03: [0x1BAD40], + 0x04: [0x1BAD5D]}, 0x0B), - InstrumentChange(0x1E, 0x00, {0x00: [0x1BB14D], - 0x01: [0x1BB169], - 0x02: [0x1BB17E], - 0x03: [0x1BB193], - 0x04: [0x1BB1A8]}, 0x09), - InstrumentChange(0x1E, 0x00, {0x05: [0x1BB1BD]}, 0x02), + SPCInstrumentChange(0x1E, 0x00, {0x00: [0x1BB14D], + 0x01: [0x1BB169], + 0x02: [0x1BB17E], + 0x03: [0x1BB193], + 0x04: [0x1BB1A8]}, 0x09), + SPCInstrumentChange(0x1E, 0x00, {0x05: [0x1BB1BD]}, 0x02), - InstrumentChange(0x1F, 0x00, {0x00: [0x1BAE86], - 0x03: [0x1BAEC6], - 0x04: [0x1BAEE4]}, 0x0B), - InstrumentChange(0x1F, 0x00, {0x00: [0x1BAE9C]}, 0x18), - InstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), - InstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02, ban=[0x10]), - InstrumentChange(0x1F, 0x01, {0x03: [0x1BAE15], - 0x04: [0x1BAE32], - 0x05: [0x1BAE4F]}, 0x11), - InstrumentChange(0x1F, 0x02, {0x01: [0x1BAF2C]}, 0x0B), - InstrumentChange(0x1F, 0x02, {0x03: [0x1BAF53], - 0x04: [0x1BAF69], - 0x05: [0x1BAF7F]}, 0x11), - InstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), + SPCInstrumentChange(0x1F, 0x00, {0x00: [0x1BAE86], + 0x03: [0x1BAEC6], + 0x04: [0x1BAEE4]}, 0x0B), + SPCInstrumentChange(0x1F, 0x00, {0x00: [0x1BAE9C]}, 0x18), + SPCInstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), + SPCInstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02, ban=[0x10]), + SPCInstrumentChange(0x1F, 0x01, {0x03: [0x1BAE15], + 0x04: [0x1BAE32], + 0x05: [0x1BAE4F]}, 0x11), + SPCInstrumentChange(0x1F, 0x02, {0x01: [0x1BAF2C]}, 0x0B), + SPCInstrumentChange(0x1F, 0x02, {0x03: [0x1BAF53], + 0x04: [0x1BAF69], + 0x05: [0x1BAF7F]}, 0x11), + SPCInstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), - InstrumentChange(0x20, 0x00, {0x00: [0x1AD49A], - 0x01: [0x1AD4BA], - 0x02: [0x1AD4D3], - 0x03: [0x1AD4EE], - 0x04: [0x1AD507]}, 0x0A), - InstrumentChange(0x20, 0x01, {0x05: [0x1AD475]}, 0x18), + SPCInstrumentChange(0x20, 0x00, {0x00: [0x1AD49A], + 0x01: [0x1AD4BA], + 0x02: [0x1AD4D3], + 0x03: [0x1AD4EE], + 0x04: [0x1AD507]}, 0x0A), + SPCInstrumentChange(0x20, 0x01, {0x05: [0x1AD475]}, 0x18), - InstrumentChange(0x21, 0x00, {0x00: [0x1AE8E3], - 0x01: [0x1AF18E], - 0x02: [0x1AF1A9], - 0x03: [0x1AF1B7], - 0x04: [0x1AF1D2], - 0x05: [0x1AF1E4], - 0x06: [0x1AF201], - 0x07: [0x1AF21C]}, 0x0F), - InstrumentChange(0x21, 0x01, {0x00: [0x1AE518]}, 0x12), - InstrumentChange(0x21, 0x01, {0x01: [0x1AE540], - 0x03: [0x1AE58D]}, 0x0B), - InstrumentChange(0x21, 0x01, {0x02: [0x1AE564]}, 0x09), - InstrumentChange(0x21, 0x01, {0x04: [0x1AE5B1], - 0x05: [0x1AE5D4]}, 0x0A), - InstrumentChange(0x21, 0x01, {0x06: [0x1AE5EB, 0x1AE60F]}, 0x02), - InstrumentChange(0x21, 0x01, {0x06: [0x1AE5F9], - 0x07: [0x1AE61B]}, 0x0C), - InstrumentChange(0x21, 0x02, {0x03: [0x1AE651], - 0x05: [0x1AE69A]}, 0x0A), - InstrumentChange(0x21, 0x03, {0x00: [0x1AEC5E], - 0x01: [0x1AEC71], - 0x02: [0x1AEC90]}, 0x0A), - InstrumentChange(0x21, 0x04, {0x01: [0x1AECB6]}, 0x0A), - InstrumentChange(0x21, 0x04, {0x07: [0x1AF3A3]}, 0x0F), - InstrumentChange(0x21, 0x05, {0x00: [0x1AE6D5], - 0x01: [0x1AE6FD], - 0x03: [0x1AE748], - 0x04: [0x1AE75E]}, 0x0A), - InstrumentChange(0x21, 0x05, {0x02: [0x1AE72A]}, 0x09), - InstrumentChange(0x21, 0x06, {0x00: [0x1AE774]}, 0x0A), - InstrumentChange(0x21, 0x06, {0x02: [0x1AE7BE], - 0x04: [0x1AE825]}, 0x09), - InstrumentChange(0x21, 0x07, {0x00: [0x1AED48], - 0x01: [0x1AED7B], - 0x03: [0x1AEDCD], - 0x04: [0x1AEDF4]}, 0x0A), - InstrumentChange(0x21, 0x07, {0x02: [0x1AEDB1]}, 0x09), - InstrumentChange(0x21, 0x08, {0x00: [0x1AE876], - 0x01: [0x1AE881]}, 0x0A), - InstrumentChange(0x21, 0x0A, {0x02: [0x1AF17C], - 0x03: [0x1AE8BB]}, 0x09), - InstrumentChange(0x21, 0x0B, {0x00: [0x1AEE0F]}, 0x11), - InstrumentChange(0x21, 0x0B, {0x01: [0x1AEE22], - 0x03: [0x1AEE46], - 0x04: [0x1AEE74]}, 0x0A), - InstrumentChange(0x21, 0x0C, {0x01: [0x1AEECD], - 0x03: [0x1AEEF9], - 0x04: [0x1AEF2B]}, 0x0A), - InstrumentChange(0x21, 0x0D, {0x00: [0x1AE954], - 0x03: [0x1AE9D4], - 0x04: [0x1AEA03]}, 0x18), - InstrumentChange(0x21, 0x0D, {0x00: [0x1AE971]}, 0x12), - InstrumentChange(0x21, 0x0D, {0x01: [0x1AE983]}, 0x09), - InstrumentChange(0x21, 0x0D, {0x01: [0x1AE9A6], - 0x03: [0x1AE9F3]}, 0x0B), - InstrumentChange(0x21, 0x0D, {0x02: [0x1AE9B6]}, 0x0A), - InstrumentChange(0x21, 0x0E, {0x00: [0x1AEA4F]}, 0x12), - InstrumentChange(0x21, 0x0E, {0x01: [0x1AEA84], - 0x03: [0x1AEAE7], - 0x05: [0x1AEB94]}, 0x0B), - InstrumentChange(0x21, 0x0E, {0x02: [0x1AEAAF]}, 0x09), - InstrumentChange(0x21, 0x0E, {0x04: [0x1AEB69]}, 0x0A), - InstrumentChange(0x21, 0x0E, {0x06: [0x1AEC03]}, 0x02), - InstrumentChange(0x21, 0x0E, {0x07: [0x1AEC3C]}, 0x0C), + SPCInstrumentChange(0x21, 0x00, {0x00: [0x1AE8E3], + 0x01: [0x1AF18E], + 0x02: [0x1AF1A9], + 0x03: [0x1AF1B7], + 0x04: [0x1AF1D2], + 0x05: [0x1AF1E4], + 0x06: [0x1AF201], + 0x07: [0x1AF21C]}, 0x0F), + SPCInstrumentChange(0x21, 0x01, {0x00: [0x1AE518]}, 0x12), + SPCInstrumentChange(0x21, 0x01, {0x01: [0x1AE540], + 0x03: [0x1AE58D]}, 0x0B), + SPCInstrumentChange(0x21, 0x01, {0x02: [0x1AE564]}, 0x09), + SPCInstrumentChange(0x21, 0x01, {0x04: [0x1AE5B1], + 0x05: [0x1AE5D4]}, 0x0A), + SPCInstrumentChange(0x21, 0x01, {0x06: [0x1AE5EB, 0x1AE60F]}, 0x02, ban=[0x04]), + SPCInstrumentChange(0x21, 0x01, {0x06: [0x1AE5F9], + 0x07: [0x1AE61B]}, 0x0C), + SPCInstrumentChange(0x21, 0x02, {0x03: [0x1AE651], + 0x05: [0x1AE69A]}, 0x0A), + SPCInstrumentChange(0x21, 0x03, {0x00: [0x1AEC5E], + 0x01: [0x1AEC71], + 0x02: [0x1AEC90]}, 0x0A), + SPCInstrumentChange(0x21, 0x04, {0x01: [0x1AECB6]}, 0x0A), + SPCInstrumentChange(0x21, 0x04, {0x07: [0x1AF3A3]}, 0x0F), + SPCInstrumentChange(0x21, 0x05, {0x00: [0x1AE6D5], + 0x01: [0x1AE6FD], + 0x03: [0x1AE748], + 0x04: [0x1AE75E]}, 0x0A), + SPCInstrumentChange(0x21, 0x05, {0x02: [0x1AE72A]}, 0x09), + SPCInstrumentChange(0x21, 0x06, {0x00: [0x1AE774]}, 0x0A), + SPCInstrumentChange(0x21, 0x06, {0x02: [0x1AE7BE], + 0x04: [0x1AE825]}, 0x09), + SPCInstrumentChange(0x21, 0x07, {0x00: [0x1AED48], + 0x01: [0x1AED7B], + 0x03: [0x1AEDCD], + 0x04: [0x1AEDF4]}, 0x0A), + SPCInstrumentChange(0x21, 0x07, {0x02: [0x1AEDB1]}, 0x09), + SPCInstrumentChange(0x21, 0x08, {0x00: [0x1AE876], + 0x01: [0x1AE881]}, 0x0A), + SPCInstrumentChange(0x21, 0x0A, {0x02: [0x1AF17C], + 0x03: [0x1AE8BB]}, 0x09), + SPCInstrumentChange(0x21, 0x0B, {0x00: [0x1AEE0F]}, 0x11), + SPCInstrumentChange(0x21, 0x0B, {0x01: [0x1AEE22], + 0x03: [0x1AEE46], + 0x04: [0x1AEE74]}, 0x0A), + SPCInstrumentChange(0x21, 0x0C, {0x01: [0x1AEECD], + 0x03: [0x1AEEF9], + 0x04: [0x1AEF2B]}, 0x0A), + SPCInstrumentChange(0x21, 0x0D, {0x00: [0x1AE954], + 0x03: [0x1AE9D4], + 0x04: [0x1AEA03]}, 0x18), + SPCInstrumentChange(0x21, 0x0D, {0x00: [0x1AE971]}, 0x12), + SPCInstrumentChange(0x21, 0x0D, {0x01: [0x1AE983]}, 0x09), + SPCInstrumentChange(0x21, 0x0D, {0x01: [0x1AE9A6], + 0x03: [0x1AE9F3]}, 0x0B), + SPCInstrumentChange(0x21, 0x0D, {0x02: [0x1AE9B6]}, 0x0A), + SPCInstrumentChange(0x21, 0x0E, {0x00: [0x1AEA4F]}, 0x12), + SPCInstrumentChange(0x21, 0x0E, {0x01: [0x1AEA84], + 0x03: [0x1AEAE7], + 0x05: [0x1AEB94]}, 0x0B), + SPCInstrumentChange(0x21, 0x0E, {0x02: [0x1AEAAF]}, 0x09), + SPCInstrumentChange(0x21, 0x0E, {0x04: [0x1AEB69]}, 0x0A), + SPCInstrumentChange(0x21, 0x0E, {0x06: [0x1AEC03]}, 0x02), + SPCInstrumentChange(0x21, 0x0E, {0x07: [0x1AEC3C]}, 0x0C), - InstrumentChange(0x22, 0x00, {0x00: [0x1ADA2D], - 0x01: [0x1ADA41], - 0x02: [0x1ADA51], - 0x03: [0x1ADA6C], - 0x04: [0x1ADA83]}, 0x0A), - InstrumentChange(0x22, 0x01, {0x05: [0x1ADAC9]}, 0x0A), - InstrumentChange(0x22, 0x02, {0x07: [0x1ADB60]}, 0x0A), - InstrumentChange(0x22, 0x03, {0x06: [0x1ADC10]}, 0x16), - InstrumentChange(0x22, 0x05, {0x05: [0x1AD7CC]}, 0x09), - InstrumentChange(0x22, 0x05, {0x06: [0x1AD803]}, 0x11), - InstrumentChange(0x22, 0x05, {0x07: [0x1AD81B]}, 0x0A), - InstrumentChange(0x22, 0x06, {0x02: [0x1AD8A6]}, 0x11), - InstrumentChange(0x22, 0x06, {0x03: [0x1AD959]}, 0x13), - InstrumentChange(0x22, 0x06, {0x06: [0x1AD9B7]}, 0x16), - InstrumentChange(0x22, 0x07, {0x00: [0x1ADCCD]}, 0x0B), - InstrumentChange(0x22, 0x07, {0x02: [0x1ADD73]}, 0x11), - InstrumentChange(0x22, 0x07, {0x04: [0x1ADDE0], - 0x07: [0x1ADE9E]}, 0x0A), - InstrumentChange(0x22, 0x07, {0x05: [0x1ADE14]}, 0x12), - InstrumentChange(0x22, 0x07, {0x06: [0x1ADE62]}, 0x16), - InstrumentChange(0x22, 0x08, {0x00: [0x1ADF02], - 0x04: [0x1ADFA8]}, 0x0B), - InstrumentChange(0x22, 0x08, {0x01: [0x1ADF2A]}, 0x11), - InstrumentChange(0x22, 0x08, {0x02: [0x1ADF83], - 0x05: [0x1ADFED]}, 0x09), - InstrumentChange(0x22, 0x08, {0x06: [0x1AE02D], - 0x07: [0x1AE047]}, 0x0A) + SPCInstrumentChange(0x22, 0x00, {0x00: [0x1ADA2D], + 0x01: [0x1ADA41], + 0x02: [0x1ADA51], + 0x03: [0x1ADA6C], + 0x04: [0x1ADA83]}, 0x0A), + SPCInstrumentChange(0x22, 0x01, {0x05: [0x1ADAC9]}, 0x0A), + SPCInstrumentChange(0x22, 0x02, {0x07: [0x1ADB60]}, 0x0A), + SPCInstrumentChange(0x22, 0x03, {0x06: [0x1ADC10]}, 0x16), + SPCInstrumentChange(0x22, 0x05, {0x05: [0x1AD7CC]}, 0x09), + SPCInstrumentChange(0x22, 0x05, {0x06: [0x1AD803]}, 0x11), + SPCInstrumentChange(0x22, 0x05, {0x07: [0x1AD81B]}, 0x0A), + SPCInstrumentChange(0x22, 0x06, {0x02: [0x1AD8A6]}, 0x11), + SPCInstrumentChange(0x22, 0x06, {0x03: [0x1AD959]}, 0x13), + SPCInstrumentChange(0x22, 0x06, {0x06: [0x1AD9B7]}, 0x16), + SPCInstrumentChange(0x22, 0x07, {0x00: [0x1ADCCD]}, 0x0B), + SPCInstrumentChange(0x22, 0x07, {0x02: [0x1ADD73]}, 0x11), + SPCInstrumentChange(0x22, 0x07, {0x04: [0x1ADDE0], + 0x07: [0x1ADE9E]}, 0x0A), + SPCInstrumentChange(0x22, 0x07, {0x05: [0x1ADE14]}, 0x12), + SPCInstrumentChange(0x22, 0x07, {0x06: [0x1ADE62]}, 0x16), + SPCInstrumentChange(0x22, 0x08, {0x00: [0x1ADF02], + 0x04: [0x1ADFA8]}, 0x0B), + SPCInstrumentChange(0x22, 0x08, {0x01: [0x1ADF2A]}, 0x11), + SPCInstrumentChange(0x22, 0x08, {0x02: [0x1ADF83], + 0x05: [0x1ADFED]}, 0x09), + SPCInstrumentChange(0x22, 0x08, {0x06: [0x1AE02D], + 0x07: [0x1AE047]}, 0x0A) ] \ No newline at end of file diff --git a/source/classes/constants.py b/source/classes/constants.py index c8e15532..1eae52dd 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -139,6 +139,7 @@ SETTINGSTOPROCESS = { "uwpalettes": "uw_palettes", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", "shuffle_songinstruments": "shuffle_songinstruments", 'msu_resume': 'msu_resume', }, diff --git a/source/gui/adjust/overview.py b/source/gui/adjust/overview.py index 967a9780..a26bd750 100644 --- a/source/gui/adjust/overview.py +++ b/source/gui/adjust/overview.py @@ -107,6 +107,7 @@ def adjust_page(top, parent, settings): "reduce_flashing": "reduce_flashing", 'msu_resume': 'msu_resume', "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", "shuffle_songinstruments": "shuffle_songinstruments", } guiargs = Namespace() @@ -159,6 +160,7 @@ def adjust_page(top, parent, settings): "nobgm": "disablemusic", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", "shuffle_songinstruments": "shuffle_songinstruments", "msu_resume": "msu_resume" } diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 14859951..a91fb518 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -311,6 +311,7 @@ def create_guiargs(parent): "uwpalettes": "uw_palettes", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", "shuffle_songinstruments": "shuffle_songinstruments" } for adjustarg in adjustargs: diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index 6e6695c0..a1f07d7d 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -225,6 +225,7 @@ def loadadjustargs(gui, settings): "uwpalettes": "adjust.uwpalettes", "reduce_flashing": "adjust.reduce_flashing", "shuffle_sfx": "adjust.shuffle_sfx", + "shuffle_sfxinstruments": "adjust.shuffle_sfxinstruments", "shuffle_songinstruments": "adjust.shuffle_songinstruments" } } diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index ca80dff7..b80b21f4 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -221,6 +221,7 @@ def roll_settings(weights): ret.ow_palettes = get_choice('ow_palettes', romweights) ret.uw_palettes = get_choice('uw_palettes', romweights) ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on' + ret.shuffle_sfxinstruments = get_choice('shuffle_sfxinstruments', romweights) == 'on' ret.shuffle_songinstruments = get_choice('shuffle_songinstruments', romweights) == 'on' ret.msu_resume = get_choice('msu_resume', romweights) == 'on' From d8f33d6b8bec9a31aeb70126b300c07ad63a1453 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 26 Jan 2024 22:39:00 -0600 Subject: [PATCH 118/123] Fixed error with canceling out of Select Destination --- source/gui/bottom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/bottom.py b/source/gui/bottom.py index a91fb518..d50010f2 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -202,7 +202,7 @@ def bottom_frame(self, parent, args=None): def select_output(): from tkinter import filedialog folder_selected = filedialog.askdirectory() - if folder_selected is not None: + if folder_selected is not None and folder_selected != '': args.outputpath = parent.settings["outputpath"] = folder_selected ## Output Button From f3afba7ee6dd288f63b3c7ef5823b6be49afa319 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 26 Jan 2024 23:27:06 -0600 Subject: [PATCH 119/123] Fixed issue with Lite ER and Vanilla GT --- source/overworld/EntranceShuffle2.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index be26d772..9c12d805 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -1316,6 +1316,22 @@ def do_limited_shuffle_exclude_drops(pool_def, avail, lw=True): must_exit = set(must_exit_lw if lw else must_exit_dw) base_set = LW_Entrances if lw else DW_Entrances entrance_pool = [x for x in base_set if x in avail.entrances and x not in reserved_drops] + if not avail.world.shuffle_ganon[avail.player]: + if avail.world.is_atgt_swapped(avail.player): + if 'Agahnims Tower' in entrance_pool: + connect_two_way('Agahnims Tower', 'Ganons Tower Exit', avail) + entrance_pool.remove('Agahnims Tower') + exits.remove('Ganons Tower Exit') + if not avail.coupled: + avail.decoupled_entrances.remove('Agahnims Tower') + avail.decoupled_exits.remove('Ganons Tower Exit') + elif 'Ganons Tower' in entrance_pool: + connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail) + entrance_pool.remove('Ganons Tower') + exits.remove('Ganons Tower Exit') + if not avail.coupled: + avail.decoupled_entrances.remove('Ganons Tower') + avail.decoupled_exits.remove('Ganons Tower Exit') random.shuffle(entrance_pool) for next_exit in exits: if next_exit not in Connector_Exit_Set: From 691ad1ff9341e017eaea4e3d43d0062eef0a82ec Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 26 Jan 2024 23:29:03 -0600 Subject: [PATCH 120/123] Fixed issue with Inverted Swapped ER placing swap for Vanilla GT --- source/overworld/EntranceShuffle2.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 9c12d805..76b459f8 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -223,10 +223,6 @@ def do_main_shuffle(entrances, exits, avail, mode_def): if not avail.coupled: avail.decoupled_entrances.remove('Agahnims Tower') avail.decoupled_exits.remove('Ganons Tower Exit') - if avail.swapped: - connect_swap('Agahnims Tower', 'Ganons Tower Exit', avail) - entrances.remove('Ganons Tower') - exits.remove('Agahnims Tower Exit') elif 'Ganons Tower' in entrances: connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail) entrances.remove('Ganons Tower') From 6ec9bec54734a7fb138c13ec0ffcdb1f41f18ee3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 26 Jan 2024 23:34:33 -0600 Subject: [PATCH 121/123] Version bump 0.3.4.2 --- CHANGELOG.md | 6 ++++++ OverworldShuffle.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 552be77a..7044b65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.4.2 +- Added Shuffle SFX Instruments as post-gen option +- Fixed some issues with Swapped ER failing to place Old Man Cave +- Changed Inverted 2.0 spawn prompt to display Bomb Shop +- Fixed some minor issues with ER and Vanilla GT + ## 0.3.4.1 - Implemented new District ER mode option - Added alternate boss logic when in GT Ice Basement diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 45da3e4d..3c845a24 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.4.1' +version_number = '0.3.4.2' # branch indicator is intentionally different across branches version_branch = '-u' From 744c2ef5271c8f0f1284a30fef5d350e39961cb7 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 29 Jan 2024 14:20:50 -0600 Subject: [PATCH 122/123] Fix for HMG SP keys in pool --- ItemList.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ItemList.py b/ItemList.py index fa658559..e8e479cc 100644 --- a/ItemList.py +++ b/ItemList.py @@ -331,6 +331,8 @@ def generate_itempool(world, player): # In HMG force swamp smalls in pots to allow getting out of swamp palace placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)' + pool.remove('Small Key (Swamp Palace)') + pool.remove('Small Key (Swamp Palace)') start_inventory = list(world.precollected_items) for item in precollected_items: From 538996b7504042b220dcbbe2c962c4fe7be8a38a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 31 Jan 2024 07:19:57 -0600 Subject: [PATCH 123/123] Temp fix for Smith not deleting on S+Q issue --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index ec8dfb47..aeb6d5e6 100644 --- a/Rom.py +++ b/Rom.py @@ -1405,7 +1405,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180035, 30) # starting max arrows rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode - rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier + #rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier rom.write_byte(0x02AF79, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) rom.write_byte(0x03A943, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) rom.write_byte(0x03A96D, 0xF0 if world.mode[player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader))