OoT ER: Interior and Overworld Entrance Shuffle (#128)
* OoT: add ER retry functionality and custom get_all_state This all_state does not have events, because they need to be gathered in the world. * OoT: reenable Interior and Overworld entrance shuffle
This commit is contained in:
parent
f26d2d5f20
commit
61ae51b30c
|
@ -413,14 +413,7 @@ def shuffle_random_entrances(ootworld):
|
|||
target_entrance_pools[pool_type] = assume_entrance_pool(entrance_pool, ootworld)
|
||||
|
||||
# Build all_state and none_state
|
||||
all_state = world.get_all_state(use_cache=False)
|
||||
all_state.child_reachable_regions[player] = set()
|
||||
all_state.adult_reachable_regions[player] = set()
|
||||
all_state.child_blocked_connections[player] = set()
|
||||
all_state.adult_blocked_connections[player] = set()
|
||||
all_state.day_reachable_regions[player] = set()
|
||||
all_state.dampe_reachable_regions[player] = set()
|
||||
all_state.stale[player] = True
|
||||
all_state = ootworld.get_state_with_complete_itempool()
|
||||
none_state = all_state.copy()
|
||||
for item_tuple in none_state.prog_items:
|
||||
if item_tuple[1] == player:
|
||||
|
@ -529,7 +522,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al
|
|||
raise EntranceShuffleError(f'Unable to place priority one-way entrance for {priority_name} in world {ootworld.player}')
|
||||
|
||||
|
||||
def shuffle_entrance_pool(ootworld, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=100):
|
||||
def shuffle_entrance_pool(ootworld, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=20):
|
||||
|
||||
restrictive_entrances, soft_entrances = split_entrances_by_requirements(ootworld, entrance_pool, target_entrances)
|
||||
|
||||
|
@ -618,6 +611,9 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
|
|||
all_state = all_state_orig.copy()
|
||||
none_state = none_state_orig.copy()
|
||||
|
||||
all_state.sweep_for_events()
|
||||
none_state.sweep_for_events()
|
||||
|
||||
if ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances or ootworld.spawn_positions:
|
||||
time_travel_state = none_state.copy()
|
||||
time_travel_state.collect(ootworld.create_item('Time Travel'), event=True)
|
||||
|
|
|
@ -163,10 +163,10 @@ class MQDungeons(Range):
|
|||
|
||||
world_options: typing.Dict[str, type(Option)] = {
|
||||
"starting_age": StartingAge,
|
||||
# "shuffle_interior_entrances": InteriorEntrances,
|
||||
"shuffle_interior_entrances": InteriorEntrances,
|
||||
"shuffle_grotto_entrances": GrottoEntrances,
|
||||
"shuffle_dungeon_entrances": DungeonEntrances,
|
||||
# "shuffle_overworld_entrances": OverworldEntrances,
|
||||
"shuffle_overworld_entrances": OverworldEntrances,
|
||||
"owl_drops": OwlDrops,
|
||||
"warp_songs": WarpSongs,
|
||||
"spawn_positions": SpawnPositions,
|
||||
|
|
|
@ -7,7 +7,7 @@ logger = logging.getLogger("Ocarina of Time")
|
|||
|
||||
from .Location import OOTLocation, LocationFactory, location_name_to_id
|
||||
from .Entrance import OOTEntrance
|
||||
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table
|
||||
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table, EntranceShuffleError
|
||||
from .Items import OOTItem, item_table, oot_data_to_ap_id
|
||||
from .ItemPool import generate_itempool, add_dungeon_items, get_junk_item, get_junk_pool
|
||||
from .Regions import OOTRegion, TimeOfDay
|
||||
|
@ -186,8 +186,6 @@ class OOTWorld(World):
|
|||
self.mq_dungeons_random = False # this will be a deprecated option later
|
||||
self.ocarina_songs = False # just need to pull in the OcarinaSongs module
|
||||
self.big_poe_count = 1 # disabled due to client-side issues for now
|
||||
self.shuffle_interior_entrances = 'off'
|
||||
self.shuffle_overworld_entrances = False # disabled due to stability issues
|
||||
|
||||
# Set internal names used by the OoT generator
|
||||
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
|
||||
|
@ -487,7 +485,35 @@ class OOTWorld(World):
|
|||
def set_rules(self):
|
||||
# This has to run AFTER creating items but BEFORE set_entrances_based_rules
|
||||
if self.entrance_shuffle:
|
||||
shuffle_random_entrances(self)
|
||||
# 10 attempts at shuffling entrances
|
||||
tries = 10
|
||||
while tries:
|
||||
try:
|
||||
shuffle_random_entrances(self)
|
||||
except EntranceShuffleError as e:
|
||||
tries -= 1
|
||||
logging.getLogger('').debug(f"Failed shuffling entrances for world {self.player}, retrying {tries} more times")
|
||||
if tries == 0:
|
||||
raise e
|
||||
# Restore original state and delete assumed entrances
|
||||
for entrance in self.get_shuffled_entrances():
|
||||
entrance.connect(self.world.get_region(entrance.vanilla_connected_region, self.player))
|
||||
if entrance.assumed:
|
||||
assumed_entrance = entrance.assumed
|
||||
if assumed_entrance.connected_region is not None:
|
||||
assumed_entrance.disconnect()
|
||||
del assumed_entrance
|
||||
entrance.reverse = None
|
||||
entrance.replaces = None
|
||||
entrance.assumed = None
|
||||
entrance.shuffled = False
|
||||
# Clean up root entrances
|
||||
root = self.get_region("Root Exits")
|
||||
root.exits = root.exits[:8]
|
||||
else:
|
||||
break
|
||||
|
||||
# Write entrances to spoiler log
|
||||
all_entrances = self.get_shuffled_entrances()
|
||||
all_entrances.sort(key=lambda x: x.name)
|
||||
all_entrances.sort(key=lambda x: x.type)
|
||||
|
@ -663,7 +689,6 @@ class OOTWorld(World):
|
|||
fill_restrictive(self.world, self.world.get_all_state(False), song_locations[:], songs[:],
|
||||
True, True)
|
||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||
tries = 0
|
||||
except FillError as e:
|
||||
tries -= 1
|
||||
if tries == 0:
|
||||
|
@ -677,6 +702,8 @@ class OOTWorld(World):
|
|||
location.item = None
|
||||
location.locked = False
|
||||
location.event = False
|
||||
else:
|
||||
break
|
||||
|
||||
# Place shop items
|
||||
# fast fill will fail because there is some logic on the shop items. we'll gather them up and place the shop items
|
||||
|
@ -912,3 +939,26 @@ class OOTWorld(World):
|
|||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Specifically ensures that only real items are gotten, not any events.
|
||||
# In particular, ensures that Time Travel needs to be found.
|
||||
def get_state_with_complete_itempool(self):
|
||||
all_state = self.world.get_all_state(use_cache=False)
|
||||
# Remove event progression items
|
||||
for item, player in all_state.prog_items:
|
||||
if (item not in item_table or item_table[item][2] is None) and player == self.player:
|
||||
all_state.prog_items[(item, player)] = 0
|
||||
# Remove all events and checked locations
|
||||
all_state.locations_checked = {loc for loc in all_state.locations_checked if loc.player != self.player}
|
||||
all_state.events = {loc for loc in all_state.events if loc.player != self.player}
|
||||
|
||||
# Invalidate caches
|
||||
all_state.child_reachable_regions[self.player] = set()
|
||||
all_state.adult_reachable_regions[self.player] = set()
|
||||
all_state.child_blocked_connections[self.player] = set()
|
||||
all_state.adult_blocked_connections[self.player] = set()
|
||||
all_state.day_reachable_regions[self.player] = set()
|
||||
all_state.dampe_reachable_regions[self.player] = set()
|
||||
all_state.stale[self.player] = True
|
||||
|
||||
return all_state
|
||||
|
|
Loading…
Reference in New Issue