|
|
|
|
@@ -23,6 +23,7 @@ class EntrancePool(object):
|
|
|
|
|
self.decoupled_exits = []
|
|
|
|
|
self.original_entrances = set()
|
|
|
|
|
self.original_exits = set()
|
|
|
|
|
self.same_world_restricted = {}
|
|
|
|
|
|
|
|
|
|
self.world = world
|
|
|
|
|
self.player = player
|
|
|
|
|
@@ -90,6 +91,10 @@ def link_entrances_new(world, player):
|
|
|
|
|
if mode not in modes:
|
|
|
|
|
raise RuntimeError(f'Shuffle mode {mode} is not yet supported')
|
|
|
|
|
mode_cfg = copy.deepcopy(modes[mode])
|
|
|
|
|
|
|
|
|
|
if world.linked_drops[player] != 'unset':
|
|
|
|
|
mode_cfg['keep_drops_together'] = 'on' if world.linked_drops[player] == 'linked' else 'off'
|
|
|
|
|
|
|
|
|
|
avail_pool.swapped = mode_cfg['undefined'] == 'swap'
|
|
|
|
|
if avail_pool.is_standard():
|
|
|
|
|
do_standard_connections(avail_pool)
|
|
|
|
|
@@ -97,11 +102,7 @@ def link_entrances_new(world, player):
|
|
|
|
|
for pool_name, pool in pool_list.items():
|
|
|
|
|
special_shuffle = pool['special'] if 'special' in pool else None
|
|
|
|
|
if special_shuffle == 'drops':
|
|
|
|
|
holes, targets = find_entrances_and_targets_drops(avail_pool, pool['entrances'])
|
|
|
|
|
if avail_pool.swapped:
|
|
|
|
|
connect_swapped(holes, targets, avail_pool)
|
|
|
|
|
else:
|
|
|
|
|
connect_random(holes, targets, avail_pool)
|
|
|
|
|
handle_skull_woods_drops(avail_pool, pool['entrances'], mode_cfg)
|
|
|
|
|
elif special_shuffle == 'fixed_shuffle':
|
|
|
|
|
do_fixed_shuffle(avail_pool, pool['entrances'])
|
|
|
|
|
elif special_shuffle == 'same_world':
|
|
|
|
|
@@ -124,12 +125,7 @@ def link_entrances_new(world, player):
|
|
|
|
|
elif special_shuffle == 'vanilla':
|
|
|
|
|
do_vanilla_connect(pool, avail_pool)
|
|
|
|
|
elif special_shuffle == 'skull':
|
|
|
|
|
entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances'])
|
|
|
|
|
if avail_pool.swapped:
|
|
|
|
|
connect_swapped(entrances, exits, avail_pool, True)
|
|
|
|
|
else:
|
|
|
|
|
connect_random(entrances, exits, avail_pool, True)
|
|
|
|
|
avail_pool.skull_handled = True
|
|
|
|
|
handle_skull_woods_entrances(avail_pool, pool['entrances'])
|
|
|
|
|
else:
|
|
|
|
|
entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances'])
|
|
|
|
|
do_main_shuffle(entrances, exits, avail_pool, mode_cfg)
|
|
|
|
|
@@ -239,12 +235,13 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
|
|
|
|
|
# mandatory exits
|
|
|
|
|
rem_entrances, rem_exits = set(), set()
|
|
|
|
|
if not cross_world:
|
|
|
|
|
determine_dungeon_restrictions(avail)
|
|
|
|
|
mand_exits = figure_out_must_exits_same_world(entrances, exits, avail)
|
|
|
|
|
must_exit_lw, must_exit_dw, lw_entrances, dw_entrances, multi_exit_caves, hyrule_forced = mand_exits
|
|
|
|
|
if hyrule_forced:
|
|
|
|
|
do_mandatory_connections(avail, lw_entrances, hyrule_forced, must_exit_lw)
|
|
|
|
|
else:
|
|
|
|
|
do_mandatory_connections(avail, lw_entrances, multi_exit_caves, must_exit_lw)
|
|
|
|
|
must_exit_lw, must_exit_dw, lw_entrances, dw_entrances, multi_exit_caves = mand_exits
|
|
|
|
|
lw_candidates = filter_restricted_caves(multi_exit_caves, 'LightWorld', avail)
|
|
|
|
|
other_candidates = [x for x in multi_exit_caves if x not in lw_candidates] # remember those not passed in
|
|
|
|
|
do_mandatory_connections(avail, lw_entrances, lw_candidates, must_exit_lw)
|
|
|
|
|
multi_exit_caves = other_candidates + lw_candidates # rebuild list from the lw_candidates and those not passed
|
|
|
|
|
# remove old man house as connector - not valid for dw must_exit if it is a spawn point
|
|
|
|
|
if not avail.inverted:
|
|
|
|
|
new_mec = []
|
|
|
|
|
@@ -254,7 +251,10 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
|
|
|
|
|
else:
|
|
|
|
|
new_mec.append(cave_option)
|
|
|
|
|
multi_exit_caves = new_mec
|
|
|
|
|
do_mandatory_connections(avail, dw_entrances, multi_exit_caves, must_exit_dw)
|
|
|
|
|
dw_candidates = filter_restricted_caves(multi_exit_caves, 'DarkWorld', avail)
|
|
|
|
|
other_candidates = [x for x in multi_exit_caves if x not in dw_candidates] # remember those not passed in
|
|
|
|
|
do_mandatory_connections(avail, dw_entrances, dw_candidates, must_exit_dw)
|
|
|
|
|
multi_exit_caves = other_candidates + dw_candidates # rebuild list from the dw_candidates and those not passed
|
|
|
|
|
rem_entrances.update(lw_entrances)
|
|
|
|
|
rem_entrances.update(dw_entrances)
|
|
|
|
|
else:
|
|
|
|
|
@@ -351,6 +351,10 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
|
|
|
|
|
if bonk_fairy_exception(x):
|
|
|
|
|
lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x)
|
|
|
|
|
do_same_world_connectors(lw_entrances, dw_entrances, multi_exit_caves, avail)
|
|
|
|
|
if avail.world.doorShuffle[avail.player] != 'vanilla':
|
|
|
|
|
determine_dungeon_restrictions(avail)
|
|
|
|
|
possibles = figure_out_possible_exits(rem_exits)
|
|
|
|
|
do_same_world_possible_connectors(lw_entrances, dw_entrances, possibles, avail)
|
|
|
|
|
unused_entrances.update(lw_entrances)
|
|
|
|
|
unused_entrances.update(dw_entrances)
|
|
|
|
|
else:
|
|
|
|
|
@@ -436,12 +440,16 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe
|
|
|
|
|
|
|
|
|
|
if not keep_together:
|
|
|
|
|
targets = [avail.one_way_map[x] for x in holes_to_shuffle]
|
|
|
|
|
connect_random(holes_to_shuffle, targets, avail)
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
connect_swapped(holes_to_shuffle, targets, avail)
|
|
|
|
|
else:
|
|
|
|
|
connect_random(holes_to_shuffle, targets, avail)
|
|
|
|
|
remove_from_list(entrances, holes_to_shuffle)
|
|
|
|
|
remove_from_list(exits, targets)
|
|
|
|
|
return # we're done here
|
|
|
|
|
|
|
|
|
|
hole_entrances, hole_targets = [], []
|
|
|
|
|
leftover_hole_entrances, leftover_hole_targets = [], []
|
|
|
|
|
for hole in drop_map:
|
|
|
|
|
if hole in avail.original_entrances and hole in linked_drop_map:
|
|
|
|
|
linked_entrance = linked_drop_map[hole]
|
|
|
|
|
@@ -451,40 +459,39 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe
|
|
|
|
|
target_drop = avail.one_way_map[hole]
|
|
|
|
|
if target_exit in exits and target_drop in exits:
|
|
|
|
|
hole_targets.append((target_exit, target_drop))
|
|
|
|
|
else:
|
|
|
|
|
if hole in avail.original_entrances and hole in entrances:
|
|
|
|
|
leftover_hole_entrances.append(hole)
|
|
|
|
|
if drop_map[hole] in exits:
|
|
|
|
|
leftover_hole_targets.append(drop_map[hole])
|
|
|
|
|
|
|
|
|
|
random.shuffle(hole_entrances)
|
|
|
|
|
if not cross_world and 'Sanctuary Grave' in holes_to_shuffle:
|
|
|
|
|
hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player)
|
|
|
|
|
is_hc_in_dw = avail.world.mode[avail.player] == 'inverted'
|
|
|
|
|
if hc.connected_region:
|
|
|
|
|
is_hc_in_dw = hc.connected_region.type == RegionType.DarkWorld
|
|
|
|
|
chosen_entrance = None
|
|
|
|
|
if is_hc_in_dw:
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances and e[0] != 'Sanctuary')
|
|
|
|
|
if not cross_world:
|
|
|
|
|
if 'Sanctuary Grave' in holes_to_shuffle:
|
|
|
|
|
hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player)
|
|
|
|
|
is_hc_in_dw = avail.world.mode[avail.player] == 'inverted'
|
|
|
|
|
if hc.connected_region:
|
|
|
|
|
is_hc_in_dw = hc.connected_region.type == RegionType.DarkWorld
|
|
|
|
|
chosen_entrance = None
|
|
|
|
|
if is_hc_in_dw:
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances and e[0] != 'Sanctuary')
|
|
|
|
|
if not chosen_entrance:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances)
|
|
|
|
|
if not chosen_entrance:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances)
|
|
|
|
|
if not chosen_entrance:
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances and e[0] != 'Sanctuary')
|
|
|
|
|
if not chosen_entrance:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances)
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances and e[0] != 'Sanctuary')
|
|
|
|
|
if not chosen_entrance:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances)
|
|
|
|
|
|
|
|
|
|
if chosen_entrance:
|
|
|
|
|
hole_entrances.remove(chosen_entrance)
|
|
|
|
|
sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit')
|
|
|
|
|
hole_targets.remove(sanc_interior)
|
|
|
|
|
connect_two_way(chosen_entrance[0], sanc_interior[0], avail) # two-way exit
|
|
|
|
|
connect_entrance(chosen_entrance[1], sanc_interior[1], avail) # hole
|
|
|
|
|
remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]])
|
|
|
|
|
remove_from_list(exits, [sanc_interior[0], sanc_interior[1]])
|
|
|
|
|
if avail.swapped and drop_map[chosen_entrance[1]] != sanc_interior[1]:
|
|
|
|
|
swap_ent, swap_ext = connect_swap(chosen_entrance[0], sanc_interior[0], avail)
|
|
|
|
|
swap_drop, swap_tgt = connect_swap(chosen_entrance[1], sanc_interior[1], avail)
|
|
|
|
|
hole_entrances.remove((swap_ent, swap_drop))
|
|
|
|
|
hole_targets.remove((swap_ext, swap_tgt))
|
|
|
|
|
remove_from_list(entrances, [swap_ent, swap_drop])
|
|
|
|
|
remove_from_list(exits, [swap_ext, swap_tgt])
|
|
|
|
|
if chosen_entrance:
|
|
|
|
|
connect_hole_via_interior(chosen_entrance, 'Sanctuary Exit', hole_entrances, hole_targets, entrances, exits, avail)
|
|
|
|
|
if 'Skull Woods First Section Hole (North)' in holes_to_shuffle:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances)
|
|
|
|
|
connect_hole_via_interior(chosen_entrance, 'Skull Woods First Section Exit', hole_entrances, hole_targets, entrances, exits, avail)
|
|
|
|
|
if 'Skull Woods Second Section Hole' in holes_to_shuffle:
|
|
|
|
|
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances)
|
|
|
|
|
connect_hole_via_interior(chosen_entrance, 'Skull Woods Second Section Exit (East)', hole_entrances, hole_targets, entrances, exits, avail)
|
|
|
|
|
|
|
|
|
|
random.shuffle(hole_targets)
|
|
|
|
|
while len(hole_entrances):
|
|
|
|
|
@@ -506,6 +513,31 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe
|
|
|
|
|
remove_from_list(entrances, [swap_ent, swap_drop])
|
|
|
|
|
remove_from_list(exits, [swap_ext, swap_tgt])
|
|
|
|
|
|
|
|
|
|
if leftover_hole_entrances and leftover_hole_targets:
|
|
|
|
|
remove_from_list(entrances, leftover_hole_entrances)
|
|
|
|
|
remove_from_list(exits, leftover_hole_targets)
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
connect_swapped(leftover_hole_entrances, leftover_hole_targets, avail)
|
|
|
|
|
else:
|
|
|
|
|
connect_random(leftover_hole_entrances, leftover_hole_targets, avail)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def connect_hole_via_interior(chosen_entrance, interior, hole_entrances, hole_targets, entrances, exits, avail):
|
|
|
|
|
hole_entrances.remove(chosen_entrance)
|
|
|
|
|
interior = next(target for target in hole_targets if target[0] == interior)
|
|
|
|
|
hole_targets.remove(interior)
|
|
|
|
|
connect_two_way(chosen_entrance[0], interior[0], avail)
|
|
|
|
|
connect_entrance(chosen_entrance[1], interior[1], avail)
|
|
|
|
|
remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]])
|
|
|
|
|
remove_from_list(exits, [interior[0], interior[1]])
|
|
|
|
|
if avail.swapped and drop_map[chosen_entrance[1]] != interior[1]:
|
|
|
|
|
swap_ent, swap_ext = connect_swap(chosen_entrance[0], interior[0], avail)
|
|
|
|
|
swap_drop, swap_tgt = connect_swap(chosen_entrance[1], interior[1], avail)
|
|
|
|
|
hole_entrances.remove((swap_ent, swap_drop))
|
|
|
|
|
hole_targets.remove((swap_ext, swap_tgt))
|
|
|
|
|
remove_from_list(entrances, [swap_ent, swap_drop])
|
|
|
|
|
remove_from_list(exits, [swap_ext, swap_tgt])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def do_links_house(entrances, exits, avail, cross_world):
|
|
|
|
|
lh_exit = 'Links House Exit'
|
|
|
|
|
@@ -628,38 +660,77 @@ def figure_out_true_exits(exits, avail):
|
|
|
|
|
return multi_exit_caves
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# todo: figure out hyrule forced better
|
|
|
|
|
def figure_out_possible_exits(exits):
|
|
|
|
|
possible_multi_exit_caves = []
|
|
|
|
|
for item in doors_possible_connectors:
|
|
|
|
|
if item in exits:
|
|
|
|
|
remove_from_list(exits, item)
|
|
|
|
|
possible_multi_exit_caves.append(item)
|
|
|
|
|
return possible_multi_exit_caves
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def determine_dungeon_restrictions(avail):
|
|
|
|
|
check_for_hc = (avail.is_standard() or avail.world.doorShuffle[avail.player] != 'vanilla')
|
|
|
|
|
for check in dungeon_restriction_checks:
|
|
|
|
|
dungeon_exits, drop_regions = check
|
|
|
|
|
if check_for_hc and any('Hyrule Castle' in x for x in dungeon_exits):
|
|
|
|
|
avail.same_world_restricted.update({x: 'LightWorld' for x in dungeon_exits})
|
|
|
|
|
else:
|
|
|
|
|
restriction = None
|
|
|
|
|
for x in dungeon_exits:
|
|
|
|
|
ent = avail.world.get_entrance(x, avail.player)
|
|
|
|
|
if ent.connected_region:
|
|
|
|
|
if ent.connected_region.type == RegionType.LightWorld:
|
|
|
|
|
restriction = 'LightWorld'
|
|
|
|
|
elif ent.connected_region.type == RegionType.DarkWorld:
|
|
|
|
|
restriction = 'DarkWorld'
|
|
|
|
|
# Holes only restrict
|
|
|
|
|
for x in drop_regions:
|
|
|
|
|
region = avail.world.get_region(x, avail.player)
|
|
|
|
|
ent = next((ent for ent in region.entrances if ent.parent_region and ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None)
|
|
|
|
|
if ent:
|
|
|
|
|
if ent.parent_region.type == RegionType.LightWorld and not avail.inverted:
|
|
|
|
|
restriction = 'LightWorld'
|
|
|
|
|
elif ent.parent_region.type == RegionType.DarkWorld and avail.inverted:
|
|
|
|
|
restriction = 'DarkWorld'
|
|
|
|
|
if restriction:
|
|
|
|
|
avail.same_world_restricted.update({x: restriction for x in dungeon_exits})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def figure_out_must_exits_same_world(entrances, exits, avail):
|
|
|
|
|
lw_entrances, dw_entrances = [], []
|
|
|
|
|
hyrule_forced = None
|
|
|
|
|
check_for_hc = (avail.is_standard() or avail.world.doorShuffle[avail.player] != 'vanilla')
|
|
|
|
|
|
|
|
|
|
for x in entrances:
|
|
|
|
|
lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x)
|
|
|
|
|
|
|
|
|
|
multi_exit_caves = figure_out_connectors(exits)
|
|
|
|
|
if check_for_hc:
|
|
|
|
|
for option in multi_exit_caves:
|
|
|
|
|
if any(x in option for x in ['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (East)',
|
|
|
|
|
'Hyrule Castle Exit (West)']):
|
|
|
|
|
hyrule_forced = [option]
|
|
|
|
|
if hyrule_forced:
|
|
|
|
|
remove_from_list(multi_exit_caves, hyrule_forced)
|
|
|
|
|
if not avail.inverted and not avail.skull_handled:
|
|
|
|
|
skull_connector = [x for x in ['Skull Woods Second Section Exit (West)', 'Skull Woods Second Section Exit (East)'] if x in exits]
|
|
|
|
|
multi_exit_caves.append(skull_connector)
|
|
|
|
|
|
|
|
|
|
must_exit_lw, must_exit_dw, unfiltered_lw, unfiltered_dw = must_exits_helper(avail, lw_entrances, dw_entrances)
|
|
|
|
|
|
|
|
|
|
return must_exit_lw, must_exit_dw, lw_entrances, dw_entrances, multi_exit_caves, hyrule_forced
|
|
|
|
|
return must_exit_lw, must_exit_dw, lw_entrances, dw_entrances, multi_exit_caves
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def must_exits_helper(avail, lw_entrances, dw_entrances):
|
|
|
|
|
must_exit_lw_orig = (Inverted_LW_Must_Exit if avail.inverted else LW_Must_Exit).copy()
|
|
|
|
|
must_exit_dw_orig = (Inverted_DW_Must_Exit if avail.inverted else DW_Must_Exit).copy()
|
|
|
|
|
if not avail.inverted and not avail.skull_handled:
|
|
|
|
|
must_exit_dw_orig.append(('Skull Woods Second Section Door (West)', 'Skull Woods Final Section'))
|
|
|
|
|
must_exit_dw_orig.append('Skull Woods Second Section Door (West)')
|
|
|
|
|
must_exit_lw = must_exit_filter(avail, must_exit_lw_orig, lw_entrances)
|
|
|
|
|
must_exit_dw = must_exit_filter(avail, must_exit_dw_orig, dw_entrances)
|
|
|
|
|
return must_exit_lw, must_exit_dw, flatten(must_exit_lw_orig), flatten(must_exit_dw_orig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def filter_restricted_caves(multi_exit_caves, restriction, avail):
|
|
|
|
|
candidates = []
|
|
|
|
|
for cave in multi_exit_caves:
|
|
|
|
|
if all(x not in avail.same_world_restricted or avail.same_world_restricted[x] == restriction for x in cave):
|
|
|
|
|
candidates.append(cave)
|
|
|
|
|
return candidates
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def flatten(list_to_flatten):
|
|
|
|
|
ret = []
|
|
|
|
|
for item in list_to_flatten:
|
|
|
|
|
@@ -672,11 +743,14 @@ def flatten(list_to_flatten):
|
|
|
|
|
|
|
|
|
|
def figure_out_must_exits_cross_world(entrances, exits, avail):
|
|
|
|
|
multi_exit_caves = figure_out_connectors(exits)
|
|
|
|
|
if not avail.skull_handled:
|
|
|
|
|
skull_connector = [x for x in ['Skull Woods Second Section Exit (West)', 'Skull Woods Second Section Exit (East)'] if x in exits]
|
|
|
|
|
multi_exit_caves.append(skull_connector)
|
|
|
|
|
|
|
|
|
|
must_exit_lw = (Inverted_LW_Must_Exit if avail.inverted else LW_Must_Exit).copy()
|
|
|
|
|
must_exit_dw = (Inverted_DW_Must_Exit if avail.inverted else DW_Must_Exit).copy()
|
|
|
|
|
if not avail.inverted and not avail.skull_handled:
|
|
|
|
|
must_exit_dw.append(('Skull Woods Second Section Door (West)', 'Skull Woods Final Section'))
|
|
|
|
|
must_exit_dw.append('Skull Woods Second Section Door (West)')
|
|
|
|
|
must_exit = must_exit_filter(avail, must_exit_lw + must_exit_dw, entrances)
|
|
|
|
|
|
|
|
|
|
return must_exit, multi_exit_caves
|
|
|
|
|
@@ -687,7 +761,7 @@ def do_same_world_connectors(lw_entrances, dw_entrances, caves, avail):
|
|
|
|
|
random.shuffle(dw_entrances)
|
|
|
|
|
random.shuffle(caves)
|
|
|
|
|
while caves:
|
|
|
|
|
# connect highest exit count caves first, prevent issue where we have 2 or 3 exits across worlds left to fill
|
|
|
|
|
# connect highest-exit-count caves first, prevent issue where we have 2 or 3 exits across worlds left to fill
|
|
|
|
|
cave_candidate = (None, 0)
|
|
|
|
|
for i, cave in enumerate(caves):
|
|
|
|
|
if isinstance(cave, str):
|
|
|
|
|
@@ -696,12 +770,19 @@ def do_same_world_connectors(lw_entrances, dw_entrances, caves, avail):
|
|
|
|
|
cave_candidate = (i, len(cave))
|
|
|
|
|
cave = caves.pop(cave_candidate[0])
|
|
|
|
|
|
|
|
|
|
target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances
|
|
|
|
|
if isinstance(cave, str):
|
|
|
|
|
cave = (cave,)
|
|
|
|
|
target, restriction = None, None
|
|
|
|
|
if any(x in avail.same_world_restricted for x in cave):
|
|
|
|
|
restriction = next(avail.same_world_restricted[x] for x in cave if x in avail.same_world_restricted)
|
|
|
|
|
target = lw_entrances if restriction == 'LightWorld' else dw_entrances
|
|
|
|
|
if target is None:
|
|
|
|
|
target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances
|
|
|
|
|
|
|
|
|
|
# check if we can still fit the cave into our target group
|
|
|
|
|
if len(target) < len(cave):
|
|
|
|
|
if restriction:
|
|
|
|
|
raise Exception('Not enough entrances for restricted cave, algorithm needs revision (main)')
|
|
|
|
|
# need to use other set
|
|
|
|
|
target = lw_entrances if target is dw_entrances else dw_entrances
|
|
|
|
|
|
|
|
|
|
@@ -715,6 +796,18 @@ def do_same_world_connectors(lw_entrances, dw_entrances, caves, avail):
|
|
|
|
|
connect_two_way(target.pop(), ext, avail)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def do_same_world_possible_connectors(lw_entrances, dw_entrances, possibles, avail):
|
|
|
|
|
random.shuffle(possibles)
|
|
|
|
|
while possibles:
|
|
|
|
|
possible = possibles.pop()
|
|
|
|
|
target = None
|
|
|
|
|
if possible in avail.same_world_restricted:
|
|
|
|
|
target = lw_entrances if avail.same_world_restricted[possible] == 'LightWorld' else dw_entrances
|
|
|
|
|
if target is None:
|
|
|
|
|
target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances
|
|
|
|
|
connect_two_way(target.pop(), possible, avail)
|
|
|
|
|
determine_dungeon_restrictions(avail)
|
|
|
|
|
|
|
|
|
|
def do_cross_world_connectors(entrances, caves, avail):
|
|
|
|
|
random.shuffle(entrances)
|
|
|
|
|
random.shuffle(caves)
|
|
|
|
|
@@ -762,6 +855,37 @@ def do_cross_world_connectors(entrances, caves, avail):
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_skull_woods_drops(avail, pool, mode_cfg):
|
|
|
|
|
skull_woods = avail.world.skullwoods[avail.player]
|
|
|
|
|
if skull_woods in ['restricted', 'loose']:
|
|
|
|
|
for drop in pool:
|
|
|
|
|
target = drop_map[drop]
|
|
|
|
|
connect_entrance(drop, target, avail)
|
|
|
|
|
elif skull_woods == 'original':
|
|
|
|
|
holes, targets = find_entrances_and_targets_drops(avail, pool)
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
connect_swapped(holes, targets, avail)
|
|
|
|
|
else:
|
|
|
|
|
connect_random(holes, targets, avail)
|
|
|
|
|
elif skull_woods == 'followlinked':
|
|
|
|
|
keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True
|
|
|
|
|
if keep_together:
|
|
|
|
|
for drop in ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)']:
|
|
|
|
|
target = drop_map[drop]
|
|
|
|
|
connect_entrance(drop, target, avail)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_skull_woods_entrances(avail, pool):
|
|
|
|
|
skull_woods = avail.world.skullwoods[avail.player]
|
|
|
|
|
if skull_woods in ['restricted', 'original']:
|
|
|
|
|
entrances, exits = find_entrances_and_exits(avail, pool)
|
|
|
|
|
if avail.swapped:
|
|
|
|
|
connect_swapped(entrances, exits, avail, True)
|
|
|
|
|
else:
|
|
|
|
|
connect_random(entrances, exits, avail, True)
|
|
|
|
|
avail.skull_handled = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def do_fixed_shuffle(avail, entrance_list):
|
|
|
|
|
max_size = 0
|
|
|
|
|
options = {}
|
|
|
|
|
@@ -803,8 +927,6 @@ def do_same_world_shuffle(avail, pool_def):
|
|
|
|
|
multi_exit = pool_def['connectors']
|
|
|
|
|
# complete_entrance_set = set()
|
|
|
|
|
lw_entrances, dw_entrances, multi_exits_caves, other_exits = [], [], [], []
|
|
|
|
|
hyrule_forced = None
|
|
|
|
|
check_for_hc = avail.is_standard() or avail.world.doorShuffle[avail.player] != 'vanilla'
|
|
|
|
|
|
|
|
|
|
single_entrances, single_exits = find_entrances_and_exits(avail, single_exit)
|
|
|
|
|
other_exits.extend(single_exits)
|
|
|
|
|
@@ -814,12 +936,7 @@ def do_same_world_shuffle(avail, pool_def):
|
|
|
|
|
for option in multi_exit:
|
|
|
|
|
multi_entrances, multi_exits = find_entrances_and_exits(avail, option)
|
|
|
|
|
# complete_entrance_set.update(multi_entrances)
|
|
|
|
|
if check_for_hc and any(x in multi_entrances for x in ['Hyrule Castle Entrance (South)',
|
|
|
|
|
'Hyrule Castle Entrance (East)',
|
|
|
|
|
'Hyrule Castle Entrance (West)']):
|
|
|
|
|
hyrule_forced = [multi_exits]
|
|
|
|
|
else:
|
|
|
|
|
multi_exits_caves.append(multi_exits)
|
|
|
|
|
multi_exits_caves.append(multi_exits)
|
|
|
|
|
for x in multi_entrances:
|
|
|
|
|
(dw_entrances, lw_entrances)[x in LW_Entrances].append(x)
|
|
|
|
|
|
|
|
|
|
@@ -828,11 +945,16 @@ def do_same_world_shuffle(avail, pool_def):
|
|
|
|
|
must_exit_lw = must_exit_filter(avail, must_exit_lw, lw_entrances)
|
|
|
|
|
must_exit_dw = must_exit_filter(avail, must_exit_dw, dw_entrances)
|
|
|
|
|
|
|
|
|
|
if hyrule_forced:
|
|
|
|
|
do_mandatory_connections(avail, lw_entrances, hyrule_forced, must_exit_lw)
|
|
|
|
|
else:
|
|
|
|
|
do_mandatory_connections(avail, lw_entrances, multi_exits_caves, must_exit_lw)
|
|
|
|
|
do_mandatory_connections(avail, dw_entrances, multi_exits_caves, must_exit_dw)
|
|
|
|
|
determine_dungeon_restrictions(avail)
|
|
|
|
|
lw_candidates = filter_restricted_caves(multi_exits_caves, 'LightWorld', avail)
|
|
|
|
|
other_candidates = [x for x in multi_exits_caves if x not in lw_candidates] # remember those not passed in
|
|
|
|
|
do_mandatory_connections(avail, lw_entrances, lw_candidates, must_exit_lw)
|
|
|
|
|
multi_exits_caves = other_candidates + lw_candidates # rebuild list from the lw_candidates and those not passed
|
|
|
|
|
|
|
|
|
|
dw_candidates = filter_restricted_caves(multi_exits_caves, 'DarkWorld', avail)
|
|
|
|
|
other_candidates = [x for x in multi_exits_caves if x not in dw_candidates] # remember those not passed in
|
|
|
|
|
do_mandatory_connections(avail, dw_entrances, dw_candidates, must_exit_dw)
|
|
|
|
|
multi_exits_caves = other_candidates + dw_candidates # rebuild list from the dw_candidates and those not passed
|
|
|
|
|
|
|
|
|
|
# connect caves
|
|
|
|
|
random.shuffle(lw_entrances)
|
|
|
|
|
@@ -845,8 +967,15 @@ def do_same_world_shuffle(avail, pool_def):
|
|
|
|
|
cave_candidate = (i, len(cave))
|
|
|
|
|
cave = multi_exits_caves.pop(cave_candidate[0])
|
|
|
|
|
|
|
|
|
|
target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances
|
|
|
|
|
target, restriction = None, None
|
|
|
|
|
if any(x in avail.same_world_restricted for x in cave):
|
|
|
|
|
restriction = next(avail.same_world_restricted[x] for x in cave if x in avail.same_world_restricted)
|
|
|
|
|
target = lw_entrances if restriction == 'LightWorld' else dw_entrances
|
|
|
|
|
if target is None:
|
|
|
|
|
target = lw_entrances if random.randint(0, 1) == 0 else dw_entrances
|
|
|
|
|
if len(target) < len(cave): # swap because we ran out of entrances in that world
|
|
|
|
|
if restriction:
|
|
|
|
|
raise Exception('Not enough entrances for restricted cave, algorithm needs revision (dungeonsfull)')
|
|
|
|
|
target = lw_entrances if target is dw_entrances else dw_entrances
|
|
|
|
|
|
|
|
|
|
for ext in cave:
|
|
|
|
|
@@ -1386,6 +1515,12 @@ modes = {
|
|
|
|
|
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
|
|
|
|
'Skull Woods Second Section Door (West)']
|
|
|
|
|
},
|
|
|
|
|
'skull_layout': {
|
|
|
|
|
'special': 'vanilla',
|
|
|
|
|
'condition': '',
|
|
|
|
|
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
|
|
|
|
'Skull Woods Second Section Door (West)']
|
|
|
|
|
},
|
|
|
|
|
'single_entrance_dungeon': {
|
|
|
|
|
'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section',
|
|
|
|
|
'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', 'Ganons Tower']
|
|
|
|
|
@@ -1419,13 +1554,15 @@ modes = {
|
|
|
|
|
'sanc_flag': 'light_world', # always light world flag
|
|
|
|
|
'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section',
|
|
|
|
|
'Agahnims Tower', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace',
|
|
|
|
|
'Ganons Tower'],
|
|
|
|
|
'Ganons Tower', 'Desert Palace Entrance (North)', 'Dark Death Mountain Ledge (East)'],
|
|
|
|
|
'connectors': [['Hyrule Castle Entrance (South)', 'Hyrule Castle Entrance (East)',
|
|
|
|
|
'Hyrule Castle Entrance (West)'],
|
|
|
|
|
['Desert Palace Entrance (South)', 'Desert Palace Entrance (East)',
|
|
|
|
|
'Desert Palace Entrance (West)', 'Desert Palace Entrance (North)'],
|
|
|
|
|
'Desert Palace Entrance (West)'],
|
|
|
|
|
['Turtle Rock', 'Turtle Rock Isolated Ledge Entrance',
|
|
|
|
|
'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)']]
|
|
|
|
|
'Dark Death Mountain Ledge (West)'],
|
|
|
|
|
['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)',
|
|
|
|
|
'Skull Woods First Section Door']]
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
@@ -1611,6 +1748,12 @@ modes = {
|
|
|
|
|
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
|
|
|
|
'Skull Woods Second Section Door (West)']
|
|
|
|
|
},
|
|
|
|
|
'skull_layout': {
|
|
|
|
|
'special': 'vanilla',
|
|
|
|
|
'condition': '',
|
|
|
|
|
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
|
|
|
|
'Skull Woods Second Section Door (West)']
|
|
|
|
|
},
|
|
|
|
|
'single_entrance_dungeon': {
|
|
|
|
|
'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section',
|
|
|
|
|
'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', 'Ganons Tower']
|
|
|
|
|
@@ -1700,6 +1843,12 @@ modes = {
|
|
|
|
|
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
|
|
|
|
'Skull Woods Second Section Door (West)']
|
|
|
|
|
},
|
|
|
|
|
'skull_layout': {
|
|
|
|
|
'special': 'vanilla',
|
|
|
|
|
'condition': '',
|
|
|
|
|
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
|
|
|
|
'Skull Woods Second Section Door (West)']
|
|
|
|
|
},
|
|
|
|
|
'single_entrance_dungeon': {
|
|
|
|
|
'entrances': ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section',
|
|
|
|
|
'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace', 'Ganons Tower']
|
|
|
|
|
@@ -1801,7 +1950,10 @@ linked_drop_map = {
|
|
|
|
|
'Lumberjack Tree Tree': 'Lumberjack Tree Cave',
|
|
|
|
|
'Sanctuary Grave': 'Sanctuary',
|
|
|
|
|
'Pyramid Hole': 'Pyramid Entrance',
|
|
|
|
|
'Inverted Pyramid Hole': 'Inverted Pyramid Entrance'
|
|
|
|
|
'Inverted Pyramid Hole': 'Inverted Pyramid Entrance',
|
|
|
|
|
|
|
|
|
|
'Skull Woods First Section Hole (North)': 'Skull Woods First Section Door',
|
|
|
|
|
'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (East)',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entrance_map = {
|
|
|
|
|
@@ -2066,6 +2218,19 @@ Connector_Exit_Set = {
|
|
|
|
|
'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dungeon_restriction_checks = [
|
|
|
|
|
(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Sanctuary Exit'], ['Sewer Drop']),
|
|
|
|
|
(['Desert Palace Exit (South)', 'Desert Palace Exit (East)', 'Desert Palace Exit (West)', 'Desert Palace Exit (North)'], []),
|
|
|
|
|
(['Turtle Rock Exit (Front)', 'Turtle Rock Isolated Ledge Exit', 'Turtle Rock Ledge Exit (West)', 'Turtle Rock Ledge Exit (East)'], []),
|
|
|
|
|
(['Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', 'Skull Woods Final Section Exit'],
|
|
|
|
|
['Skull Pinball', 'Skull Left Drop', 'Skull Pot Circle', 'Skull Back Drop'])
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
doors_possible_connectors = [
|
|
|
|
|
'Sanctuary Exit', 'Desert Palace Exit (North)', 'Skull Woods First Section Exit',
|
|
|
|
|
'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', 'Skull Woods Final Section Exit'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups
|
|
|
|
|
Must_Exit_Invalid_Connections = defaultdict(set, {
|
|
|
|
|
'Dark Death Mountain Ledge (East)': {'Dark Death Mountain Ledge (West)', 'Mimic Cave'},
|
|
|
|
|
|