From d1b0b57d533b6811aa2f7369481f72d9396de3cf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 21 Aug 2021 18:19:57 -0500 Subject: [PATCH 1/9] Retain Link state on transition in Crossed OW unless bunny --- Rom.py | 2 +- asm/owrando.asm | 9 ++++++--- data/base2current.bps | Bin 141151 -> 141164 bytes 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Rom.py b/Rom.py index 51a7ac10..5dc3e361 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '363c49821f25327f10bc200c98375bde' +RANDOMIZERBASEHASH = '261aa02eb4ee7e56e626361f170de5f4' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 300d4da8..07173255 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -351,12 +351,15 @@ OWNewDestination: .inverted lda $7ef3ca : and.b #$40 : eor #$40 + cmp #$40 : bne .nobunny ; turn into bunny - lda #$17 : sta $5d - lda #$01 : sta $02e0 : sta $56 + lda $5d : cmp #$04 : beq + ; if swimming, continue + lda #$17 : sta $5d + + lda #$01 : sta $02e0 : sta $56 bra .return .nobunny - stz $5d : stz $02e0 : stz $56 + lda $5d : cmp #$17 : bne + ; retain current state unless bunny + stz $5d + + stz $02e0 : stz $56 .return lda $05 : sta $8a diff --git a/data/base2current.bps b/data/base2current.bps index 17f77f5f1b222671e41c05764cdc4c3b6cecbbd4..512a6dc9d18d1f1cfd322a5900c2cf095832b4e8 100644 GIT binary patch delta 124 zcmV-?0E7SE&Is(z2(U>31c)zp(1T3@w@m>75d}s{rUjp3WA&0HvVkp=7l(lemn!Fh zfSU>trmaap$w1H(rCrGc@C2(hc&43z4W(Vl7tjKm3xJuW1%-;jFkqu2O7t(_CbyRc e0nShX50_?F0W<+Yx0_c1yw3=kE9FaYHnYlZoHH>1 delta 111 zcmV-#0FeLe&IsSm2(U>31RXWv%7aY-w@m>75d}FurUjp3WA&0HvVkX)7l(lemn!Fh zfSU>trj Date: Tue, 24 Aug 2021 02:39:24 -0500 Subject: [PATCH 2/9] Expanded and restructured OW Shuffle to include more Crossed options --- BaseClasses.py | 2 +- CLI.py | 2 +- DoorShuffle.py | 2 +- Main.py | 2 +- Mystery.py | 2 +- OverworldShuffle.py | 707 ++++++++---------- Plando.py | 3 +- Plandomizer_Template.txt | 2 +- Rom.py | 6 +- Rules.py | 2 +- resources/app/cli/args.json | 9 +- resources/app/cli/lang/en.json | 10 +- resources/app/gui/lang/en.json | 5 + .../app/gui/randomize/overworld/widgets.json | 11 +- 14 files changed, 344 insertions(+), 421 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b00c3afc..71dbcddc 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2980,7 +2980,7 @@ class Settings(object): @staticmethod def make_code(w, p): code = bytes([ - (dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owCrossed[p] else 0) | (0x08 if w.owMixed[p] else 0) | er_mode[w.shuffle[p]], + (dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owCrossed[p] != 'none' else 0) | (0x08 if w.owMixed[p] else 0) | er_mode[w.shuffle[p]], (logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3) | (sword_mode[w.swords[p]] << 1) | (1 if w.retro[p] else 0), diff --git a/CLI.py b/CLI.py index ccc66e00..1d0f9a43 100644 --- a/CLI.py +++ b/CLI.py @@ -146,7 +146,7 @@ def parse_settings(): "openpyramid": False, "shuffleganon": True, "ow_shuffle": "vanilla", - "ow_crossed": False, + "ow_crossed": "none", "ow_keepsimilar": False, "ow_mixed": False, "ow_fluteshuffle": "vanilla", diff --git a/DoorShuffle.py b/DoorShuffle.py index 94d7ff32..c6644f35 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -215,7 +215,7 @@ def vanilla_key_logic(world, player): analyze_dungeon(key_layout, world, player) world.key_logic[player][builder.name] = key_layout.key_logic log_key_logic(builder.name, key_layout.key_logic) - # if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and not world.owCrossed[player] and not world.owMixed[player] and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: + # if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'none' and not world.owMixed[player] and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: # validate_vanilla_key_logic(world, player) diff --git a/Main.py b/Main.py index 94dc4b9a..407205e4 100644 --- a/Main.py +++ b/Main.py @@ -263,7 +263,7 @@ def main(args, seed=None, fish=None): customize_shops(world, player) balance_money_progression(world) - if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] or world.owMixed[1] or str(world.seed).startswith('M'): + if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] != 'none' or world.owMixed[1] or str(world.seed).startswith('M'): outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' else: outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' diff --git a/Mystery.py b/Mystery.py index 65c506c9..702a67d5 100644 --- a/Mystery.py +++ b/Mystery.py @@ -135,8 +135,8 @@ def roll_settings(weights): overworld_shuffle = get_choice('overworld_shuffle') ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' + ret.ow_crossed = get_choice('overworld_crossed') ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on' - ret.ow_crossed = get_choice('overworld_crossed') == 'on' ret.ow_mixed = get_choice('overworld_mixed') == 'on' overworld_flute = get_choice('flute_shuffle') ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla' diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 05c3d35a..da55a883 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -11,10 +11,108 @@ def link_overworld(world, player): for exitname, destname in temporary_mandatory_connections: connect_two_way(world, exitname, destname, 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] + else: + raise Exception('No parallel edge found for edge %s', edgename) + + def getNewSets(all_set, other_set): + new_all_set = list(map(getParallel, all_set)) + if not all(edge in orig_swaps for edge in new_all_set): + raise Exception('Cannot move a parallel edge without the other') + else: + for edge in new_all_set: + swaps.remove(edge) + new_other_set = getNewSet(other_set) + return (new_all_set, new_other_set) + + def getNewSet(edge_set): + new_set = [] + for edge in edge_set: + if edge in orig_swaps: + new_edge = getParallel(edge) + if new_edge not in orig_swaps: + raise Exception('Cannot move a parallel edge without the other') + new_set.append(new_edge) + swaps.remove(new_edge) + else: + new_set.append(edge) + return new_set + + # swaps edges from one pool to another + orig_swaps = copy.deepcopy(swaps) + new_groups = {} + for group in groups.keys(): + new_groups[group] = ([],[]) + + for group in groups.keys(): + (mode, wrld, dir, terrain, parallel, count) = 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) + allF = all(edge in orig_swaps for edge in forward_set) + allB = all(edge in orig_swaps for edge in back_set) + if not (anyF or anyB): + # no change + new_groups[group][0].append(forward_set) + new_groups[group][1].append(back_set) + elif allF and allB: + # move both sets + 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_groups[(OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][0].append(forward_set) + new_groups[(OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][1].append(back_set) + for edge in forward_set: + swaps.remove(edge) + for edge in back_set: + swaps.remove(edge) + elif anyF or anyB: + if parallel == IsParallel.Yes: + if allF or allB: + # move one set + if allF and not (world.owKeepSimilar[player] and anyB): + (new_forward_set, new_back_set) = getNewSets(forward_set, back_set) + elif allB and not (world.owKeepSimilar[player] and anyF): + (new_back_set, new_forward_set) = getNewSets(back_set, forward_set) + else: + raise Exception('Cannot move an edge out of a Similar group') + new_groups[group][0].append(new_forward_set) + new_groups[group][1].append(new_back_set) + else: + # move individual edges + if not world.owKeepSimilar[player]: + new_groups[group][0].append(getNewSet(forward_set) if anyF else forward_set) + new_groups[group][1].append(getNewSet(back_set) if anyB else back_set) + else: + raise Exception('Cannot move an edge out of a Similar group') + else: + raise NotImplementedError('Cannot move one side of a non-parallel connection') + else: + raise NotImplementedError('Invalid OW Edge swap scenario') + groups = new_groups + + tile_groups = reorganize_tile_groups(world, player) trimmed_groups = copy.deepcopy(OWEdgeGroups) + swapped_edges = list() - # adjust Frog/Dig Game swap manually due to NP/P relationship with LW - if world.owShuffle[player] == 'parallel' and not world.owKeepSimilar[player]: + # restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship + if world.owKeepSimilar[player]: + 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)) + 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)) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Kakariko Suburb WS']) + trimmed_groups[group] = (forward_edges, back_edges) + else: for group in trimmed_groups.keys(): (std, region, axis, terrain, _, _) = group (forward_edges, back_edges) = trimmed_groups[group] @@ -29,131 +127,17 @@ def link_overworld(world, player): trimmed_groups[group] = (forward_edges, back_edges) # tile shuffle + logging.getLogger('').debug('Swapping overworld tiles') if world.owMixed[player]: - tile_groups = {} - for (name, groupType) in OWTileGroups.keys(): - if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks']: - if world.shuffle[player] in ['vanilla', 'simple', 'dungeonssimple']: - tile_groups[(name,)] = ([], [], []) - else: - tile_groups[(name, groupType)] = ([], [], []) - - for (name, groupType) in OWTileGroups.keys(): - if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks']: - (lw_owids, dw_owids) = OWTileGroups[(name, groupType,)] - if world.shuffle[player] in ['vanilla', 'simple', 'dungeonssimple']: - (exist_owids, exist_lw_regions, exist_dw_regions) = tile_groups[(name,)] - exist_owids.extend(lw_owids) - exist_owids.extend(dw_owids) - for owid in lw_owids: - exist_lw_regions.extend(OWTileRegions.inverse[owid]) - for owid in dw_owids: - exist_dw_regions.extend(OWTileRegions.inverse[owid]) - tile_groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions) - else: - (exist_owids, exist_lw_regions, exist_dw_regions) = tile_groups[(name, groupType)] - exist_owids.extend(lw_owids) - exist_owids.extend(dw_owids) - for owid in lw_owids: - exist_lw_regions.extend(OWTileRegions.inverse[owid]) - for owid in dw_owids: - exist_dw_regions.extend(OWTileRegions.inverse[owid]) - tile_groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions) + swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], player) - # tile shuffle happens here, the groups that remain in the list are the tiles that get swapped - removed = list() - for group in tile_groups.keys(): - if random.randint(0, 1): - removed.append(group) - for group in removed: - tile_groups.pop(group, None) + # move swapped regions/edges to other world + performSwap(trimmed_groups, swapped_edges) + assert len(swapped_edges) == 0, 'Not all edges were swapped successfully: ' + ', '.join(swapped_edges ) - # save shuffled tiles to world object - for group in tile_groups.keys(): - (owids, lw_regions, dw_regions) = tile_groups[group] - (exist_owids, exist_lw_regions, exist_dw_regions) = world.owswaps[player] - exist_owids.extend(owids) - exist_lw_regions.extend(lw_regions) - exist_dw_regions.extend(dw_regions) - world.owswaps[player] = [exist_owids, exist_lw_regions, exist_dw_regions] - - # replace LW edges with DW - ignore_list = list() #TODO: Remove ignore_list when special OW areas are included in pool - for edgeset in temporary_mandatory_connections: - for edge in edgeset: - ignore_list.append(edge) - - swapped_edges = list() - def getSwappedEdges(world, lst, player): - for regionname in lst: - region = world.get_region(regionname, player) - for exit in region.exits: - if exit.spot_type == 'OWEdge' and exit.name not in ignore_list: - swapped_edges.append(exit.name) - - getSwappedEdges(world, world.owswaps[player][1], player) - getSwappedEdges(world, world.owswaps[player][2], player) - - def performSwap(groups, swaps, nonParallelOnly=False): - try: - for group in groups.keys(): - (mode, wrld, dir, terrain, parallel, count) = group - for p in range(0, len(groups[group])): - edgepool = groups[group][p] - for s in range(0, len(edgepool)): - if s <= len(edgepool): - for e in range(0, len(edgepool[s])): - if len(edgepool) > 0 and edgepool[s][e] in swaps: - if parallel == IsParallel.Yes: - if not nonParallelOnly: - if wrld == WorldType.Light and edgepool[s][e] in parallel_links: - logging.getLogger('').debug('%s was moved', edgepool[s][e]) - swaps.remove(edgepool[s][e]) - groups[group][p][s][e] = parallel_links[edgepool[s][e]] - elif wrld == WorldType.Dark and edgepool[s][e] in parallel_links.inverse: - logging.getLogger('').debug('%s was moved', edgepool[s][e]) - swaps.remove(edgepool[s][e]) - groups[group][p][s][e] = parallel_links.inverse[edgepool[s][e]][0] - else: - for edge in edgepool[s]: - logging.getLogger('').debug('%s was moved', edge) - swaps.remove(edge) - groups[(mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][p].append(edgepool[s]) - groups[group][p].remove(edgepool[s]) - except IndexError: - #TODO: Figure out a way to handle index changes on the fly when removing items - logging.getLogger('').warning('OW Tile Swap encountered minor IndexError... retrying') - - if world.owShuffle[player] != 'parallel' and 0x28 in world.owswaps[player][0]: # handle Frog/Dig Game swap manually due to NP/P relationship with LW - trimmed_groups[(OpenStd.Open, WorldType.Dark, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][0].append(['Maze Race ES']) - trimmed_groups[(OpenStd.Open, WorldType.Dark, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][1].append(['Kakariko Suburb WS']) - trimmed_groups[(OpenStd.Open, WorldType.Light, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][0].remove(['Maze Race ES']) - trimmed_groups[(OpenStd.Open, WorldType.Light, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][1].remove(['Kakariko Suburb WS']) - - trimmed_groups[(OpenStd.Open, WorldType.Light, PolSlot.EastWest, Terrain.Land, IsParallel.No, 2)][0].append(['Dig Game EC', 'Dig Game ES']) - trimmed_groups[(OpenStd.Open, WorldType.Light, PolSlot.EastWest, Terrain.Land, IsParallel.No, 2)][1].append(['Frog WC', 'Frog WS']) - trimmed_groups[(OpenStd.Open, WorldType.Dark, PolSlot.EastWest, Terrain.Land, IsParallel.No, 2)] = [[],[]] - - swapped_edges.remove('Maze Race ES') - swapped_edges.remove('Kakariko Suburb WS') - swapped_edges.remove('Dig Game EC') - swapped_edges.remove('Dig Game ES') - swapped_edges.remove('Frog WC') - swapped_edges.remove('Frog WS') - - tries = 5 - while tries > 0: - performSwap(trimmed_groups, swapped_edges) - if len(swapped_edges) == 0: - tries = 0 - continue - tries -= 1 - assert len(swapped_edges) == 0 - - #move swapped regions to other world update_world_regions(world, player) - # make new connections + # apply tile logical connections for owid in ow_connections.keys(): if (world.mode[player] == 'inverted') == (owid in world.owswaps[player][0] and world.owMixed[player]): for (exitname, regionname) in ow_connections[owid][0]: @@ -162,10 +146,50 @@ def link_overworld(world, player): for (exitname, regionname) in ow_connections[owid][1]: connect_simple(world, exitname, regionname, player) - connected_edges = [] + # crossed shuffle + logging.getLogger('').debug('Crossing overworld edges') + if world.owCrossed[player] in ['grouped', 'limited']: + if world.owCrossed[player] == 'grouped': + crossed_edges = shuffle_tiles(world, tile_groups, [[],[],[]], player) + elif world.owCrossed[player] in ['limited', 'chaos']: + crossed_edges = list() + crossed_candidates = list() + for group in trimmed_groups.keys(): + (mode, wrld, dir, terrain, parallel, count) = group + if parallel == IsParallel.Yes and wrld == WorldType.Light and (mode == OpenStd.Open or world.mode[player] != 'standard'): + for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): + if world.owKeepSimilar[player]: + if world.owCrossed[player] == 'chaos' and random.randint(0, 1): + for edge in forward_set: + crossed_edges.append(edge) + elif world.owCrossed[player] == 'limited': + crossed_candidates.append(forward_set) + else: + for edge in forward_set: + if world.owCrossed[player] == 'chaos' and random.randint(0, 1): + crossed_edges.append(edge) + elif world.owCrossed[player] == 'limited': + crossed_candidates.append(forward_set) + break + if world.owCrossed[player] == 'limited': + random.shuffle(crossed_candidates) + for edge_set in crossed_candidates[:9]: + for edge in edge_set: + crossed_edges.append(edge) + for edge in copy.deepcopy(crossed_edges): + if edge in parallel_links: + crossed_edges.append(parallel_links[edge]) + elif edge in parallel_links.inverse: + crossed_edges.append(parallel_links.inverse[edge][0]) + + performSwap(trimmed_groups, crossed_edges) + assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges) # layout shuffle - if world.owShuffle[player] == 'vanilla' and not world.owCrossed[player]: + logging.getLogger('').debug('Shuffling overworld layout') + connected_edges = [] + + if world.owShuffle[player] == 'vanilla': # vanilla transitions groups = list(trimmed_groups.values()) for (forward_edge_sets, back_edge_sets) in groups: @@ -174,100 +198,61 @@ def link_overworld(world, player): 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) - - assert len(connected_edges) == len(default_connections) * 2, connected_edges else: - if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel': + if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: for exitname, destname in parallelsimilar_connections: connect_two_way(world, exitname, destname, player, connected_edges) - if world.owShuffle[player] == 'vanilla' and world.owCrossed[player]: - if world.mode[player] == 'standard': - # connect vanilla std - for group in trimmed_groups.keys(): - (std, _, _, _, _, _) = group - if std == OpenStd.Standard: - (forward_set, back_set) = trimmed_groups[group] - for (forward_edges, back_edges) in zip(forward_set, back_set): - for (forward_edge, back_edge) in zip(forward_edges, back_edges): - connect_two_way(world, forward_edge, back_edge, player, connected_edges) - - # connect non-parallel edges - for group in trimmed_groups.keys(): - (_, _, _, _, parallel, _) = group - if parallel == IsParallel.No: - (forward_set, back_set) = trimmed_groups[group] - for (forward_edges, back_edges) in zip(forward_set, back_set): - for (forward_edge, back_edge) in zip(forward_edges, back_edges): - if forward_edge not in connected_edges and back_edge not in connected_edges: - connect_two_way(world, forward_edge, back_edge, player, connected_edges) - #TODO: Remove, just for testing for exitname, destname in test_connections: connect_two_way(world, exitname, destname, player, connected_edges) connect_custom(world, connected_edges, player) + # layout shuffle trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) - groups = reorganize_groups(world, trimmed_groups, player) - # all layout shuffling occurs here - if world.owShuffle[player] != 'vanilla': - # layout shuffle - if world.mode[player] == 'standard': - random.shuffle(groups[2:]) # keep first 2 groups (Standard) first - else: - random.shuffle(groups) - - for (forward_edge_sets, back_edge_sets) in groups: - assert len(forward_edge_sets) == len(back_edge_sets) - 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] - 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] - 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]) + if world.mode[player] == 'standard': + random.shuffle(groups[2:]) # keep first 2 groups (Standard) first else: - # vanilla/crossed shuffle - for (forward_edge_sets, back_edge_sets) in groups: - assert len(forward_edge_sets) == len(back_edge_sets) - for (forward_set, back_set) in zip(forward_edge_sets, back_edge_sets): - assert len(forward_set) == len(back_set) - swapped = random.randint(0, 1) - for (forward_edge, back_edge) in zip(forward_set, back_set): - if forward_edge not in connected_edges and back_edge not in connected_edges: - if swapped: - forward_edge = parallel_links[forward_edge] if forward_edge in parallel_links else parallel_links.inverse[forward_edge][0] + random.shuffle(groups) + + for (forward_edge_sets, back_edge_sets) in groups: + assert len(forward_edge_sets) == len(back_edge_sets) + 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] + 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] + 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) - - assert len(connected_edges) == len(default_connections) * 2, 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]) + assert len(connected_edges) == len(default_connections) * 2, connected_edges # flute shuffle def connect_flutes(flute_destinations): @@ -352,7 +337,20 @@ def connect_simple(world, exitname, regionname, 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) + + 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): + return + else: + raise Exception('Edges \'%s\' and \'%s\' already connected elsewhere', edgename1, edgename2) + # if these were already connected somewhere, remove the backreference if edge1.connected_region is not None: edge1.connected_region.entrances.remove(edge1) @@ -361,17 +359,10 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): edge1.connect(edge2.parent_region) edge2.connect(edge1.parent_region) - x = world.check_for_owedge(edgename1, player) - y = world.check_for_owedge(edgename2, player) - if x is None: - logging.getLogger('').error('%s is not a valid edge.', edgename1) - elif y is None: - logging.getLogger('').error('%s is not a valid edge.', edgename2) - else: - x.dest = y - y.dest = x + x.dest = y + y.dest = x - if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player]: + if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none': world.spoiler.set_overworld(edgename2, edgename1, 'both', player) if connected_edges is not None: @@ -379,7 +370,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): connected_edges.append(edgename2) # connecting parallel connections - if world.owShuffle[player] == 'parallel' or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player]): + if world.owShuffle[player] in ['vanilla', 'parallel']: if (edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys()): try: parallel_forward_edge = parallel_links[edgename1] if edgename1 in parallel_links.keys() else parallel_links.inverse[edgename1][0] @@ -390,6 +381,77 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): # TODO: Figure out why non-parallel edges are getting into parallel groups raise KeyError('No parallel edge for edge %s' % edgename2) +def shuffle_tiles(world, groups, result_list, player): + swapped_edges = list() + + # tile shuffle happens here + removed = list() + for group in groups.keys(): + if random.randint(0, 1): + removed.append(group) + + # save shuffled tiles to list + for group in groups.keys(): + if group not in removed: + (owids, lw_regions, dw_regions) = groups[group] + (exist_owids, exist_lw_regions, exist_dw_regions) = result_list + exist_owids.extend(owids) + exist_lw_regions.extend(lw_regions) + exist_dw_regions.extend(dw_regions) + result_list = [exist_owids, exist_lw_regions, exist_dw_regions] + + # replace LW edges with DW + ignore_list = list() #TODO: Remove ignore_list when special OW areas are included in pool + for edgeset in temporary_mandatory_connections: + for edge in edgeset: + ignore_list.append(edge) + + if world.owCrossed[player] != 'polar': + # in polar, the actual edge connections remain vanilla + def getSwappedEdges(world, lst, player): + for regionname in lst: + region = world.get_region(regionname, player) + for exit in region.exits: + if exit.spot_type == 'OWEdge' and exit.name not in ignore_list: + swapped_edges.append(exit.name) + + getSwappedEdges(world, result_list[1], player) + getSwappedEdges(world, result_list[2], player) + + return swapped_edges + +def reorganize_tile_groups(world, player): + groups = {} + for (name, groupType) in OWTileGroups.keys(): + if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks']: + if world.shuffle[player] in ['vanilla', 'simple', 'dungeonssimple']: + groups[(name,)] = ([], [], []) + else: + groups[(name, groupType)] = ([], [], []) + + for (name, groupType) in OWTileGroups.keys(): + if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks']: + (lw_owids, dw_owids) = OWTileGroups[(name, groupType,)] + if world.shuffle[player] in ['vanilla', 'simple', 'dungeonssimple']: + (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name,)] + exist_owids.extend(lw_owids) + exist_owids.extend(dw_owids) + for owid in lw_owids: + exist_lw_regions.extend(OWTileRegions.inverse[owid]) + for owid in dw_owids: + exist_dw_regions.extend(OWTileRegions.inverse[owid]) + groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions) + else: + (exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType)] + exist_owids.extend(lw_owids) + exist_owids.extend(dw_owids) + for owid in lw_owids: + exist_lw_regions.extend(OWTileRegions.inverse[owid]) + for owid in dw_owids: + exist_dw_regions.extend(OWTileRegions.inverse[owid]) + groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions) + return groups + def remove_reserved(world, groupedlist, connected_edges, player): new_grouping = {} for group in groupedlist.keys(): @@ -405,7 +467,7 @@ def remove_reserved(world, groupedlist, connected_edges, player): back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges) # remove parallel edges from pool, since they get added during shuffle - if (not world.owCrossed[player] and world.owShuffle[player] == 'parallel') and region == WorldType.Dark: + if world.owShuffle[player] == 'parallel' and region == WorldType.Dark: for edge in parallel_links: forward_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in forward_edges) back_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in back_edges) @@ -428,250 +490,87 @@ def reorganize_groups(world, groups, player): # predefined shuffle groups get reorganized here # this restructures the candidate pool based on the chosen settings if world.owShuffle[player] == 'full': - if world.owCrossed[player]: - if world.owKeepSimilar[player]: - if world.mode[player] == 'standard': - # tuple goes to (A,_,C,D,_,F) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (std, _, axis, terrain, _, count) = group - new_grouping[(std, axis, terrain, count)] = ([], []) - - for group in grouping.keys(): - (std, _, axis, terrain, _, count) = group - (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain, count)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(std, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - # tuple goes to (_,_,C,D,_,F) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (_, _, axis, terrain, _, count) = group - new_grouping[(axis, terrain, count)] = ([], []) - - for group in grouping.keys(): - (_, _, axis, terrain, _, count) = group - (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain, count)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(axis, terrain, count)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - if world.mode[player] == 'standard': - # tuple goes to (A,_,C,D,_,_) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (std, _, axis, terrain, _, _) = group - new_grouping[(std, axis, terrain)] = ([], []) - - for group in grouping.keys(): - (std, _, axis, terrain, _, _) = group - (forward_edges, back_edges) = grouping[group] - forward_edges = [[i] for l in forward_edges for i in l] - back_edges = [[i] for l in back_edges for i in l] - - (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(std, axis, terrain)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - # tuple goes to (_,_,C,D,_,_) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (_, _, axis, terrain, _, _) = group - new_grouping[(axis, terrain)] = ([], []) - - for group in grouping.keys(): - (_, _, axis, terrain, _, _) = group - (forward_edges, back_edges) = grouping[group] - forward_edges = [[i] for l in forward_edges for i in l] - back_edges = [[i] for l in back_edges for i in l] - - (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(axis, terrain)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - if world.owKeepSimilar[player]: - if world.mode[player] == 'standard': - # tuple goes to (A,B,C,D,_,F) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (std, region, axis, terrain, _, count) = group - new_grouping[(std, region, axis, terrain, count)] = ([], []) - - for group in grouping.keys(): - (std, region, axis, terrain, _, count) = group - (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, count)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(std, region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - # tuple goes to (_,B,C,D,_,F) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (_, region, axis, terrain, _, count) = group - new_grouping[(region, axis, terrain, count)] = ([], []) - - for group in grouping.keys(): - (_, region, axis, terrain, _, count) = group - (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, count)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - if world.mode[player] == 'standard': - # tuple goes to (A,B,C,D,_,_) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (std, region, axis, terrain, _, _) = group - new_grouping[(std, region, axis, terrain)] = ([], []) - - for group in grouping.keys(): - (std, region, axis, terrain, _, _) = group - (forward_edges, back_edges) = grouping[group] - forward_edges = [[i] for l in forward_edges for i in l] - back_edges = [[i] for l in back_edges for i in l] - - (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(std, region, axis, terrain)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - # tuple goes to (_,B,C,D,_,_) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (_, region, axis, terrain, _, _) = group - new_grouping[(region, axis, terrain)] = ([], []) - - for group in grouping.keys(): - (_, region, axis, terrain, _, _) = group - (forward_edges, back_edges) = grouping[group] - forward_edges = [[i] for l in forward_edges for i in l] - back_edges = [[i] for l in back_edges for i in l] - - (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(region, axis, terrain)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - elif world.owShuffle[player] == 'parallel' and world.owCrossed[player]: if world.owKeepSimilar[player]: if world.mode[player] == 'standard': - # tuple goes to (A,_,C,D,E,F) + # tuple goes to (A,B,C,D,_,F) for grouping in (groups,): new_grouping = {} for group in grouping.keys(): - (std, _, axis, terrain, parallel, count) = group - new_grouping[(std, axis, terrain, parallel, count)] = ([], []) + (std, region, axis, terrain, _, count) = group + new_grouping[(std, region, axis, terrain, count)] = ([], []) for group in grouping.keys(): - (std, _, axis, terrain, parallel, count) = group + (std, region, axis, terrain, _, count) = group (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain, parallel, count)] + (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, count)] exist_forward_edges.extend(forward_edges) exist_back_edges.extend(back_edges) - new_grouping[(std, axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges) + new_grouping[(std, region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) return list(new_grouping.values()) else: - # tuple goes to (_,_,C,D,E,F) + # tuple goes to (_,B,C,D,_,F) for grouping in (groups,): new_grouping = {} for group in grouping.keys(): - (_, _, axis, terrain, parallel, count) = group - new_grouping[(axis, terrain, parallel, count)] = ([], []) + (_, region, axis, terrain, _, count) = group + new_grouping[(region, axis, terrain, count)] = ([], []) for group in grouping.keys(): - (_, _, axis, terrain, parallel, count) = group + (_, region, axis, terrain, _, count) = group (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain, parallel, count)] + (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, count)] exist_forward_edges.extend(forward_edges) exist_back_edges.extend(back_edges) - new_grouping[(axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges) + new_grouping[(region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) return list(new_grouping.values()) else: if world.mode[player] == 'standard': - # tuple goes to (A,_,C,D,E,_) + # tuple goes to (A,B,C,D,_,_) for grouping in (groups,): new_grouping = {} for group in grouping.keys(): - (std, _, axis, terrain, parallel, _) = group - new_grouping[(std, axis, terrain, parallel)] = ([], []) + (std, region, axis, terrain, _, _) = group + new_grouping[(std, region, axis, terrain)] = ([], []) for group in grouping.keys(): - (std, _, axis, terrain, parallel, _) = group + (std, region, axis, terrain, _, _) = group (forward_edges, back_edges) = grouping[group] forward_edges = [[i] for l in forward_edges for i in l] back_edges = [[i] for l in back_edges for i in l] - (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain, parallel)] + (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain)] exist_forward_edges.extend(forward_edges) exist_back_edges.extend(back_edges) - new_grouping[(std, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) + new_grouping[(std, region, axis, terrain)] = (exist_forward_edges, exist_back_edges) return list(new_grouping.values()) else: - # tuple goes to (_,_,C,D,E,_) + # tuple goes to (_,B,C,D,_,_) for grouping in (groups,): new_grouping = {} for group in grouping.keys(): - (_, _, axis, terrain, parallel, _) = group - new_grouping[(axis, terrain, parallel)] = ([], []) + (_, region, axis, terrain, _, _) = group + new_grouping[(region, axis, terrain)] = ([], []) for group in grouping.keys(): - (_, _, axis, terrain, parallel, _) = group + (_, region, axis, terrain, _, _) = group (forward_edges, back_edges) = grouping[group] forward_edges = [[i] for l in forward_edges for i in l] back_edges = [[i] for l in back_edges for i in l] - (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain, parallel)] + (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain)] exist_forward_edges.extend(forward_edges) exist_back_edges.extend(back_edges) - new_grouping[(axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) + new_grouping[(region, axis, terrain)] = (exist_forward_edges, exist_back_edges) return list(new_grouping.values()) - elif world.owShuffle[player] == 'parallel' or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player]): + elif world.owShuffle[player] == 'parallel': if world.owKeepSimilar[player]: if world.mode[player] == 'standard': # tuple stays (A,B,C,D,E,F) diff --git a/Plando.py b/Plando.py index c1cd8c23..fa9fa848 100755 --- a/Plando.py +++ b/Plando.py @@ -162,8 +162,7 @@ def prefill_world(world, plando, text_patches): world.owShuffle = {1: modestr.strip()} elif line.startswith('!owCrossed'): _, modestr = line.split(':', 1) - modestr = modestr.strip().lower() - world.owCrossed = {1: True if modestr in ('true', 'yes', 'on', 'enabled') else False} + world.owCrossed = {1: modestr.strip()} elif line.startswith('!owKeepSimilar'): _, modestr = line.split(':', 1) modestr = modestr.strip().lower() diff --git a/Plandomizer_Template.txt b/Plandomizer_Template.txt index dce3f005..41782f89 100644 --- a/Plandomizer_Template.txt +++ b/Plandomizer_Template.txt @@ -246,6 +246,6 @@ Ganon: Triforce # set Overworld connections (lines starting with $, separate edges with =) !owShuffle: parallel #!owMixed: true # Mixed OW not supported yet -!owCrossed: true +!owCrossed: none !owKeepSimilar: true $Links House NE = Kakariko Village SE diff --git a/Rom.py b/Rom.py index 5dc3e361..47bf48c1 100644 --- a/Rom.py +++ b/Rom.py @@ -646,16 +646,16 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # patch overworld edges inverted_buffer = [0] * 0x82 - if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] or world.owMixed[player]: + if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player]: owMode = 0 if world.owShuffle[player] == 'parallel': owMode = 1 elif world.owShuffle[player] == 'full': owMode = 2 - if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player]): + if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] in ['limited', 'chaos']): owMode |= 0x100 - if world.owCrossed[player]: + if world.owCrossed[player] != 'none': owMode |= 0x200 world.fix_fake_world[player] = True if world.owMixed[player]: diff --git a/Rules.py b/Rules.py index c3842593..abc447ba 100644 --- a/Rules.py +++ b/Rules.py @@ -2053,7 +2053,7 @@ def set_inverted_big_bomb_rules(world, player): else: raise Exception('No logic found for routing from %s to the pyramid.' % bombshop_entrance.name) - if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player]: + if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none': add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) #temp disable progression until routing to Pyramid get be guaranteed diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index a1d73102..edbb11f0 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -117,8 +117,13 @@ ] }, "ow_crossed": { - "action": "store_true", - "type": "bool" + "choices": [ + "none", + "polar", + "grouped", + "limited", + "chaos" + ] }, "ow_keepsimilar": { "action": "store_true", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 3968360d..c4ff9781 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -202,7 +202,15 @@ " will have an independent map shape." ], "ow_crossed": [ - "This allows cross-world connections to occur on the overworld." ], + "This allows cross-world connections to occur on the overworld.", + "None: No transitions are cross-world connections.", + "Polar: Only used when Mixed is enabled. This retains original", + " connections even when overworld tiles are swapped.", + "Limited: Exactly nine transitions are randomly chosen as", + " cross-world connections (to emulate the nine portals).", + "Chaos: Every transition has a 50/50 chance to become a", + " crossworld connection." + ], "ow_keepsimilar": [ "This keeps similar edge transitions together. ie. the two west edges on", "Potion Shop will be paired with another similar pair." ], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 7612c1bb..12e8bef8 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -118,6 +118,11 @@ "randomizer.overworld.overworldshuffle.full": "Full", "randomizer.overworld.crossed": "Crossed", + "randomizer.overworld.crossed.none": "None", + "randomizer.overworld.crossed.polar": "Polar", + "randomizer.overworld.crossed.grouped": "Grouped", + "randomizer.overworld.crossed.limited": "Limited", + "randomizer.overworld.crossed.chaos": "Chaos", "randomizer.overworld.keepsimilar": "Keep Similar Edges Together", "randomizer.overworld.mixed": "Mixed", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index 15b89cab..2d15fdd3 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -21,8 +21,15 @@ }, "rightOverworldFrame": { "crossed": { - "type": "checkbox", - "default": false + "type": "selectbox", + "default": "vanilla", + "options": [ + "none", + "polar", + "grouped", + "limited", + "chaos" + ] }, "mixed": { "type": "checkbox", From 16e004060f66b7cead6a290b7f86d9cf7cb6de6e Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 24 Aug 2021 04:53:55 -0500 Subject: [PATCH 3/9] Fixed issue with Grouped/Mixed+None/Chaos Crossed shuffles --- OverworldShuffle.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index da55a883..5ae6fdbb 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -93,7 +93,7 @@ def link_overworld(world, player): raise NotImplementedError('Cannot move one side of a non-parallel connection') else: raise NotImplementedError('Invalid OW Edge swap scenario') - groups = new_groups + return new_groups tile_groups = reorganize_tile_groups(world, player) trimmed_groups = copy.deepcopy(OWEdgeGroups) @@ -132,7 +132,7 @@ def link_overworld(world, player): swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], player) # move swapped regions/edges to other world - performSwap(trimmed_groups, swapped_edges) + trimmed_groups = performSwap(trimmed_groups, swapped_edges) assert len(swapped_edges) == 0, 'Not all edges were swapped successfully: ' + ', '.join(swapped_edges ) update_world_regions(world, player) @@ -148,7 +148,7 @@ def link_overworld(world, player): # crossed shuffle logging.getLogger('').debug('Crossing overworld edges') - if world.owCrossed[player] in ['grouped', 'limited']: + if world.owCrossed[player] in ['grouped', 'limited', 'chaos']: if world.owCrossed[player] == 'grouped': crossed_edges = shuffle_tiles(world, tile_groups, [[],[],[]], player) elif world.owCrossed[player] in ['limited', 'chaos']: @@ -182,7 +182,7 @@ def link_overworld(world, player): elif edge in parallel_links.inverse: crossed_edges.append(parallel_links.inverse[edge][0]) - performSwap(trimmed_groups, crossed_edges) + trimmed_groups = performSwap(trimmed_groups, crossed_edges) assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges) # layout shuffle From 0f8efed663b35928cc9ac0239d18784018573768 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 24 Aug 2021 23:26:57 -0500 Subject: [PATCH 4/9] Fixed Limited OW to abide by Keep Similar option --- OverworldShuffle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 5ae6fdbb..d705f1a1 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -169,8 +169,7 @@ def link_overworld(world, player): if world.owCrossed[player] == 'chaos' and random.randint(0, 1): crossed_edges.append(edge) elif world.owCrossed[player] == 'limited': - crossed_candidates.append(forward_set) - break + crossed_candidates.append(edge) if world.owCrossed[player] == 'limited': random.shuffle(crossed_candidates) for edge_set in crossed_candidates[:9]: From 151506d130cd66f022d0b1beb48c5169090b11e3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 25 Aug 2021 02:22:21 -0500 Subject: [PATCH 5/9] Play SFX on crossworld transition in Crossed OW --- Rom.py | 2 +- asm/owrando.asm | 6 ++++-- data/base2current.bps | Bin 141164 -> 141181 bytes 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Rom.py b/Rom.py index 47bf48c1..d27a0f04 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '261aa02eb4ee7e56e626361f170de5f4' +RANDOMIZERBASEHASH = 'df12af8b5970ecc392b29b45595d9351' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 07173255..2ad5e8cc 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -342,10 +342,12 @@ OWNewDestination: ; crossed OW shuffle LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : beq .return - ldx $05 : lda.l OWTileWorldAssoc,x : sta.l $7ef3ca ; change world + ldx $05 : lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq + + sta.l $7ef3ca ; change world + lda #$38 : sta $012f ; play sfx - #$3b is an alternative ; toggle bunny mode - lda $7ef357 : bne .nobunny + + lda $7ef357 : bne .nobunny lda.l InvertedMode : bne .inverted lda $7ef3ca : and.b #$40 : bra + .inverted lda $7ef3ca : and.b #$40 : eor #$40 diff --git a/data/base2current.bps b/data/base2current.bps index 512a6dc9d18d1f1cfd322a5900c2cf095832b4e8..10f67fd0a1e0181683d24b54a8b6ce7779b974b7 100644 GIT binary patch delta 149 zcmV;G0BZm2&ItX^2(U>31l%bj)`Lv}w@m>7@B{%-mp%poAXinU1-}5gs?W;ve((v8 z%JY7yIE^m>k|na4Ew4&|Fwh8>5JdKYfSVu@rmaap$w1H(rCrGc@C2(%c&43z4W(Vl z7tjKm3xJuW1%-;jFkqu2O7sKZJh!9<0nShYc{P`GR{=8tSGTBF0ld!$5HG=y*vne_ Du>U|u delta 132 zcmV-~0DJ%a&Is(z2(U>31c)zp(1T3@w@m>7@B{$`mp%poAVx~21)pML^^zsBfi064 zhk*!}D(8WKn+g%8tw})1K+qGVUC9LS1gkZ8rk#KdrCrGv&;pwafSIKQg^I#3V51{S m^e^Bhx3vZV&QJmmHkXB00W$$Xx4BmVyw3=kE9FaYHnYmQ!!^JF From 0b05ec9927d7261e498fec9898fdd079fedf6bb0 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 25 Aug 2021 02:22:59 -0500 Subject: [PATCH 6/9] Improved accuracy of Crossed OW flag in ROM --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index d27a0f04..15857882 100644 --- a/Rom.py +++ b/Rom.py @@ -655,7 +655,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] in ['limited', 'chaos']): owMode |= 0x100 - if world.owCrossed[player] != 'none': + if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]): owMode |= 0x200 world.fix_fake_world[player] = True if world.owMixed[player]: From a7554530613d8005194e0184b15f50531f6daca1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 26 Aug 2021 00:55:37 -0500 Subject: [PATCH 7/9] Updated baserom - Fixes TR Pegs issue when fluting direct to TR Pegs from area with hammerpegs - Changed Crossed OW to only affect Link state on world change --- Rom.py | 2 +- asm/owrando.asm | 32 ++++++++++++++++---------------- data/base2current.bps | Bin 141181 -> 141185 bytes 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Rom.py b/Rom.py index 15857882..c9ebb8ad 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'df12af8b5970ecc392b29b45595d9351' +RANDOMIZERBASEHASH = '712ebda1ef6818c59cde0371b5a4e4a9' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 2ad5e8cc..8082d38f 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -342,26 +342,26 @@ OWNewDestination: ; crossed OW shuffle LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : beq .return - ldx $05 : lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq + + ldx $05 : lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return sta.l $7ef3ca ; change world lda #$38 : sta $012f ; play sfx - #$3b is an alternative - ; toggle bunny mode - + lda $7ef357 : bne .nobunny - lda.l InvertedMode : bne .inverted - lda $7ef3ca : and.b #$40 : bra + - .inverted lda $7ef3ca : and.b #$40 : eor #$40 - + cmp #$40 : bne .nobunny - ; turn into bunny - lda $5d : cmp #$04 : beq + ; if swimming, continue - lda #$17 : sta $5d - + lda #$01 : sta $02e0 : sta $56 - bra .return + ; toggle bunny mode + + lda $7ef357 : bne .nobunny + lda.l InvertedMode : bne .inverted + lda $7ef3ca : and.b #$40 : bra + + .inverted lda $7ef3ca : and.b #$40 : eor #$40 + + cmp #$40 : bne .nobunny + ; turn into bunny + lda $5d : cmp #$04 : beq + ; if swimming, continue + lda #$17 : sta $5d + + lda #$01 : sta $02e0 : sta $56 + bra .return - .nobunny - lda $5d : cmp #$17 : bne + ; retain current state unless bunny - stz $5d - + stz $02e0 : stz $56 + .nobunny + lda $5d : cmp #$17 : bne + ; retain current state unless bunny + stz $5d + + stz $02e0 : stz $56 .return lda $05 : sta $8a diff --git a/data/base2current.bps b/data/base2current.bps index 10f67fd0a1e0181683d24b54a8b6ce7779b974b7..997df3d6bc406f60caf48ec8383651712d25f0be 100644 GIT binary patch delta 5560 zcmW+(30xCL7tiE?a3`RM0vgr>5X6Xh;f0Dqm1kYd|z(6)33~|K; zv%o|Qh%1eX;DLgz1g%8tr+Cz>myNc@qxEe4h9T7N*!#waOCMP(EjRc5lfkQ!zAgg6A?d z+E9pwP)5Sj#5q4Q&_peqB$^&)EN9{-7Q9XIAQN^QiSq@>DcfZvChV$rsNHgEdrT9j;5VXpKSL*atRb@APa(zmT4EG@E1C=>aDq5CU`H9ggtFU2 z+_hqmL1_sM%oRt2L$ENu(#G}9|7%Ca;URQ>08;PYbNiq}6u+fr@K>FKxB*52* zfFABEK^k1|p6$2r7D_Clve}P>l)|8$41aU~R9;=Y$-=hWx1xz$O!cUJU6@yWtG!JdM7$a69w=` z=_vUkBO1?X3HQJ3$N(2kPBoisth(;j2||G?Hy*=Z!MVLnNO-N6MVo=Lz3KlIE}Km8wa9xxI|$~ouH zM&et2xN}eS)V-X_fyHx53u#sGZIrRoMDV|GPUk@uo9nM%xe(H*nt2U5Gq8ewzJxlp zj$*FodRh!c6S7V&!=@o7vxBlsi>V6LCSC94`xMax2NhoZBfls?&cn>sVP!g+@GL>w z!|<9<4XBbPwi-|xG7|GB&e>^H8Q^DLso*v|;uY=VfQ!_m##y?tz@!=h|L|Hi%yb29 z*V4q*My^*24fft3l1f=oB%MQ1QHLvtb!CWvaGiJH$bDtV8=s+SLw$rJcj| z+Gc7QdaNauRn^KDY2!%VXKra9vWjqV{~39YmgrZ_3c}bPqiRYuavoCo8Sw7l@y0z#(`>LqHS?N zOaxd7izX(Go>h#t>$GkRrzqCoFpoLkslELxmy%Db4i_VD4RwN9HSfnn(&Tg!9mQw} zzU+@SFeye9Kz@%54&yq9JJH4z8`oq0uWkBN0Km7l#DvepA)_uJE1-WD@{m%xB*c{O zks5iAS1W?bi5OdZ;x_<<+rFA*6alHteYQ~ILtR9qCvgV1V&c6maNam~aM3n*;Sq1J z!X{dgCJJ=gQR6vrcNnR3u`85?O?G!zF!2tqUDXe~vuRdIy+Ex^|HUB*@U~6fv;hEz z?eOM5B;Y>Wm%9SIv_&9KxFCD(z08np{2(`x8}wdXrw)&_1L&CrLtTD?7!o z8-0jR3AKcPJQ#0LB@eoQi1;GVF5AzwE!l-0$cV}!L97Oi)}VF`N-ae93eo*S^q>&! zEJ6>9keZ)HD(%1p)a}X|Re!5P%1lmqcYYSUV@Y=NP)f;h z;`WESH2)z<`ZCpG4g4gBrn(G1B;yY!9GDEoz)c5c2NrZ8ORy{4oZ(6TR9v-=QfgA( zsA}Q$1D{PAom{b&yylP*-a2&H^Azc0mzRpH0<|U4PO5M6Q%I#}{ktID8+EiUMyJ#* z)GgPo(5=y}({0p^hhJI4f@5Y^R5*wZ9m?R@2@1p04Dw9xQ)z1o0h_D=IE{a6ordLb zufO0uTyJ@%RXBXeSj9qkr+%e$o&mX@XE1Z&oQCCK8?0|A2nrnhH%gyss&?f2?5w`9{s}#Xb?nZ}=!LQdHmO4__br1jNE6 zjd5T!)HkNBa!V>QM!r?^dN`)6LS|X!5?hwkg_OZv7Im^^mP1SgbRx!PbxHa3=eZ7H zmO5)+$~YFT7wXyNB7KYZf8A~oZ>$1=I^qqJs&Z0lb^~rbU_Ojorqd6YOqwUboxo*4GSfdhM&u>lu+>VS8!Dh ztrfc5m@zX9Fn)$X3tZt$7{;DwsNg!^wdCFn0ea2)o#yRQ8b(gLyOvTF!ZCb2_y*?i zAw#oh#0($U&CCrVVNgCLHK&A-WLA(d^;r@p|n1rvoyYslBs7!Ad6hu7`<4qwHekZc+c zHp7;t1$h;fyk3+OnILct;uA1Ls<5$y*#^B~`RQ?_g%{mS1ws^DlZdgEVH&2sN# zd^#ic%BRxI-Uak!mfH?vNtj781j$S+W)q65YAWz7IlqtrnhGl(_vaTeBX&|7%}UbP z!g3)2j7L7z#41amrCQkQA7^K>oT9D_BHRIbJM1hsr1x3FZ)I+sl2{pEQe-Xir?UbE z(4PmupP_pIZFUQwjl*$AG*b*`&eNIuz~e{aB&)OUngij}BYH3rl1Kl>Z{AJE=F6t` z&4*L^=4%$p*ufZj!W+k4MF)TBNmc-XL(LbFW?6_Q`Qs4Gd9bQ@HX()SCnCTJn0sPS+=*IqkUh8M zRIPacW^J|R#h9I~H7~X2Ze>0u$}FAt`j^@tC=_FBnc;J)-)<>_a(MYffP8F#p0b-q z1n6Nvlcv(GZaDnw#Al!{Tzqm(%5DpO*(8Yf4IF*H`lQ$z?@JtP59Ze-7_>E$BpnsT z(-Lc`*j&zxTv)A5xoY{2ty_~`U3(B(sFSvz-N{tX8tqL>G4zfinXjSjR2Y~CV^1mP zZNwRDRID4RA^Y7-747cu#n>X@i=O4GtqOs<&MUvdm2^{DGN*w_N-$^~^*A!%XAro{ofc~X_AgH zR*RxJz2OH;P0ei*lDf$*C#m~*kAIp2j`Kd0=4ne?25HVFX`lYB!L5cp#y+j-4Frs* znj=Z0t~G-fF_Udyw;cyyxozaxW1vrH5Pj6k3VJ% z2?bdYc5MLR(Cxx(umPrCSmQBfic6cpFa06W_T7awLa-i=zBCm0z$KSvNIyHt2ffHu zBtvv*ZusgHx{A=AMh_JHdN@==5$2EuwB}wZKP^ztjt$gPtITc`TnYPk$ALF+Zufvb zISV{VYI^%j{Nm|i7Q~V!QOaYuy*niE(;O+;^7vGn-@iVQ@1GcEozHEPGa2wu_n%-b zta9Y}uTtpY99=aPW?f0=hgtL3C>V2j*rKtChKh!)8iAl#&8(bur)BpUbm$EF{tUWw z7X5q{9Sg72;u?$DcT}zTqdl`T)7~2~4llTtG6`fkU9#8p!3OKc9hSc%WiFAzdXCvL zzUi{!7;LyaI3Z&2Z&hMffW;n^?a@(7Nezwc%r76+C4Tv+EPS(ZE%o7kdt3ckB#W}U z9O7T)10N^8IK<5Ktb6B8IKG?xxiv) z=*CXi^n(cufJs+}jC9}fv}~fPB$X8H7qr=G(SA{tn+(IF|3PUlZ2k}W_QDpp_i6|j zU~9h03&B@#^>syH#8^Ewg6Z#DYMJpO*WtmBc##{=B*3ccp^+jlP4v8dnMvCzm$%X%Djqa2i}+|01{U zPZ;`zL1S1~yBMb37#CR6$;>aL`Y?-9f3Nc4C-z=sA~OtD-x%(LhX%)&?jAX(@kUAw z?71=4U*K=F4119qw0;~e<@{hFbcc$YMMCi$XuYWfJ@Dqu^1P{Q+{yo`zIRD*+OWD; zedMQDc8U1y#Xtpn$vkQFALeNv-ZgI1NUfJnuZwcQNnhOBFIRR@uWD2JRejr)AQ1Aa z@%M;tZ&hoT;EF3M&RF(Af3|uz2Z-%tO{uSWVU)pCDkfe)&Ne9fWnXfs^q?S($ZX^y zwyG^0{NvXAe)GEz6etymxE-awSsawD5Y%%5MH*aqdyHpdchJfQng?pdd}z9z4C>*X z+j$@WF8?veJNoh=q2k_x5iU{6R+#@|Di{c_{#Z3oa@qQ}T~;ZWZjMyQ$)?JLu*QTi z$6G5!3#K#6q2W$o=(IQwvTiG*`8yP+Tf zj=g&sT!GSiYk?JheQyUyg)i=<13R33KNRDv`!oBc$9Rxd0SSr?g0wun7hp2NJV+P{ z&)p9TPT5l0cz0@#(&g5MTk<)(PIuW54Ft)={jvuq`>L-ams^+YWii=lCF!uRb|oW9 zgO?u!;1A4S9@O@W7+0%EFXw z%P;d=ql3YVjK&>H5B3P*&DybW=C3>aw#|4)Wo_DAIAH11!C~4pw#&Z`@bnCuxl8Lu z02cIzf4kJc4}SJE6@Qi;dAbyggMQEY_5Z@7q($iR<`>|y-^~gPm@_mbWLChb&(4Ca z5Ii3QvTb3{8Icqh@`@iIlMT=O-8Xt&qP9W+9O3+w=M9<6-IX6}X+NuEh`HRr)BA%R zG0ewClbZu%@N3z02wu{-3NXCvi_^U2)jwx9j|RXEfEXrT2!{5Lv7eiqKJl71VkY!trigRrbto212I)R3yoJ>>n-d?0|v7RVTdb6 z%mRZL5Eo4qMHEC^3APezKM!g>Xg_PC*0kEH*iXNq$?whk?;LOD&Aj(!9@Q=W-&Jub zF*eAAcxwtCQSFZ?ltj|1G_q=cg{t{Fay7j9=9p^QV|dE?nDo}0Y3sUpa^aSqaq0h5|d`)gb9m@${=CgOfXQ6)M*_$?SAE-6P7eiIXkun#O8)ZITZ{#~MpW*T|p3G2$);k(VmP^jh) z1HI754;~`=9$hl~xeJ^gu4$BxuSFXx_ZI6i43R5$(Zn)%kv}gYyA%BY13GnW$5<6Z z6K6SgrB*ixqT!T@keFH593xGzFi9{a-cZiOC+>ce?nS2TGZB||BbRKCiTGeoy-TGt z6Jfg%Ins~3%mlp^7g6V-`p^@aXn`jL34Z!c^h8VKy-OyA8XXY}-v}lGKKwv9Heg#B zRzlfrChpiU$fb0I1{MfMfd<$hoCLPQ$HFn71%`=+d7?6O$3!fIGey%uik%j11ma&V zBOX>G0(yC_0(0Sd&wRhEAJMd8YPDq!pHdig3Gi3XW%5INQ7U65KJMQy89v0PaFKX~ zSk;A8c+YmhVlfdf>R*P-weqOW)yVFriF4)bBNMTy9J!R`TBC{K8+f?y@G_KFP7|4( z=qOE4@F(#Y`8*Sv!0HHL>Dj0;n$iklKOE|n9`HjKI>>x7zbG9KirrJcpH_Vh3%o`J zPQokEG&@%sjFoiA&b>{YG_ez&_j=FYg-<_ymx;KJd6EE|!&4}d%mh#2N0L1C@Bcxr zBPQaja@O^WiTK(O>Dpg4=>V(x4lmCoE}~T-cTnywGr|3rb-9jO@Nxb1kR42#R8wvs zS1#V52Uk(2&RN3Vx;ZbFq6t|in`_q+36`OG=4`4$wNc-%d|)w6R6>PxVD!4Z$koJb z8&RgG3D3P~M>rm{#E1^k#AYL!hfKt5igk6GRC>5vIv3oAUr9&#d@~i10Q?1K$zliXF`^WTBC&DZ z=h)m!%_Fr!k`iJUPKQvo805oevWuf;GajGr5@^%X4$!^I>UZ=%LD=0UO~9f0Y=! zVeXMCVuC$8AT|mthsB9A$4xCkJM=mahE3Dg)w6$^ zMw(qNqP+wS$IABDK~k(BVCQ$p=rpZ!dJ?TniD^CNZ|qYh0RTGe(^8fT2ghDPHbDQz z=OCqYi-^SUkQ#YUP%A>pi5Ppwv=aafw|_aqBmg42XBuDRQ}8V!CB!*g3WW~pT4LE~y$v1qv^6)GG7Q!8M zO0W+$)w1K9&j6Nje4JRI%2;$)OBWb}@7pe_)M_O|v^v@-g z>nNo*y-&r$ACD|g8l76Pmb~GV5ng)KBsoX=IOL@Q8&6$3%|WVrxMWf(ssATP-=~h& z$LL4vv-FGgx%zy)TK}2;J-F2t9v78XQQ;)o^(dFKj8ho@$R*qRze-!uhth5#v8Y?j zEx}6hoI=&btIQ_ouuaCRlGb1Gjx^N%sZ%(8$gzqn*jv9!oNh$!=ebNOoZYYld<*C)`x{D!=%H|ko=Syh&B8nzQ0Fn>Fzu*`2$_4cY`_XiSGb9?1dKI)K=!G*1K2vk zxzFbmMhoiO{NVqNE(N3D$Bpq|G%RbJ2YB$;M#XelCt~b2x0sv%yuiuNQ|BE@A8&#A ze1j!hU^wIbZ?#8MpN+>;NA)qYR4z(wS%XV2sDZ;4>kWe@lGgWO`mwaI>(xl*up(lt z1`$_aB$0R)5j?Vus4yU+7ut^{r%FFHXt|{g3q+Kb%do5xQSj4OFn0s3xM?novSV@W#|TIai#Jn(sdLA!pJb(5He!zSNbOR09j54eOO97MKvM)!+?MhS3A zIC*=4Q%FemvBXPEtDuPslxNb1a6kzdEoR_JchfEPXM38 zbH}r=vC2;b=G{HO-jOLrn#ri@->pATVRd!X4eNf&%TjKhYUcEU4UE)-KI4>Ddsx`} z|8^Y1=^1|!O-rla90T-jQ=@7-irE%DGt>EwHeUM}?w*4m2e-|w$iTPIR|wpIgX`-bHP+DoN8R3p+S~@zPMoEXuvG zwu;YlORLYzEUcog3?f_s`YXPHd%jWxfdJad3!qIS0|wEfm=ZW?w%+Os&wUjyT9$v$ zDuaK2WdPAod-89v8tyoiDf=Ku11AJ&vIMKLi38ab`ML2GwU`Kr&>`OdS2# ztgh{J3T;AiutR`3j|_yN_6k1s%0qJsEyHFSD|y<^{yxDMr$Ny2MM!7W;|6u3zf{Jz zNgdv$d>fDUm#VD`p}u7?$b^qtR{BO18nmsh1BI}%B#jWkoU>7drxCMd^y`nv`(){J zmRHT|IolSbQT9a-gZJ{#?>&xXH~t~S`4KD2AKM33C#Mmq#~CN4yOl%Gs*x1<*-KLmVo2hgC2FmryD}a z(yCktS{DO9_;Kr+bXbcor4-?zA>$rYwF+$szQoavP;PaKQCFQLx>#X4FS3;it>sMg zf+}75wc2kib*txB)f|PjRIB|LPjZ%|TGvxs0=;8MW&;d57Y;szLP zB?t7(73{kx#<*6*75`bFwkde(I;p0@J+nu=cUA*4ImM`Tj%Nn{m0vX6hQ216!rDgk z^U|QUD9ja2K5Zl6sdF#+J3JtFKE5!szM(_8Z~F)Ef;zLI|DT1)imBo#GeKK-ka}dX zQqLNUv~T0^M$W7a;J6AZ&i;MlEH1Ob$6k6evw=Bj{mXn6gPIe@SK4W(7+Y;vb7q4R z)3N3@5lP*&EFr18SVmv7>@?>?X@5V{mP=amNm??nC8Wi;-z0CzY#0g?sOD(Wq-)9L z1k8B*_O{aiWZUB}oC1LZLgpM1e7rDBwbwO#9o8l|m41UvYVyfWPe%xWDUj z5DukR(!d(H;K~}WxMa63m&>`HY`=144IfudxEcW@aLLuF;-#%z$cqBSbZEPp5xFd# zt|W@iqh1BK9!6*>!Wx`KYwwqGiNOYoGT4A)w+986!Xe%9aWBs!cW?=_b4cHr=oFq) zWLpzWN^DD`NfGmDmV~6HbWF!rSQnEumNW~}e}h!_;NZm@#N?UZ&b9fy^@)D>%sAt6 zL7SY(f%fj-!6&fVS?HguFu+;*Dk|Kziq?eN3M~xARi6n5BjZCDC1?AJ=6JD}DPlJ9k~h7ubaEt9@@T$lXCDiGd8w- zNjm2gc6G^K;T}i6zwHq>&3M74fyLLu{A(|wko?`bkBxjeuhzlPjbFo4*UcahrvETJ z4s89SEK#+0E-5(7Yip`OhXs`$G7L}r3&s8LtAC+yKRgAkKMV$e_SPRbK3EUe+*HI9 z?;EJmOn`4`?bH_qPA_isi-H6u!MF55+t7=EoJma7@i+~ojOM!)jlRIK-8s-Q4z}M+ z@*VVk>DRMf6bv%rg0Mo#tx-{l`GyC@JZE534m+Ia{Fdc-WRz2&h;wWsK~!B*zQL#w zk!nR8TyiVcfAK2AMSHDdxYHx&NPicL5FWZUShQlMfk}pKx5fy}UO)NWY zQRW*=LsNUWNXzzYD7ii8z4$D{1^zVcAOJVZF(#F7!@LC>@ zx9f3p-S2R~XGX1YT}>ujbbEZTwUfy#q5_$@bAPM!;S&3GNMsc7@a>U4xG5~5bYI-8 z#@p#u_|xt8{kO?YwIf~>gsdNrAANqX2m%<%)73OK}W zIX6h7bc(r#Ifah5PCKOcMpXFmwTc*CcQI=ib4N+=5anipic&!d#J&T69@M!9jX$in}3(hMT?ip5eQ_xh$M-c-VjubB3n)%*Sx* zpBHd)E_yx`tguHsX9S~h2BEkPne}mdZDO8V%?-oB{0Z}N`5Y3z$~R8aRq%i_l1qNx zu#&l_hQIwC3T8vmOM1YPE#w7sx!|1At*|l1!<23E{ElDhbUg%sfum6T9!5l84r8+>!F_E zIg)Q_Umnl%taSFbSGI0H#R&O(W>VXxc#&=6w(CR5>5Lx$gvZh-gZh^h&Eo;^03e1* z=7WfVF%Hz1r*?4eKODo|Py4L03D0zS32J5wvx5)ziZ*(=8F90>0J!+lv(0jPmr zys}=a;O$$mSgQ?}UmQ|rP6Q#~J!W4Phy-(+&u4)*UDD;Nf220o?4owmh?*}A;Xmht bk Date: Thu, 26 Aug 2021 06:00:00 -0500 Subject: [PATCH 8/9] Updated OW GUI layout --- resources/app/gui/lang/en.json | 2 +- .../app/gui/randomize/overworld/widgets.json | 22 +++++++++---------- source/gui/randomize/overworld.py | 18 ++++++++++----- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 12e8bef8..8544efff 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -124,7 +124,7 @@ "randomizer.overworld.crossed.limited": "Limited", "randomizer.overworld.crossed.chaos": "Chaos", "randomizer.overworld.keepsimilar": "Keep Similar Edges Together", - "randomizer.overworld.mixed": "Mixed", + "randomizer.overworld.mixed": "Tile Swap (Mixed)", "randomizer.overworld.overworldflute": "Flute Shuffle", "randomizer.overworld.overworldflute.vanilla": "Vanilla", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index 2d15fdd3..faf4a100 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -9,17 +9,6 @@ "full" ] }, - "overworldflute": { - "type": "selectbox", - "default": "vanilla", - "options": [ - "vanilla", - "balanced", - "random" - ] - } - }, - "rightOverworldFrame": { "crossed": { "type": "selectbox", "default": "vanilla", @@ -35,6 +24,17 @@ "type": "checkbox", "default": true }, + "overworldflute": { + "type": "selectbox", + "default": "vanilla", + "options": [ + "vanilla", + "balanced", + "random" + ] + } + }, + "rightOverworldFrame": { "keepsimilar": { "type": "checkbox", "default": true diff --git a/source/gui/randomize/overworld.py b/source/gui/randomize/overworld.py index 50cad240..77be948a 100644 --- a/source/gui/randomize/overworld.py +++ b/source/gui/randomize/overworld.py @@ -18,8 +18,9 @@ def overworld_page(parent): # These get split left & right self.frames["leftOverworldFrame"] = Frame(self) self.frames["rightOverworldFrame"] = Frame(self) - self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW) - self.frames["rightOverworldFrame"].pack(anchor=NW) + + self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y) + self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y) with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets: myDict = json.load(overworldWidgets) @@ -27,9 +28,14 @@ def overworld_page(parent): dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) for key in dictWidgets: self.widgets[key] = dictWidgets[key] - if framename == "rightOverworldFrame": - self.widgets[key].pack(side=LEFT) - else: - self.widgets[key].pack(anchor=E) + packAttrs = {"anchor":E} + if key == "keepsimilar": + packAttrs = {"side":LEFT, "pady":(18,0)} + elif key == "overworldflute": + packAttrs["pady"] = (20,0) + elif key == "mixed": + packAttrs = {"anchor":W, "padx":(79,0)} + + self.widgets[key].pack(packAttrs) return self From af96bce77bfc4df0fc142be403cb6f82c0bdaa99 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 26 Aug 2021 08:02:06 -0500 Subject: [PATCH 9/9] Version bump 0.1.9.0 --- CHANGELOG.md | 8 ++++++- OverworldShuffle.py | 2 +- README.md | 53 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c7b2c6..00f42918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### 0.1.9.0 +- Expanded Crossed OW to four separate options, see Readme for details +- Crossed OW will now play a short SFX when changing worlds +- Improved Link/Bunny state in Crossed OW +- Fixed issue with TR Pegs when fluting directly from an area with hammerpegs +- Updated OW GUI layout + ### 0.1.8.2 - Fixed issue with game crashing on using Flute - Fixed issues with Link/Bunny state in Crossed OW @@ -7,7 +14,6 @@ - Fixed issue with Mystery for OW boolean options - ~~Merged DR v0.5.1.0 - Major Keylogic Update~~ - ### 0.1.8.1 - Fixed issue with activating flute in DW (OW Mixed) - Fixed issue with Parallel+Crossed not generating diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d705f1a1..502dedb7 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -2,7 +2,7 @@ import RaceRandom as random, logging, copy from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OpenStd, parallel_links, IsParallel -__version__ = '0.1.8.2-u' +__version__ = '0.1.9.0-u' def link_overworld(world, player): # setup mandatory connections diff --git a/README.md b/README.md index 7bf01573..d9e5e15f 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,19 @@ See https://alttpr.com/ for more details on the normal randomizer. This is a very new mode of LTTPR so the tools and info is very limited. - There is an [OW Rando Cheat Sheet](https://zelda.codemann8.com/images/shared/ow-rando-reference-sheet.png) that shows all the transitions that exist and are candidates for shuffle. - There is OW tracking capability within the following trackers: - - CodeTracker, an [EmoTracker](https://emotracker.net) package for LTTPR - [Community Tracker](https://alttptracker.dunka.net/) + - CodeTracker, an [EmoTracker](https://emotracker.net) package for LTTPR - There is an [OW OWG Reference Sheet](https://zelda.codemann8.com/images/shared/ow-owg-reference-sheet.png) that shows all the in-logic places where boots/mirror clips and fake flippers are expected from the player. # Known Issues -(Updated 2021-06-23) +(Updated 2021-08-26) ### If you want to playtest this, know these things: - Big Red Bomb may require bomb duping as ledge drops may be in the way of your path to the Pyramid Fairy crack - If you fake flipper, beware of transitioning south. You could end up at the top of the waterfall in the southeast of either world. If you mistakenly drop down, it is important to NOT make any other movements and S+Q immediately when the game allows you to (might take several seconds, the game has to scroll back to the original point of water entry) or there will be a hardlock. Falling from the waterfall is avoidable but it is super easy to do as it is super close to the transition. -- In Crossed OW Tile Swap, there are some interesting bunny water-walk situations that can occur, these are meant to be out-of-logic but beware of logic bugs around this area. +- In Crossed OW, there are some interesting bunny swimming situations that can occur, these are meant to be out-of-logic but beware of logic bugs around this area. But also, hardlocks can occur; if you take damage, be sure to S+Q immediately before moving in any direction, or you may get an infinite screen wrap glitch. ### Known bugs: -- ~~In Mixed OW Tile Swap, Smith and Stumpy have issues when their tiles are swapped. Progression cannot be found on them when these tiles are swapped~~ (Fixed in 0.1.6.4) - Screens that loop on itself and also have free-standing items, the sprites are duplicated and can cause item duplication - When OWG are performed to enter mega-tile screens (large OW screens), there is a small chance that an incorrect VRAM reference value causes the map graphics to offset in increments of 16 pixels @@ -66,26 +65,58 @@ OW Transitions are shuffled, but both worlds will have a matching layout. OW Transitions are shuffled within each world separately. -## Crossed (--ow_crossed) +## Crossed Options (--ow_crossed) This allows OW connections to be shuffled cross-world. -## Visual Representation of Main OW Shuffle Settings +Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable -![OW Shuffle Settings Combination](https://zelda.codemann8.com/images/shared/ow-modes.gif) +Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. + +See each option to get more details on the differences. + +### None + +Transitions will remain same-world. + +### Polar + +Only effective if Mixed/Tile Swap is enabled. Enabling Polar preserves the original/vanilla connections even when tiles are swapped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny (as per Mixed swapping some tiles to the other world). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there. (see Tile Swap/Mixed section for more details) + +### Grouped + +This option shuffles connections cross-world in the same manner as Tile Swap/Mixed, the connections leading in and coming out of a group of tiles are crossed. Unlike Polar, this uses a different set of tile groups as a basis of crossing connections, albeit the same rule govern which groups of tiles must cross together (see Tile Swap/Mixed for more details) + +### Limited + +Every transition independently is a candidate to be chosen as a cross-world connection, however only 9 transitions become crossed (in each world). This option abides by the Keep Similar Edges Together option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. + +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. + +Motive: Why 9 connections? To imitate the effect of the 9 standard portals that exist. + +### 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. ## Keep Similar Edges Together (--ow_keepsimilar) This keeps similar edge transitions together. ie. The 2 west edges of Potion Shop will be paired to another set of two similar edges -## Mixed Overworld (--ow_mixed) +Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW. -OW tiles are randomly chosen to become a part of the opposite world +## Tile Swap / Mixed Overworld (--ow_mixed) + +OW tiles are randomly chosen to become a part of the opposite world. When on the Overworld, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW. + +Note: Tiles are put into groups that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to another tile, those tiles must swap together; (an exception to this is the Old Man Rescue cave which has been modified similar to how Inverted modifies it, Old Man Rescue is ALWAYS accessible from the Light World) ## Flute Shuffle (--ow_fluteshuffle) When enabled, new flute spots are generated and gives the player the option to cancel out of the flute menu by pressing X. +Note: Desert Teleporter Ledge is always guaranteed to be chosen. One of the three Mountain tiles are guaranteed if OW Layout Shuffle is set to Vanilla. + ### Vanilla Flute spots remain unchanged. @@ -114,10 +145,10 @@ Show the help message and exit. For specifying the overworld layout shuffle you want as above. (default: vanilla) ``` ---ow_crossed +--ow_crossed ``` -This allows cross-world connections on the overworld +For specifying the type of cross-world connections you want on the overworld ``` --ow_keepsimilar