OoT: implement decoupled entrance pools
This commit is contained in:
parent
61ffdff207
commit
f9e1db41e9
|
@ -29,7 +29,7 @@ def assume_entrance_pool(entrance_pool, ootworld):
|
|||
assumed_pool = []
|
||||
for entrance in entrance_pool:
|
||||
assumed_forward = entrance.assume_reachable()
|
||||
if entrance.reverse != None:
|
||||
if entrance.reverse != None and not ootworld.decouple_entrances:
|
||||
assumed_return = entrance.reverse.assume_reachable()
|
||||
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 \
|
||||
|
@ -309,6 +309,8 @@ entrance_shuffle_table = [
|
|||
('Overworld', ('ZD Behind King Zora -> Zoras Fountain', { 'index': 0x0225 }),
|
||||
('Zoras Fountain -> ZD Behind King Zora', { 'index': 0x01A1 })),
|
||||
|
||||
('Overworld', ('GV Lower Stream -> Lake Hylia', { 'index': 0x0219 })),
|
||||
|
||||
('OwlDrop', ('LH Owl Flight -> Hyrule Field', { 'index': 0x027E, 'addresses': [0xAC9F26] })),
|
||||
('OwlDrop', ('DMT Owl Flight -> Kak Impas Rooftop', { 'index': 0x0554, 'addresses': [0xAC9EF2] })),
|
||||
|
||||
|
@ -377,16 +379,24 @@ def shuffle_random_entrances(ootworld):
|
|||
entrance_pools['Dungeon'] = ootworld.get_shufflable_entrances(type='Dungeon', only_primary=True)
|
||||
if ootworld.open_forest == 'closed':
|
||||
entrance_pools['Dungeon'].remove(world.get_entrance('KF Outside Deku Tree -> Deku Tree Lobby', player))
|
||||
if ootworld.decouple_entrances:
|
||||
entrance_pools['DungeonReverse'] = [entrance.reverse for entrance in entrance_pools['Dungeon']]
|
||||
if ootworld.shuffle_interior_entrances != 'off':
|
||||
entrance_pools['Interior'] = ootworld.get_shufflable_entrances(type='Interior', only_primary=True)
|
||||
if ootworld.shuffle_special_interior_entrances:
|
||||
entrance_pools['Interior'] += ootworld.get_shufflable_entrances(type='SpecialInterior', only_primary=True)
|
||||
if ootworld.decouple_entrances:
|
||||
entrance_pools['InteriorReverse'] = [entrance.reverse for entrance in entrance_pools['Interior']]
|
||||
if ootworld.shuffle_grotto_entrances:
|
||||
entrance_pools['GrottoGrave'] = ootworld.get_shufflable_entrances(type='Grotto', only_primary=True)
|
||||
entrance_pools['GrottoGrave'] += ootworld.get_shufflable_entrances(type='Grave', only_primary=True)
|
||||
if ootworld.decouple_entrances:
|
||||
entrance_pools['GrottoGraveReverse'] = [entrance.reverse for entrance in entrance_pools['GrottoGrave']]
|
||||
if ootworld.shuffle_overworld_entrances:
|
||||
exclude_overworld_reverse = ootworld.mix_entrance_pools == 'all' and not ootworld.decouple_entrances
|
||||
entrance_pools['Overworld'] = ootworld.get_shufflable_entrances(type='Overworld', only_primary=exclude_overworld_reverse)
|
||||
if not ootworld.decouple_entrances:
|
||||
entrance_pools['Overworld'].remove(world.get_entrance('GV Lower Stream -> Lake Hylia', player))
|
||||
|
||||
# Mark shuffled entrances
|
||||
for entrance in chain(chain.from_iterable(one_way_entrance_pools.values()), chain.from_iterable(entrance_pools.values())):
|
||||
|
@ -631,24 +641,25 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
|
|||
time_travel_state.collect(ootworld.create_item('Time Travel'), event=True)
|
||||
time_travel_state._oot_update_age_reachable_regions(player)
|
||||
|
||||
# For various reasons, we don't want the player to end up through certain entrances as the wrong age
|
||||
# Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age
|
||||
# This means we need to hard check that none of the relevant entrances are ever reachable as that age
|
||||
# This is mostly relevant when shuffling special interiors (such as windmill or kak potion shop)
|
||||
# Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
|
||||
CHILD_FORBIDDEN = ['OGC Great Fairy Fountain -> Castle Grounds', 'GV Carpenter Tent -> GV Fortress Side']
|
||||
ADULT_FORBIDDEN = ['HC Great Fairy Fountain -> Castle Grounds', 'HC Storms Grotto -> Castle Grounds']
|
||||
|
||||
for entrance in ootworld.get_shufflable_entrances():
|
||||
if entrance.shuffled and entrance.replaces:
|
||||
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential child access')
|
||||
if entrance.replaces.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.replaces.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
|
||||
else:
|
||||
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as child')
|
||||
if entrance.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as adult')
|
||||
if not ootworld.decouple_entrances:
|
||||
for entrance in ootworld.get_shufflable_entrances():
|
||||
if entrance.shuffled and entrance.replaces:
|
||||
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential child access')
|
||||
if entrance.replaces.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.replaces.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
|
||||
else:
|
||||
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as child')
|
||||
if entrance.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as adult')
|
||||
|
||||
# Check if all locations are reachable if not beatable-only or game is not yet complete
|
||||
if locations_to_ensure_reachable:
|
||||
|
@ -745,14 +756,14 @@ def get_entrance_replacing(region, entrance_name, player):
|
|||
def change_connections(entrance, target):
|
||||
entrance.connect(target.disconnect())
|
||||
entrance.replaces = target.replaces
|
||||
if entrance.reverse:
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
target.replaces.reverse.connect(entrance.reverse.assumed.disconnect())
|
||||
target.replaces.reverse.replaces = entrance.reverse
|
||||
|
||||
def restore_connections(entrance, target):
|
||||
target.connect(entrance.disconnect())
|
||||
entrance.replaces = None
|
||||
if entrance.reverse:
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
entrance.reverse.assumed.connect(target.replaces.reverse.disconnect())
|
||||
target.replaces.reverse.replaces = None
|
||||
|
||||
|
@ -769,7 +780,7 @@ def check_entrances_compatibility(entrance, target, rollbacks):
|
|||
def confirm_replacement(entrance, target):
|
||||
delete_target_entrance(target)
|
||||
logging.getLogger('').debug(f'Connected {entrance} to {entrance.connected_region}')
|
||||
if entrance.reverse:
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
replaced_reverse = target.replaces.reverse
|
||||
delete_target_entrance(entrance.reverse.assumed)
|
||||
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')
|
||||
|
|
|
@ -513,20 +513,6 @@ class OOTWorld(World):
|
|||
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)
|
||||
for loadzone in all_entrances:
|
||||
if loadzone.primary:
|
||||
entrance = loadzone
|
||||
else:
|
||||
entrance = loadzone.reverse
|
||||
if entrance.reverse is not None:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'both', self.player)
|
||||
else:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
|
||||
set_rules(self)
|
||||
set_entrances_based_rules(self)
|
||||
|
||||
|
@ -790,6 +776,24 @@ class OOTWorld(World):
|
|||
create_patch_file(rom, output_path(output_directory, outfile_name + '.apz5'))
|
||||
rom.restore()
|
||||
|
||||
# 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)
|
||||
if not self.decouple_entrances:
|
||||
for loadzone in all_entrances:
|
||||
if loadzone.primary:
|
||||
entrance = loadzone
|
||||
else:
|
||||
entrance = loadzone.reverse
|
||||
if entrance.reverse is not None:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'both', self.player)
|
||||
else:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
else:
|
||||
for entrance in all_entrances:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
|
||||
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
|
||||
@classmethod
|
||||
def stage_generate_output(cls, world: MultiWorld, output_directory: str):
|
||||
|
|
Loading…
Reference in New Issue