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",