@@ -157,17 +157,16 @@ def __init__(self, world: World, coupled: bool):
157
157
def placed_regions (self ) -> set [Region ]:
158
158
return self .collection_state .reachable_regions [self .world .player ]
159
159
160
- def find_placeable_exits (self , check_validity : bool ) -> list [Entrance ]:
160
+ def find_placeable_exits (self , check_validity : bool , usable_exits : list [ Entrance ] ) -> list [Entrance ]:
161
161
if check_validity :
162
162
blocked_connections = self .collection_state .blocked_connections [self .world .player ]
163
- blocked_connections = sorted ( blocked_connections , key = lambda x : x . name )
164
- placeable_randomized_exits = [ connection for connection in blocked_connections
165
- if not connection . connected_region
166
- and connection .is_valid_source_transition (self )]
163
+ placeable_randomized_exits = [ ex for ex in usable_exits
164
+ if not ex . connected_region
165
+ and ex in blocked_connections
166
+ and ex .is_valid_source_transition (self )]
167
167
else :
168
168
# this is on a beaten minimal attempt, so any exit anywhere is fair game
169
- placeable_randomized_exits = [ex for region in self .world .multiworld .get_regions (self .world .player )
170
- for ex in region .exits if not ex .connected_region ]
169
+ placeable_randomized_exits = [ex for ex in usable_exits if not ex .connected_region ]
171
170
self .world .random .shuffle (placeable_randomized_exits )
172
171
return placeable_randomized_exits
173
172
@@ -181,7 +180,8 @@ def _connect_one_way(self, source_exit: Entrance, target_entrance: Entrance) ->
181
180
self .placements .append (source_exit )
182
181
self .pairings .append ((source_exit .name , target_entrance .name ))
183
182
184
- def test_speculative_connection (self , source_exit : Entrance , target_entrance : Entrance ) -> bool :
183
+ def test_speculative_connection (self , source_exit : Entrance , target_entrance : Entrance ,
184
+ usable_exits : set [Entrance ]) -> bool :
185
185
copied_state = self .collection_state .copy ()
186
186
# simulated connection. A real connection is unsafe because the region graph is shallow-copied and would
187
187
# propagate back to the real multiworld.
@@ -198,6 +198,9 @@ def test_speculative_connection(self, source_exit: Entrance, target_entrance: En
198
198
# ignore the source exit, and, if coupled, the reverse exit. They're not actually new
199
199
if _exit .name == source_exit .name or (self .coupled and _exit .name == target_entrance .name ):
200
200
continue
201
+ # make sure we are only paying attention to usable exits
202
+ if _exit not in usable_exits :
203
+ continue
201
204
# technically this should be is_valid_source_transition, but that may rely on side effects from
202
205
# on_connect, which have not happened here (because we didn't do a real connection, and if we did, we would
203
206
# not want them to persist). can_reach is a close enough approximation most of the time.
@@ -326,6 +329,24 @@ def randomize_entrances(
326
329
# similar to fill, skip validity checks on entrances if the game is beatable on minimal accessibility
327
330
perform_validity_check = True
328
331
332
+ if not er_targets :
333
+ er_targets = sorted ([entrance for region in world .multiworld .get_regions (world .player )
334
+ for entrance in region .entrances if not entrance .parent_region ], key = lambda x : x .name )
335
+ if not exits :
336
+ exits = sorted ([ex for region in world .multiworld .get_regions (world .player )
337
+ for ex in region .exits if not ex .connected_region ], key = lambda x : x .name )
338
+ if len (er_targets ) != len (exits ):
339
+ raise EntranceRandomizationError (f"Unable to randomize entrances due to a mismatched count of "
340
+ f"entrances ({ len (er_targets )} ) and exits ({ len (exits )} ." )
341
+
342
+ # used when membership checks are needed on the exit list, e.g. speculative sweep
343
+ exits_set = set (exits )
344
+ for entrance in er_targets :
345
+ entrance_lookup .add (entrance )
346
+
347
+ # place the menu region and connected start region(s)
348
+ er_state .collection_state .update_reachable_regions (world .player )
349
+
329
350
def do_placement (source_exit : Entrance , target_entrance : Entrance ) -> None :
330
351
placed_exits , removed_entrances = er_state .connect (source_exit , target_entrance )
331
352
# remove the placed targets from consideration
@@ -339,7 +360,7 @@ def do_placement(source_exit: Entrance, target_entrance: Entrance) -> None:
339
360
340
361
def find_pairing (dead_end : bool , require_new_exits : bool ) -> bool :
341
362
nonlocal perform_validity_check
342
- placeable_exits = er_state .find_placeable_exits (perform_validity_check )
363
+ placeable_exits = er_state .find_placeable_exits (perform_validity_check , exits )
343
364
for source_exit in placeable_exits :
344
365
target_groups = target_group_lookup [source_exit .randomization_group ]
345
366
for target_entrance in entrance_lookup .get_targets (target_groups , dead_end , preserve_group_order ):
@@ -355,7 +376,7 @@ def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
355
376
and len (placeable_exits ) == 1 )
356
377
if exit_requirement_satisfied and source_exit .can_connect_to (target_entrance , dead_end , er_state ):
357
378
if (needs_speculative_sweep
358
- and not er_state .test_speculative_connection (source_exit , target_entrance )):
379
+ and not er_state .test_speculative_connection (source_exit , target_entrance , exits_set )):
359
380
continue
360
381
do_placement (source_exit , target_entrance )
361
382
return True
@@ -407,21 +428,6 @@ def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
407
428
f"All unplaced entrances: { unplaced_entrances } \n "
408
429
f"All unplaced exits: { unplaced_exits } " )
409
430
410
- if not er_targets :
411
- er_targets = sorted ([entrance for region in world .multiworld .get_regions (world .player )
412
- for entrance in region .entrances if not entrance .parent_region ], key = lambda x : x .name )
413
- if not exits :
414
- exits = sorted ([ex for region in world .multiworld .get_regions (world .player )
415
- for ex in region .exits if not ex .connected_region ], key = lambda x : x .name )
416
- if len (er_targets ) != len (exits ):
417
- raise EntranceRandomizationError (f"Unable to randomize entrances due to a mismatched count of "
418
- f"entrances ({ len (er_targets )} ) and exits ({ len (exits )} ." )
419
- for entrance in er_targets :
420
- entrance_lookup .add (entrance )
421
-
422
- # place the menu region and connected start region(s)
423
- er_state .collection_state .update_reachable_regions (world .player )
424
-
425
431
# stage 1 - try to place all the non-dead-end entrances
426
432
while entrance_lookup .others :
427
433
if not find_pairing (dead_end = False , require_new_exits = True ):
0 commit comments