From 483e7f49ad99b2bcdf030830d7de3473475e02bb Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:36:51 +0100 Subject: [PATCH] Fix issues with dynamic flute and mirror exits --- Main.py | 13 ++++---- OverworldShuffle.py | 49 ++++++++++++---------------- source/overworld/EntranceShuffle2.py | 2 +- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/Main.py b/Main.py index 873173a1..ff1771d8 100644 --- a/Main.py +++ b/Main.py @@ -17,7 +17,7 @@ from OverworldGlitchRules import create_owg_connections from PotShuffle import shuffle_pots, shuffle_pot_switches from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations from OWEdges import create_owedges -from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_exits +from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_flute_exits, create_dynamic_mirror_exits from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string from Doors import create_doors from DoorShuffle import link_doors, connect_portal, link_doors_prep @@ -172,7 +172,6 @@ def main(args, seed=None, fish=None): create_shops(world, player) update_world_regions(world, player) mark_light_dark_world_regions(world, player) - create_dynamic_exits(world, player) init_districts(world) @@ -809,13 +808,13 @@ def copy_world(world): update_world_regions(ret, player) if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) - create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) create_shops(ret, player) - #create_doors(ret, player) create_rooms(ret, player) create_dungeons(ret, player) + create_dynamic_mirror_exits(ret, player) + create_dynamic_flute_exits(ret, player) # there are region references here they must be migrated to preserve integrity # ret.exp_cache = world.exp_cache.copy() @@ -940,7 +939,7 @@ def copy_world(world): return ret -def copy_world_premature(world, player): +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, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, @@ -1032,13 +1031,15 @@ def copy_world_premature(world, player): update_world_regions(ret, player) if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) - create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) create_shops(ret, player) create_doors(ret, player) create_rooms(ret, player) create_dungeons(ret, player) + create_dynamic_mirror_exits(ret, player) # assumes these have already been added to world + if create_flute_exits: + create_dynamic_flute_exits(ret, player) if world.mode[player] == 'standard': parent = ret.get_region('Menu', player) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 028f1659..8febdd13 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -200,6 +200,7 @@ def link_overworld(world, player): connect_simple(world, exitname, regionname, player) categorize_world_regions(world, player) + create_dynamic_mirror_exits(world, player) if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(world, player) @@ -424,8 +425,6 @@ 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) - - world.owsectors[player] = build_sectors(world, player) else: if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel': for exitname, destname in parallelsimilar_connections: @@ -557,13 +556,14 @@ def link_overworld(world, player): connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges) remove_connected(forward_edge_sets, back_edge_sets) assert len(connected_edges) == len(default_connections) * 2, connected_edges - - world.owsectors[player] = build_sectors(world, player) + valid_layout = validate_layout(world, player) tries -= 1 assert valid_layout, 'Could not find a valid OW layout' + world.owsectors[player] = build_sectors(world, player) + # flute shuffle logging.getLogger('').debug('Shuffling flute spots') def connect_flutes(flute_destinations): @@ -725,6 +725,8 @@ def link_overworld(world, player): s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('flute', text_output, new_spots, player) + create_dynamic_flute_exits(world, player) + def connect_custom(world, connected_edges, groups, forced, player): forced_crossed, forced_noncrossed = forced def remove_pair_from_pool(edgename1, edgename2, is_crossed): @@ -1292,7 +1294,7 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches) return groups -def create_flute_exits(world, player): +def create_dynamic_flute_exits(world, player): flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) if not flute_in_pool: return @@ -1303,6 +1305,7 @@ def create_flute_exits(world, player): exit.spot_type = 'Flute' exit.connect(world.get_region('Flute Sky', player)) region.exits.append(exit) + world.initialize_regions() def get_mirror_exit_name(from_region, to_region): if from_region in mirror_connections and to_region in mirror_connections[from_region]: @@ -1329,7 +1332,7 @@ def get_mirror_edges(world, region, player): mirror_exits.append(tuple([get_mirror_exit_name(other_world_region_name, region.name), region.name])) return mirror_exits -def create_mirror_exits(world, player): +def create_dynamic_mirror_exits(world, player): mirror_exits = set() for region in (r for r in world.regions if r.player == player and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']): if region.type == (RegionType.DarkWorld if world.mode[player] != 'inverted' else RegionType.LightWorld): @@ -1350,12 +1353,6 @@ def create_mirror_exits(world, player): region.exits.append(exit) mirror_exits.add(exitname) - elif region.terrain == Terrain.Land: - pass - -def create_dynamic_exits(world, player): - create_flute_exits(world, player) - create_mirror_exits(world, player) world.initialize_regions() def categorize_world_regions(world, player): @@ -1433,7 +1430,7 @@ def build_sectors(world, player): # perform accessibility check on duplicate world for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_premature(world, player) + base_world = copy_world_premature(world, player, create_flute_exits=False) # build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances) regions = list(OWTileRegions.copy().keys()) @@ -1510,7 +1507,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F if build_copy_world: for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world_premature(world, player) + base_world = copy_world_premature(world, player, create_flute_exits=True) base_world.override_bomb_check = True else: base_world = world @@ -1554,11 +1551,9 @@ def validate_layout(world, player): 'Pyramid Area': ['Pyramid Exit Ledge'] } - from Main import copy_world_premature - from Utils import stack_size3a # TODO: Find a better source for the below lists, original sourced was deprecated from source.overworld.EntranceData import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections - + dungeon_entrances = list(zip(*default_dungeon_connections + [('Ganons Tower', '')]))[0] connector_entrances = list(zip(*default_connector_connections))[0] item_entrances = list(zip(*default_item_connections))[0] @@ -1567,12 +1562,11 @@ def validate_layout(world, player): flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) def explore_region(region_name, region=None): - if stack_size3a() > 500: - raise GenerationException(f'Infinite loop detected for "{region_name}" located at \'validate_layout\'') - - explored_regions.append(region_name) + if region_name in explored_regions: + return + explored_regions.add(region_name) if not region: - region = base_world.get_region(region_name, player) + region = world.get_region(region_name, player) for exit in region.exits: if exit.connected_region is not None and exit.connected_region.name not in explored_regions \ and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: @@ -1586,11 +1580,8 @@ def validate_layout(world, player): for dest_region in sane_connectors[region_name]: if dest_region not in explored_regions: explore_region(dest_region) - - for p in range(1, world.players + 1): - world.key_logic[p] = {} - base_world = copy_world_premature(world, player) - explored_regions = list() + + explored_regions = set() if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]: if not world.is_bombshop_start(player): @@ -1616,14 +1607,14 @@ def validate_layout(world, player): start_region = 'Hyrule Castle Ledge' explore_region(start_region) - unreachable_regions = OrderedDict() + unreachable_regions = {} unreachable_count = -1 while unreachable_count != len(unreachable_regions): # find unreachable regions unreachable_regions = {} for region_name in list(OWTileRegions.copy().keys()): if region_name not in explored_regions and region_name not in isolated_regions: - region = base_world.get_region(region_name, player) + region = world.get_region(region_name, player) unreachable_regions[region_name] = region # loop thru unreachable regions to check if some can be excluded diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 777edaec..8ef10110 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -864,7 +864,7 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo for p in range(1, avail.world.players + 1): avail.world.key_logic[p] = {} - base_world = copy_world_premature(avail.world, avail.player) + base_world = copy_world_premature(avail.world, avail.player, create_flute_exits=True) base_world.override_bomb_check = True connect_simple(base_world, 'Links House S&Q', start_region, avail.player)