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/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/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..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 @@ -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') + return 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 + trimmed_groups = 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,49 @@ 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', 'chaos']: + 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(edge) + if world.owCrossed[player] == 'limited': + random.shuffle(crossed_candidates) + for edge_set in crossed_candidates[:9]: + for edge in edge_set: + crossed_edges.append(edge) + for edge in copy.deepcopy(crossed_edges): + if edge in parallel_links: + crossed_edges.append(parallel_links[edge]) + elif edge in parallel_links.inverse: + crossed_edges.append(parallel_links.inverse[edge][0]) + + trimmed_groups = performSwap(trimmed_groups, crossed_edges) + assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges) # 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 +197,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 +336,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 +358,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 +369,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 +380,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 +466,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 +489,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/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 diff --git a/Rom.py b/Rom.py index 51a7ac10..c9ebb8ad 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '363c49821f25327f10bc200c98375bde' +RANDOMIZERBASEHASH = '712ebda1ef6818c59cde0371b5a4e4a9' class JsonRom(object): @@ -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' and (world.owCrossed[player] != 'polar' or world.owMixed[player]): 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/asm/owrando.asm b/asm/owrando.asm index 300d4da8..8082d38f 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -342,21 +342,26 @@ 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 .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 #$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 - 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 17f77f5f..997df3d6 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ 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..8544efff 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -118,8 +118,13 @@ "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", + "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 15b89cab..faf4a100 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -9,6 +9,21 @@ "full" ] }, + "crossed": { + "type": "selectbox", + "default": "vanilla", + "options": [ + "none", + "polar", + "grouped", + "limited", + "chaos" + ] + }, + "mixed": { + "type": "checkbox", + "default": true + }, "overworldflute": { "type": "selectbox", "default": "vanilla", @@ -20,14 +35,6 @@ } }, "rightOverworldFrame": { - "crossed": { - "type": "checkbox", - "default": false - }, - "mixed": { - "type": "checkbox", - "default": true - }, "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