From 84803530280f99e66c758ccb2136288b8d52b674 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 7 Jan 2026 10:59:36 -0700 Subject: [PATCH] fix: specific fix for vanilla entrance shuffle with no doors to properly process dungeon in the correct order for key logic --- BaseClasses.py | 10 ++++++++++ DoorShuffle.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 3d7e80b5..b16f456b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -284,6 +284,16 @@ class World(object): return portal raise RuntimeError('No such portal %s for player %d' % (portal_name, player)) + def get_portal_unsafe(self, portal_name, player): + if (portal_name, player) in self._portal_cache: + return self._portal_cache[(portal_name, player)] + else: + for portal in self.dungeon_portals[player]: + if portal.name == portal_name and portal.player == player: + self._portal_cache[(portal_name, player)] = portal + return portal + return None + def is_atgt_swapped(self, player): return self.mode[player] == 'inverted' diff --git a/DoorShuffle.py b/DoorShuffle.py index 32b15ec5..793e5ead 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -104,6 +104,9 @@ def link_doors_prep(world, player): world.get_portal('Turtle Rock Eye Bridge', player).destination = True else: analyze_portals(world, player) + if world.doorShuffle[player] == 'vanilla': # these are always not destinations + world.get_portal('Desert Back', player).destination = False + world.get_portal('Skull 3', player).destination = False for portal in world.dungeon_portals[player]: connect_portal(portal, world, player) @@ -233,11 +236,21 @@ def vanilla_key_logic(world, player): enabled_entrances = world.enabled_entrances[player] = {} builder_queue = deque(builders) last_key, loops = None, 0 + + # --- Precompute all potential portals for each builder using dungeon_portals --- + from DungeonGenerator import dungeon_portals + all_potential_portals_map = {} + for builder in builders: + portal_names = list(dungeon_portals.get(builder.name, [])) + all_potential_portals_map[builder.name] = set(world.get_portal(portal_name, player).door.entrance.parent_region.name for portal_name in portal_names) + while len(builder_queue) > 0: builder = builder_queue.popleft() origin_list = entrances_map[builder.name] find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name) - if len(origin_list) <= 0: + all_potential_origins = all_potential_portals_map[builder.name] + enabled = entrances_map[builder.name] + if len(origin_list) <= 0 or should_delay_processing(enabled, all_potential_origins, potentials, connections, world, player): if last_key == builder.name or loops > 1000: origin_name = (world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin') @@ -310,6 +323,33 @@ def validate_vanilla_reservation(dungeon, world, player): return validate_key_layout(world.key_layout[player][dungeon.name], world, player) +def should_delay_processing(enabled_origins, potential_origins, potentials, connections, world, player): + disabled_origins = potential_origins.difference(set(enabled_origins)) + main_targets = [] + for do in disabled_origins: + region = world.get_region(do, player) + portal = _find_portal_for_region(region, world, player) + if portal and not portal.destination: + main_targets.append(region) + if not main_targets: + return False # No non-destination disabled origins found + enabling_regions = {connections[r.name] for r in main_targets} + for enabling_region in enabling_regions: + dungeon_names = { + portal.door.entrance.parent_region.dungeon.name + for dungeon_r in potentials[enabling_region] + if (portal := _find_portal_for_region(world.get_region(dungeon_r, player), world, player)) + } + if len(dungeon_names) > 1: + return True + return False + + +def _find_portal_for_region(region, world, player): + return next((p for ent in region.entrances + if (p := world.get_portal_unsafe(ent.parent_region.name.rstrip(' Portal'), player))), None) + + # some useful functions oppositemap = { Direction.South: Direction.North,