OoT Time Optimization (#2401)

- Entrance randomizer no longer grows with multiworld
- Improved ER success rate again by prioritizing Temple of Time even more
- Prefill is faster, has slightly reduced failure rate when map/compass are in dungeon but previous items in any_dungeon (which consumed all available locations), no longer removes items from the main itempool; itemlinked prefill items removed to accomodate improvements
- Now triggers only one recache after `generate_basic` instead of one per oot world
- Avoids recaches during `create_regions`
- All ER temp entrances have unique names (so the entrance cache does not break)
This commit is contained in:
espeon65536
2023-10-29 21:05:49 -06:00
committed by GitHub
parent f81e72686a
commit db978aa48a
4 changed files with 172 additions and 146 deletions

View File

@@ -2,6 +2,7 @@ from itertools import chain
import logging
from worlds.generic.Rules import set_rule, add_rule
from BaseClasses import CollectionState
from .Hints import get_hint_area, HintAreaNotFound
from .Regions import TimeOfDay
@@ -25,12 +26,12 @@ def set_all_entrances_data(world, player):
return_entrance.data['index'] = 0x7FFF
def assume_entrance_pool(entrance_pool, ootworld):
def assume_entrance_pool(entrance_pool, ootworld, pool_type):
assumed_pool = []
for entrance in entrance_pool:
assumed_forward = entrance.assume_reachable()
assumed_forward = entrance.assume_reachable(pool_type)
if entrance.reverse != None and not ootworld.decouple_entrances:
assumed_return = entrance.reverse.assume_reachable()
assumed_return = entrance.reverse.assume_reachable(pool_type)
if not (ootworld.mix_entrance_pools != 'off' and (ootworld.shuffle_overworld_entrances or ootworld.shuffle_special_interior_entrances)):
if (entrance.type in ('Dungeon', 'Grotto', 'Grave') and entrance.reverse.name != 'Spirit Temple Lobby -> Desert Colossus From Spirit Lobby') or \
(entrance.type == 'Interior' and ootworld.shuffle_special_interior_entrances):
@@ -41,15 +42,15 @@ def assume_entrance_pool(entrance_pool, ootworld):
return assumed_pool
def build_one_way_targets(world, types_to_include, exclude=(), target_region_names=()):
def build_one_way_targets(world, pool, types_to_include, exclude=(), target_region_names=()):
one_way_entrances = []
for pool_type in types_to_include:
one_way_entrances += world.get_shufflable_entrances(type=pool_type)
valid_one_way_entrances = list(filter(lambda entrance: entrance.name not in exclude, one_way_entrances))
if target_region_names:
return [entrance.get_new_target() for entrance in valid_one_way_entrances
return [entrance.get_new_target(pool) for entrance in valid_one_way_entrances
if entrance.connected_region.name in target_region_names]
return [entrance.get_new_target() for entrance in valid_one_way_entrances]
return [entrance.get_new_target(pool) for entrance in valid_one_way_entrances]
# Abbreviations
@@ -423,14 +424,14 @@ multi_interior_regions = {
}
interior_entrance_bias = {
'Kakariko Village -> Kak Potion Shop Front': 4,
'Kak Backyard -> Kak Potion Shop Back': 4,
'Kakariko Village -> Kak Impas House': 3,
'Kak Impas Ledge -> Kak Impas House Back': 3,
'Goron City -> GC Shop': 2,
'Zoras Domain -> ZD Shop': 2,
'ToT Entrance -> Temple of Time': 4,
'Kakariko Village -> Kak Potion Shop Front': 3,
'Kak Backyard -> Kak Potion Shop Back': 3,
'Kakariko Village -> Kak Impas House': 2,
'Kak Impas Ledge -> Kak Impas House Back': 2,
'Market Entrance -> Market Guard House': 2,
'ToT Entrance -> Temple of Time': 1,
'Goron City -> GC Shop': 1,
'Zoras Domain -> ZD Shop': 1,
}
@@ -443,7 +444,8 @@ def shuffle_random_entrances(ootworld):
player = ootworld.player
# Gather locations to keep reachable for validation
all_state = world.get_all_state(use_cache=True)
all_state = ootworld.get_state_with_complete_itempool()
all_state.sweep_for_events(locations=ootworld.get_locations())
locations_to_ensure_reachable = {loc for loc in world.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))}
# Set entrance data for all entrances
@@ -523,12 +525,12 @@ def shuffle_random_entrances(ootworld):
for pool_type, entrance_pool in one_way_entrance_pools.items():
if pool_type == 'OwlDrop':
valid_target_types = ('WarpSong', 'OwlDrop', 'Overworld', 'Extra')
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, valid_target_types, exclude=['Prelude of Light Warp -> Temple of Time'])
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, pool_type, valid_target_types, exclude=['Prelude of Light Warp -> Temple of Time'])
for target in one_way_target_entrance_pools[pool_type]:
set_rule(target, lambda state: state._oot_reach_as_age(target.parent_region, 'child', player))
elif pool_type in {'Spawn', 'WarpSong'}:
valid_target_types = ('Spawn', 'WarpSong', 'OwlDrop', 'Overworld', 'Interior', 'SpecialInterior', 'Extra')
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, valid_target_types)
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, pool_type, valid_target_types)
# Ensure that the last entrance doesn't assume the rest of the targets are reachable
for target in one_way_target_entrance_pools[pool_type]:
add_rule(target, (lambda entrances=entrance_pool: (lambda state: any(entrance.connected_region == None for entrance in entrances)))())
@@ -538,14 +540,11 @@ def shuffle_random_entrances(ootworld):
target_entrance_pools = {}
for pool_type, entrance_pool in entrance_pools.items():
target_entrance_pools[pool_type] = assume_entrance_pool(entrance_pool, ootworld)
target_entrance_pools[pool_type] = assume_entrance_pool(entrance_pool, ootworld, pool_type)
# Build all_state and none_state
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:
none_state.prog_items[item_tuple] = 0
none_state = CollectionState(ootworld.multiworld)
# Plando entrances
if world.plando_connections[player]:
@@ -628,7 +627,7 @@ def shuffle_random_entrances(ootworld):
logging.getLogger('').error(f'Root Exit: {exit} -> {exit.connected_region}')
logging.getLogger('').error(f'Root has too many entrances left after shuffling entrances')
# Game is beatable
new_all_state = world.get_all_state(use_cache=False)
new_all_state = ootworld.get_state_with_complete_itempool()
if not world.has_beaten_game(new_all_state, player):
raise EntranceShuffleError('Cannot beat game')
# Validate world
@@ -700,7 +699,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, pool_type, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=20):
def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=10):
restrictive_entrances, soft_entrances = split_entrances_by_requirements(ootworld, entrance_pool, target_entrances)
@@ -745,7 +744,6 @@ def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollback
def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entrances):
world = ootworld.multiworld
player = ootworld.player
# Disconnect all root assumed entrances and save original connections
@@ -755,7 +753,7 @@ def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entran
if entrance.connected_region:
original_connected_regions[entrance] = entrance.disconnect()
all_state = world.get_all_state(use_cache=False)
all_state = ootworld.get_state_with_complete_itempool()
restrictive_entrances = []
soft_entrances = []
@@ -793,8 +791,8 @@ 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()
all_state.sweep_for_events(locations=ootworld.get_locations())
none_state.sweep_for_events(locations=ootworld.get_locations())
if ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances or ootworld.spawn_positions:
time_travel_state = none_state.copy()