diff --git a/BaseClasses.py b/BaseClasses.py index dcf7ff69..e3214a70 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -20,11 +20,12 @@ from source.overworld.EntranceData import door_addresses class World(object): - def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, + def __init__(self, players, owLayout, owParallel, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints, spoiler_mode): self.players = players self.teams = 1 - self.owShuffle = owShuffle.copy() + self.owLayout = owLayout.copy() + self.owParallel = owParallel.copy() self.owTerrain = {} self.owKeepSimilar = {} self.owMixed = owMixed.copy() @@ -3041,7 +3042,8 @@ class Spoiler(object): 'bow_mode': self.world.bow_mode, 'goal': self.world.goal, 'custom_goals': self.world.custom_goals, - 'ow_shuffle': self.world.owShuffle, + 'ow_layout': self.world.owLayout, + 'ow_parallel': self.world.owParallel, 'ow_terrain': self.world.owTerrain, 'ow_crossed': self.world.owCrossed, 'ow_keepsimilar': self.world.owKeepSimilar, @@ -3312,11 +3314,12 @@ class Spoiler(object): outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['dropshuffle'][player]) outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player]) outfile.write('\n') - outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) - if self.metadata['ow_shuffle'][player] != 'vanilla': + outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_layout'][player]) + if self.metadata['ow_layout'][player] != 'vanilla': + outfile.write('Parallel OW:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_parallel'][player])) outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player])) outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player]) - if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none': + if self.metadata['ow_layout'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none': outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player])) outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player])) @@ -3728,8 +3731,8 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique # byte 10: settings_version -# byte 11: OOOT WCCC (OWR layout, free terrain, whirlpools, OWR crossed) -or_mode = {"vanilla": 0, "parallel": 1, "full": 2} +# byte 11: POOT WCCC (parallel, OWR layout, free terrain, whirlpools, OWR crossed) +orlayout_mode = {"vanilla": 0, "grid": 1, "wild": 2} orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "unrestricted": 4} # byte 12: KMBQ FF?? (keep similar, mixed/tile flip, bonk drops, follower quests, flute spots) @@ -3795,7 +3798,7 @@ class Settings(object): settings_version, - (or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owTerrain[p] else 0) + (0x80 if w.owParallel[p] else 0) | (orlayout_mode[w.owLayout[p]] << 5) | (0x10 if w.owTerrain[p] else 0) | (0x08 if w.owWhirlpoolShuffle[p] else 0) | orcrossed_mode[w.owCrossed[p]], (0x80 if w.owKeepSimilar[p] else 0) | (0x40 if w.owMixed[p] else 0) @@ -3877,7 +3880,8 @@ class Settings(object): args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3] args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)] - args.ow_shuffle[p] = r(or_mode)[(settings[11] & 0xE0) >> 5] + args.ow_parallel[p] = True if settings[11] & 0x80 else False + args.ow_layout[p] = r(orlayout_mode)[(settings[11] & 0x60) >> 5] args.ow_terrain[p] = True if settings[11] & 0x10 else False args.ow_whirlpool[p] = True if settings[11] & 0x08 else False args.ow_crossed[p] = r(orcrossed_mode)[(settings[11] & 0x07)] diff --git a/CLI.py b/CLI.py index 5eb4f3e2..0b4dceba 100644 --- a/CLI.py +++ b/CLI.py @@ -120,6 +120,16 @@ def parse_cli(argv, no_defaults=False): ret.take_any = 'random' if ret.take_any == 'none' else ret.take_any ret.keyshuffle = 'universal' + if ret.ow_unparallel: + ret.ow_parallel = False + + if ret.ow_shuffle == 'parallel': + ret.ow_layout = 'wild' + ret.ow_parallel = True + elif ret.ow_shuffle == 'full': + ret.ow_layout = 'wild' + ret.ow_parallel = False + if player_num: defaults = copy.deepcopy(ret) for player in range(1, player_num + 1): @@ -130,8 +140,8 @@ def parse_cli(argv, no_defaults=False): for k, v in playersettings.items(): setattr(playerargs, k, v) - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', - 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', 'ow_layout', + 'ow_parallel', 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', 'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'startinventory', @@ -193,7 +203,10 @@ def parse_settings(): # Shuffle Ganon defaults to TRUE "openpyramid": "auto", "shuffleganon": True, - "ow_shuffle": "vanilla", + "ow_shuffle": "vanilla", # for backwards compatibility + "ow_layout": "vanilla", + "ow_parallel": True, + "ow_unparallel": False, "ow_terrain": False, "ow_crossed": "none", "ow_keepsimilar": False, diff --git a/Main.py b/Main.py index a25f6bc0..1f8c92ab 100644 --- a/Main.py +++ b/Main.py @@ -432,7 +432,7 @@ def init_world(args, fish): customized.load_yaml(args.customizer) customized.adjust_args(args, False) - world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + world = World(args.multi, args.ow_layout, args.ow_parallel, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints, args.spoiler) @@ -725,7 +725,7 @@ def set_starting_inventory(world, args): def copy_world(world): # ToDo: Not good yet - ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, + ret = World(world.players, world.owLayout, world.owParallel, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode) ret.teams = world.teams @@ -946,7 +946,7 @@ def copy_world(world): def copy_world_premature(world, player, create_flute_exits=True): # ToDo: Not good yet - ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, + ret = World(world.players, world.owLayout, world.owParallel, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode) ret.teams = world.teams diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 5b95f33e..61d3d397 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -144,7 +144,7 @@ def link_overworld(world, player): parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})} connected_edges = [] - if world.owShuffle[player] != 'vanilla': + if world.owLayout[player] != 'vanilla': trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) trimmed_groups = reorganize_groups(world, trimmed_groups, player) @@ -264,10 +264,10 @@ def link_overworld(world, player): s[0x30], s[0x35], s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player) - elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'): + elif limited_crossed > -1 or (world.owLayout[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'): crossed_candidates = list() for group in trimmed_groups.keys(): - (mode, wrld, dir, terrain, parallel, count, _) = group + (mode, wrld, _, terrain, parallel, _, _) = group if wrld == WorldType.Light and mode != OpenStd.Standard: for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): if forward_set[0] in parallel_links_new: @@ -278,7 +278,7 @@ def link_overworld(world, player): combine_set = forward_combine+back_combine skip_forward = False - if world.owShuffle[player] == 'vanilla': + if world.owLayout[player] == 'vanilla': if any(edge in force_crossed for edge in combine_set): if not any(edge in force_noncrossed for edge in combine_set): if any(edge in force_crossed for edge in forward_combine): @@ -412,7 +412,7 @@ def link_overworld(world, player): # layout shuffle logging.getLogger('').debug('Shuffling overworld layout') - if world.owShuffle[player] == 'vanilla': + if world.owLayout[player] == 'vanilla': # apply outstanding flips trimmed_groups = performSwap(trimmed_groups, edges_to_swap) assert len(edges_to_swap) == 0, 'Not all edges were flipped successfully: ' + ', '.join(edges_to_swap) @@ -425,8 +425,10 @@ 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) + elif world.owLayout[player] == 'grid': + raise NotImplementedError() else: - if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel': + if world.owKeepSimilar[player] and world.owParallel[player]: for exitname, destname in parallelsimilar_connections: connect_two_way(world, exitname, destname, player, connected_edges) @@ -822,7 +824,7 @@ def connect_custom(world, connected_edges, groups, forced, player): remove_pair_from_pool(edge1.name, edge2.name, is_crossed) connect_two_way(world, edge1.name, edge2.name, player, connected_edges) # resolve parallel - if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new: + if world.owParallel[player] and edge1.name in parallel_links_new: parallel_forward_edge = parallel_links_new[edge1.name] parallel_back_edge = parallel_links_new[edge2.name] if validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed): @@ -838,7 +840,7 @@ def connect_custom(world, connected_edges, groups, forced, player): connect_two_way(world, forward_edge, back_edge, player, connected_edges) else: raise GenerationException('Violation of force crossed rules on unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge) - if world.owShuffle[player] == 'parallel' and forward_edge in parallel_links_new: + if world.owParallel[player] and forward_edge in parallel_links_new: parallel_forward_edge = parallel_links_new[forward_edge] parallel_back_edge = parallel_links_new[back_edge] if not validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed): @@ -868,7 +870,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): x.dest = y y.dest = x - if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none': + if world.owLayout[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: @@ -876,7 +878,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): connected_edges.append(edgename2) # connecting parallel connections - if world.owShuffle[player] in ['vanilla', 'parallel']: + if world.owLayout[player] == 'vanilla' or world.owParallel[player]: if edgename1 in parallel_links_new: try: parallel_forward_edge = parallel_links_new[edgename1] @@ -965,7 +967,7 @@ def determine_forced_flips(world, tile_ow_groups, do_grouped, player): for whirl1, whirl2 in custom_whirlpools.items(): if [whirlpool_map[whirl1], whirlpool_map[whirl2]] not in merged_owids and should_merge_group(whirlpool_map[whirl1], whirlpool_map[whirl2]): merged_owids.append([whirlpool_map[whirl1], whirlpool_map[whirl2]]) - if world.owShuffle[player] != 'vanilla': + if world.owLayout[player] != 'vanilla': custom_edges = world.customizer.get_owedges() if custom_edges and player in custom_edges: custom_edges = custom_edges[player] @@ -1071,6 +1073,9 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): exist_dw_regions.extend(dw_regions) parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)] + if world.owLayout[player] == 'grid': + parity[1] = 0 + parity[2] = 0 if not world.owKeepSimilar[player]: parity[1] += 2*parity[2] parity[2] = 0 @@ -1164,12 +1169,16 @@ def define_tile_groups(world, do_grouped, player): if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']: merge_groups([[0x05, 0x07]]) - # all non-parallel screens - if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped): - merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]]) + # special screens + if world.owLayout[player] != 'wild' and (world.owCrossed[player] == 'none' or do_grouped): + merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81]]) + + # remaining non-parallel edges + if world.owLayout[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped): + merge_groups([[0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]]) # special case: non-parallel keep similar - if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped): + if world.owLayout[player] == 'wild' and world.owParallel[player] and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x28, 0x29]]) # whirlpool screens @@ -1225,7 +1234,7 @@ def reorganize_groups(world, groups, player): new_group[0] = None if world.owTerrain[player]: new_group[3] = None - if world.owShuffle[player] != 'parallel': + if not world.owParallel[player]: new_group[4] = None if not world.owKeepSimilar[player]: new_group[5] = None diff --git a/Plando.py b/Plando.py index 25003f24..ade90949 100755 --- a/Plando.py +++ b/Plando.py @@ -24,7 +24,7 @@ def main(args): start_time = time.process_time() # initialize the world - world = World(1, 'vanilla', 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False) + world = World(1, 'vanilla', True, 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False) world.player_names[1].append("Player 1") logger = logging.getLogger('') @@ -157,9 +157,9 @@ def prefill_world(world, plando, text_patches): elif line.startswith('!goal'): _, goalstr = line.split(':', 1) world.goal = {1: goalstr.strip()} - elif line.startswith('!owShuffle'): + elif line.startswith('!owLayout'): _, modestr = line.split(':', 1) - world.owShuffle = {1: modestr.strip()} + world.owLayout = {1: modestr.strip()} elif line.startswith('!owCrossed'): _, modestr = line.split(':', 1) world.owCrossed = {1: modestr.strip()} diff --git a/Plandomizer_Template.txt b/Plandomizer_Template.txt index 41782f89..68b46544 100644 --- a/Plandomizer_Template.txt +++ b/Plandomizer_Template.txt @@ -244,7 +244,7 @@ Ganons Tower - Validation Chest: Nothing Ganon: Triforce # set Overworld connections (lines starting with $, separate edges with =) -!owShuffle: parallel +!owLayout: wild #!owMixed: true # Mixed OW not supported yet !owCrossed: none !owKeepSimilar: true diff --git a/README.md b/README.md index f5c6572a..f4c3102e 100644 --- a/README.md +++ b/README.md @@ -132,20 +132,24 @@ Note: These changes do impact the logic. If you use `CodeTracker`, these Inverte Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md) -## Overworld Layout Shuffle (--ow_shuffle) +## Overworld Layout Shuffle (--ow_layout) OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://zelda.codemann8.com/images/shared/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Flip) ### Vanilla OW Transitions are not shuffled. -### Parallel +### Grid -OW Transitions are shuffled, but both worlds will have a matching layout, similar to that of vanilla. +OW Screens are shuffled in such a way that they are still arranged on an 8x8 grid for each world like vanilla, and the OW Transitions are based on that arrangement. -### Full +### Wild -OW Transitions are shuffled within each world separately. +OW Transitions are shuffled with no respect to geometric coherence. + +## Parallel (--ow_unparallel to disable) + +With OW Layout Shuffle, this forces both worlds to have a matching layout. ## Free Terrain (--ow_terrain) @@ -389,11 +393,17 @@ Districts are a concept originally conceived by Aerinon in the Door Randomizer, Show the help message and exit. ``` ---ow_shuffle +--ow_layout ``` For specifying the overworld layout shuffle you want as above. (default: vanilla) +``` +--ow_unparallel +``` + +With OW Layout Shuffle, this no longer forces both worlds to have a matching layout. + ``` --ow_terrain ``` diff --git a/Rom.py b/Rom.py index a2bc02a6..08d769f1 100644 --- a/Rom.py +++ b/Rom.py @@ -557,13 +557,10 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): # patch overworld edges inverted_buffer = [0] * 0x82 owMode = 0 - if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] not in ['none', 'polar'] or world.owMixed[player]: - 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] == 'unrestricted'): + if world.owLayout[player] != 'vanilla' or world.owCrossed[player] not in ['none', 'polar'] or world.owMixed[player]: + if world.owLayout[player] != 'vanilla': + owMode = 1 if world.owParallel[player] else 2 + if world.owKeepSimilar[player] and (world.owLayout[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'): owMode |= 0x0100 if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]): owMode |= 0x0200 @@ -2393,7 +2390,7 @@ def write_strings(rom, world, player, team): if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active': items_to_hint.remove(flute_item) flute_item = 'Ocarina (Activated)' - if world.owShuffle[player] != 'vanilla' or world.owMixed[player]: + if world.owLayout[player] != 'vanilla' or world.owMixed[player]: # Adding a guaranteed hint for the Flute in overworld shuffle. this_location = world.find_items_not_key_only(flute_item, player) if this_location and this_location not in hinted_locations: @@ -2411,7 +2408,7 @@ def write_strings(rom, world, player, team): random.shuffle(items_to_hint) hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8 hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 - hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0 + hint_count += 1 if world.owLayout[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0 while hint_count > 0 and len(items_to_hint) > 0: this_item = items_to_hint.pop(0) this_location = world.find_items_not_key_only(this_item, player) diff --git a/docs/Customizer.md b/docs/Customizer.md index 17c5c337..f7d53421 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -234,7 +234,7 @@ You may define a list of items and a list of locations. Those items will be cons ### ow-edges -This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`. +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_layout` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`. #### two-way diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index 5390eeb3..c2f3943c 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -20,7 +20,8 @@ settings: shuffle_followers: true shuffle: crossed shufflelinks: true - ow_shuffle: parallel + ow_layout: wild + ow_parallel: true ow_terrain: true ow_crossed: grouped ow_keepsimilar: true diff --git a/presets/world/owr_districtshuffle-full.yaml b/presets/world/owr_districtshuffle-full.yaml index e9e62916..8903fb58 100644 --- a/presets/world/owr_districtshuffle-full.yaml +++ b/presets/world/owr_districtshuffle-full.yaml @@ -1,6 +1,7 @@ settings: 1: - ow_shuffle: full + ow_layout: wild + ow_parallel: false ow_keepsimilar: false ow-edges: 1: diff --git a/presets/world/owr_districtshuffle-pangea.yaml b/presets/world/owr_districtshuffle-pangea.yaml index e4d1ff3c..7f47e4f0 100644 --- a/presets/world/owr_districtshuffle-pangea.yaml +++ b/presets/world/owr_districtshuffle-pangea.yaml @@ -1,6 +1,7 @@ settings: 1: - ow_shuffle: full + ow_layout: wild + ow_parallel: false ow_keepsimilar: false ow-edges: 1: diff --git a/presets/world/owr_districtshuffle-vanillaborders.yaml b/presets/world/owr_districtshuffle-vanillaborders.yaml index 016dd9da..8f3f29d5 100644 --- a/presets/world/owr_districtshuffle-vanillaborders.yaml +++ b/presets/world/owr_districtshuffle-vanillaborders.yaml @@ -1,6 +1,7 @@ settings: 1: - ow_shuffle: full + ow_layout: wild + ow_parallel: false ow_keepsimilar: false ow-edges: 1: diff --git a/presets/world/owr_vanilla-mirroredsimilar.yaml b/presets/world/owr_vanilla-mirroredsimilar.yaml index f9411f08..9f302899 100644 --- a/presets/world/owr_vanilla-mirroredsimilar.yaml +++ b/presets/world/owr_vanilla-mirroredsimilar.yaml @@ -1,6 +1,7 @@ settings: 1: - ow_shuffle: full + ow_layout: wild + ow_parallel: false ow_terrain: true ow-edges: 1: diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 8141614a..6e2e462c 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -173,6 +173,22 @@ "full" ] }, + "ow_layout": { + "choices": [ + "vanilla", + "grid", + "wild" + ] + }, + "ow_parallel": { + "action": "store_true", + "help": "suppress", + "type": "bool" + }, + "ow_unparallel": { + "action": "store_true", + "type": "bool" + }, "ow_terrain": { "action": "store_true", "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 44835df1..8b0c40ec 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -234,14 +234,18 @@ "the entrances vanilla." ], "ow_shuffle": [ + "Deprecated, use ow_layout and ow_unparallel instead." + ], + "ow_layout": [ "This shuffles the layout of the overworld.", "Vanilla: All overworld transitions are connected the same", " way they were in the base game.", - "Parallel: Overworld transitions are shuffled, but both worlds", - " will have the same pattern/shape.", - "Full: Overworld transitions are shuffled, but both worlds", - " will have an independent map shape." + "Grid: OW Screens are arranged on 8x8 grids and OW Transitions", + " work according to this arrangement.", + "Wild: OW Transitions are shuffled with no respect to geometric coherence." ], + "ow_unparallel": [ + "With OW Layout Shuffle, this no longer forces both worlds to have a matching layout." ], "ow_terrain": [ "With OW Layout Shuffle, this allows land and water edges to be connected." ], "ow_crossed": [ diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 0ad57757..1d1cee1f 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -157,10 +157,12 @@ "randomizer.enemizer.enemylogic.allow_all": "Allow special enemies anywhere", - "randomizer.overworld.overworldshuffle": "Layout Shuffle", - "randomizer.overworld.overworldshuffle.vanilla": "Vanilla", - "randomizer.overworld.overworldshuffle.parallel": "Parallel", - "randomizer.overworld.overworldshuffle.full": "Full", + "randomizer.overworld.layout": "Layout Shuffle", + "randomizer.overworld.layout.vanilla": "Vanilla", + "randomizer.overworld.layout.grid": "Grid", + "randomizer.overworld.layout.wild": "Wild", + + "randomizer.overworld.parallel": "Keep Worlds Parallel", "randomizer.overworld.terrain": "Free Terrain", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index c3c49c94..a2332d30 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -1,13 +1,13 @@ { "topOverworldFrame": {}, "leftOverworldFrame": { - "overworldshuffle": { + "layout": { "type": "selectbox", "default": "vanilla", "options": [ "vanilla", - "parallel", - "full" + "grid", + "wild" ] }, "crossed": { @@ -18,7 +18,10 @@ "grouped", "polar", "unrestricted" - ] + ], + "config": { + "pady": [16,0] + } }, "mixed": { "type": "checkbox", @@ -48,19 +51,17 @@ } }, "rightOverworldFrame": { + "parallel": { + "type": "checkbox", + "default": true + }, "terrain": { "type": "checkbox", - "default": false, - "config": { - "pady": [3,0] - } + "default": false }, "keepsimilar": { "type": "checkbox", - "default": false, - "config": { - "pady": [6,0] - } + "default": false } } } \ No newline at end of file diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 7a3f3712..32b9cda0 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -89,7 +89,8 @@ class CustomSettings(object): args.mystery = True else: settings = defaultdict(lambda: None, player_setting) - args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p]) + args.ow_layout[p] = get_setting(settings['ow_layout'], args.ow_layout[p]) + args.ow_parallel[p] = get_setting(settings['ow_parallel'], args.ow_parallel[p]) args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p]) args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p]) if args.ow_crossed[p] == 'chaos': @@ -135,6 +136,14 @@ class CustomSettings(object): args.take_any[p] = 'random' if args.take_any[p] == 'none' else args.take_any[p] args.keyshuffle[p] = 'universal' + ow_shuffle = get_setting(settings['ow_shuffle'], args.ow_shuffle[p]) + if ow_shuffle == 'parallel': + args.ow_layout = 'wild' + args.ow_parallel = True + elif ow_shuffle == 'full': + args.ow_layout = 'wild' + args.ow_parallel = False + args.mixed_travel[p] = get_setting(settings['mixed_travel'], args.mixed_travel[p]) args.standardize_palettes[p] = get_setting(settings['standardize_palettes'], args.standardize_palettes[p]) @@ -356,7 +365,8 @@ class CustomSettings(object): self.world_rep['start_inventory'] = start_inv = {} for p in self.player_range: settings_dict[p] = {} - settings_dict[p]['ow_shuffle'] = world.owShuffle[p] + settings_dict[p]['ow_layout'] = world.owLayout[p] + settings_dict[p]['ow_parallel'] = world.owParallel[p] settings_dict[p]['ow_terrain'] = world.owTerrain[p] settings_dict[p]['ow_crossed'] = world.owCrossed[p] settings_dict[p]['ow_keepsimilar'] = world.owKeepSimilar[p] diff --git a/source/classes/constants.py b/source/classes/constants.py index c8e017ef..2960a1cc 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -92,7 +92,8 @@ SETTINGSTOPROCESS = { "bombbag": "bombbag" }, "overworld": { - "overworldshuffle": "ow_shuffle", + "layout": "ow_layout", + "parallel": "ow_parallel", "terrain": "ow_terrain", "crossed": "ow_crossed", "keepsimilar": "ow_keepsimilar", diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 8ef10110..d189a457 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -635,7 +635,7 @@ def do_dark_sanc(entrances, exits, avail): forbidden.append('Links House') else: forbidden.append('Big Bomb Shop') - if avail.world.owShuffle[avail.player] == 'vanilla': + if avail.world.owLayout[avail.player] == 'vanilla': choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances] else: choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances] @@ -679,7 +679,7 @@ def do_links_house(entrances, exits, avail, cross_world): forbidden.append(links_house_vanilla) forbidden.extend(Forbidden_Swap_Entrances) shuffle_mode = avail.world.shuffle[avail.player] - if avail.world.owShuffle[avail.player] == 'vanilla': + if avail.world.owLayout[avail.player] == 'vanilla': # simple shuffle - if shuffle_mode == 'simple': avail.links_on_mountain = True # taken care of by the logic below @@ -733,7 +733,7 @@ def do_links_house(entrances, exits, avail, cross_world): # links on dm dm_spots = LH_DM_Connector_List.union(LH_DM_Exit_Forbidden) - if links_house in dm_spots and avail.world.owShuffle[avail.player] == 'vanilla': + if links_house in dm_spots and avail.world.owLayout[avail.player] == 'vanilla': if avail.links_on_mountain: return # connector is fine logging.getLogger('').warning(f'Links House is placed in tight area and is now unhandled. Report any errors that occur from here.') diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index ac130d8b..65fffd56 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -120,8 +120,16 @@ def roll_settings(weights): ret.accessibility = get_choice('accessibility') ret.restrict_boss_items = get_choice('restrict_boss_items') + overworld_layout = get_choice('overworld_layout') + ret.ow_layout = overworld_layout if overworld_layout != 'none' else 'vanilla' + ret.ow_parallel = get_choice_bool('overworld_parallel') overworld_shuffle = get_choice('overworld_shuffle') - ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' + if overworld_shuffle == 'parallel': + ret.ow_layout = 'wild' + ret.ow_parallel = True + elif overworld_shuffle == 'full': + ret.ow_layout = 'wild' + ret.ow_parallel = False ret.ow_terrain = get_choice_bool('overworld_terrain') valid_options = {'none': 'none', 'polar': 'polar', 'grouped': 'polar', 'chaos': 'unrestricted', 'unrestricted': 'unrestricted'} ret.ow_crossed = get_choice('overworld_crossed')