2633 lines
146 KiB
Python
2633 lines
146 KiB
Python
import RaceRandom as random, logging, copy
|
|
from collections import OrderedDict, defaultdict
|
|
from DungeonGenerator import GenerationException
|
|
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
|
|
from Regions import mark_light_dark_world_regions
|
|
from source.overworld.EntranceShuffle2 import connect_simple
|
|
from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel
|
|
from OverworldGlitchRules import create_owg_connections
|
|
from Utils import bidict
|
|
|
|
version_number = '0.6.0.2'
|
|
# branch indicator is intentionally different across branches
|
|
version_branch = ''
|
|
|
|
__version__ = '%s%s' % (version_number, version_branch)
|
|
|
|
parallel_links_new = None # needs to be globally available, reset every new generation/player
|
|
|
|
def link_overworld(world, player):
|
|
global parallel_links_new
|
|
|
|
# setup mandatory connections
|
|
for exitname, regionname in mandatory_connections:
|
|
connect_simple(world, exitname, regionname, player)
|
|
|
|
def performSwap(groups, swaps):
|
|
def getParallel(edgename):
|
|
if edgename in parallel_links_new:
|
|
return parallel_links_new[edgename]
|
|
else:
|
|
raise Exception('No parallel edge found for edge %s', edgename)
|
|
|
|
def getNewSets(all_set, other_set):
|
|
new_all_set = list(map(getParallel, all_set))
|
|
if not all(edge in orig_swaps for edge in new_all_set):
|
|
raise Exception('Cannot move a parallel edge without the other')
|
|
else:
|
|
for edge in new_all_set:
|
|
swaps.remove(edge)
|
|
new_other_set = getNewSet(other_set)
|
|
return (new_all_set, new_other_set)
|
|
|
|
def getNewSet(edge_set):
|
|
new_set = []
|
|
for edge in edge_set:
|
|
if edge in orig_swaps:
|
|
new_edge = getParallel(edge)
|
|
if new_edge not in orig_swaps:
|
|
raise Exception('Cannot move a parallel edge without the other')
|
|
new_set.append(new_edge)
|
|
swaps.remove(new_edge)
|
|
else:
|
|
new_set.append(edge)
|
|
return new_set
|
|
|
|
# swaps edges from one pool to another
|
|
orig_swaps = copy.deepcopy(swaps)
|
|
new_groups = {}
|
|
for group in groups.keys():
|
|
new_groups[group] = ([],[])
|
|
|
|
for group in groups.keys():
|
|
(mode, wrld, dir, terrain, parallel, count, custom) = group
|
|
for (forward_set, back_set) in zip(groups[group][0], groups[group][1]):
|
|
anyF = any(edge in orig_swaps for edge in forward_set)
|
|
anyB = any(edge in orig_swaps for edge in back_set)
|
|
allF = all(edge in orig_swaps for edge in forward_set)
|
|
allB = all(edge in orig_swaps for edge in back_set)
|
|
if not (anyF or anyB):
|
|
# no change
|
|
new_groups[group][0].append(forward_set)
|
|
new_groups[group][1].append(back_set)
|
|
elif allF and allB:
|
|
# move both sets
|
|
if parallel == IsParallel.Yes and not (all(edge in orig_swaps for edge in map(getParallel, forward_set)) and all(edge in orig_swaps for edge in map(getParallel, back_set))):
|
|
raise Exception('Cannot move a parallel edge without the other')
|
|
new_mode = OpenStd.Open
|
|
if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)) not in new_groups.keys():
|
|
# when Links House tile is flipped, the DW edges need to get put into existing Standard group
|
|
new_mode = OpenStd.Standard
|
|
new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)][0].append(forward_set)
|
|
new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)][1].append(back_set)
|
|
for edge in forward_set:
|
|
swaps.remove(edge)
|
|
for edge in back_set:
|
|
swaps.remove(edge)
|
|
elif anyF or anyB:
|
|
if parallel == IsParallel.Yes:
|
|
if allF or allB:
|
|
# move one set
|
|
if allF and not (world.owKeepSimilar[player] and anyB):
|
|
(new_forward_set, new_back_set) = getNewSets(forward_set, back_set)
|
|
elif allB and not (world.owKeepSimilar[player] and anyF):
|
|
(new_back_set, new_forward_set) = getNewSets(back_set, forward_set)
|
|
else:
|
|
raise Exception('Cannot move an edge out of a Similar group')
|
|
new_groups[group][0].append(new_forward_set)
|
|
new_groups[group][1].append(new_back_set)
|
|
else:
|
|
# move individual edges
|
|
if not world.owKeepSimilar[player]:
|
|
new_groups[group][0].append(getNewSet(forward_set) if anyF else forward_set)
|
|
new_groups[group][1].append(getNewSet(back_set) if anyB else back_set)
|
|
else:
|
|
raise Exception('Cannot move an edge out of a Similar group')
|
|
else:
|
|
raise NotImplementedError('Cannot move one side of a non-parallel connection')
|
|
else:
|
|
raise NotImplementedError('Invalid OW Edge flip scenario')
|
|
return new_groups
|
|
|
|
trimmed_groups = copy.deepcopy(OWEdgeGroupsTerrain if world.owTerrain[player] else OWEdgeGroups)
|
|
swapped_edges = list()
|
|
|
|
# restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship
|
|
parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken)
|
|
if world.owKeepSimilar[player]:
|
|
del parallel_links_new['Maze Race ES']
|
|
del parallel_links_new['Kakariko Suburb WS']
|
|
for group in trimmed_groups.keys():
|
|
(std, region, axis, terrain, parallel, _, custom) = group
|
|
if parallel == IsParallel.Yes:
|
|
(forward_edges, back_edges) = trimmed_groups[group]
|
|
if ['Maze Race ES'] in forward_edges:
|
|
forward_edges.remove(['Maze Race ES'])
|
|
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES'])
|
|
if ['Kakariko Suburb WS'] in back_edges:
|
|
back_edges.remove(['Kakariko Suburb WS'])
|
|
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS'])
|
|
trimmed_groups[group] = (forward_edges, back_edges)
|
|
else:
|
|
for group in trimmed_groups.keys():
|
|
(std, region, axis, terrain, _, _, custom) = group
|
|
(forward_edges, back_edges) = trimmed_groups[group]
|
|
if ['Dig Game EC', 'Dig Game ES'] in forward_edges:
|
|
forward_edges.remove(['Dig Game EC', 'Dig Game ES'])
|
|
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES'])
|
|
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC'])
|
|
if ['Frog WC', 'Frog WS'] in back_edges:
|
|
back_edges.remove(['Frog WC', 'Frog WS'])
|
|
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS'])
|
|
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC'])
|
|
trimmed_groups[group] = (forward_edges, back_edges)
|
|
parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})}
|
|
|
|
connected_edges = []
|
|
if world.owShuffle[player] != 'vanilla':
|
|
trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
|
|
trimmed_groups = reorganize_groups(world, trimmed_groups, player)
|
|
|
|
# tile shuffle
|
|
logging.getLogger('').debug('Flipping overworld tiles')
|
|
if world.owMixed[player]:
|
|
tile_groups, force_flipped, force_nonflipped, undefined_chance, allow_flip_sanc = define_tile_groups(world, False, player)
|
|
world.allow_flip_sanc[player] = allow_flip_sanc
|
|
swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, (force_flipped, force_nonflipped, undefined_chance), player)
|
|
|
|
update_world_regions(world, player)
|
|
|
|
# update spoiler
|
|
s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40, 0x82)]))
|
|
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
|
s[0x00], s[0x03], s[0x05],
|
|
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
|
s[0x0a], s[0x0f],
|
|
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
|
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
|
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
|
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
|
s[0x40], s[0x32],s[0x33],s[0x34], s[0x37],
|
|
s[0x30], s[0x35],
|
|
s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
|
world.spoiler.set_map('swaps', text_output, world.owswaps[player][0], player)
|
|
|
|
# apply tile logical connections
|
|
if not world.is_bombshop_start(player):
|
|
connect_simple(world, 'Links House S&Q', 'Links House', player)
|
|
else:
|
|
connect_simple(world, 'Links House S&Q', 'Big Bomb Shop', player)
|
|
|
|
if not world.is_dark_chapel_start(player):
|
|
connect_simple(world, 'Sanctuary S&Q', 'Sanctuary', player)
|
|
else:
|
|
connect_simple(world, 'Sanctuary S&Q', 'Dark Sanctuary Hint', player)
|
|
|
|
if not world.is_tile_swapped(0x1b, player):
|
|
connect_simple(world, 'Other World S&Q', 'Pyramid Area', player)
|
|
else:
|
|
connect_simple(world, 'Other World S&Q', 'Hyrule Castle Ledge', player)
|
|
|
|
for owid in ow_connections.keys():
|
|
if not world.is_tile_swapped(owid, player):
|
|
for (exitname, regionname) in ow_connections[owid][0]:
|
|
connect_simple(world, exitname, regionname, player)
|
|
else:
|
|
for (exitname, regionname) in ow_connections[owid][1]:
|
|
connect_simple(world, exitname, regionname, player)
|
|
|
|
categorize_world_regions(world, player)
|
|
|
|
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
|
create_owg_connections(world, player)
|
|
|
|
# crossed shuffle
|
|
logging.getLogger('').debug('Crossing overworld edges')
|
|
|
|
# customizer setup
|
|
force_crossed = set()
|
|
force_noncrossed = set()
|
|
count_crossed = 0
|
|
limited_crossed = -1
|
|
undefined_chance = 50
|
|
if world.customizer:
|
|
custom_crossed = world.customizer.get_owcrossed()
|
|
if custom_crossed and player in custom_crossed:
|
|
custom_crossed = custom_crossed[player]
|
|
if 'force_crossed' in custom_crossed and len(custom_crossed['force_crossed']) > 0:
|
|
for edgename in custom_crossed['force_crossed']:
|
|
edge = world.get_owedge(edgename, player)
|
|
force_crossed.add(edge.name)
|
|
if 'force_noncrossed' in custom_crossed and len(custom_crossed['force_noncrossed']) > 0:
|
|
for edgename in custom_crossed['force_noncrossed']:
|
|
edge = world.get_owedge(edgename, player)
|
|
force_noncrossed.add(edge.name)
|
|
if 'limit_crossed' in custom_crossed:
|
|
if world.owCrossed[player] == 'unrestricted':
|
|
limited_crossed = custom_crossed['limit_crossed']
|
|
if 'undefined_chance' in custom_crossed:
|
|
undefined_chance = custom_crossed['undefined_chance']
|
|
|
|
if limited_crossed > -1:
|
|
# connect forced crossed non-parallel edges based on previously determined tile flips
|
|
for edge in swapped_edges:
|
|
if edge not in parallel_links_new:
|
|
world.owcrossededges[player].append(edge)
|
|
count_crossed = count_crossed + 1
|
|
|
|
if world.owCrossed[player] == 'grouped':
|
|
# the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work
|
|
# Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask
|
|
ow_crossed_tiles_mask = [[],[],[]]
|
|
tile_groups, force_flipped, force_nonflipped, undefined_chance, _ = define_tile_groups(world, True, player)
|
|
world.owcrossededges[player] = shuffle_tiles(world, tile_groups, ow_crossed_tiles_mask, True, (force_flipped, force_nonflipped, undefined_chance), player)
|
|
ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])]
|
|
|
|
# update spoiler
|
|
s = list(map(lambda x: 'O' if x not in ow_crossed_tiles else 'X', [i for i in range(0x40, 0x82)]))
|
|
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
|
s[0x00], s[0x03], s[0x05],
|
|
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
|
s[0x0a], s[0x0f],
|
|
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
|
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
|
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
|
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
|
s[0x40], s[0x32],s[0x33],s[0x34], s[0x37],
|
|
s[0x30], s[0x35],
|
|
s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
|
world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player)
|
|
elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'):
|
|
crossed_candidates = list()
|
|
for group in trimmed_groups.keys():
|
|
(mode, wrld, dir, terrain, parallel, count, _) = group
|
|
if wrld == WorldType.Light and mode != OpenStd.Standard:
|
|
for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]):
|
|
if forward_set[0] in parallel_links_new:
|
|
forward_parallel = [parallel_links_new[e] for e in forward_set]
|
|
back_parallel = [parallel_links_new[e] for e in back_set]
|
|
forward_combine = forward_set+forward_parallel
|
|
back_combine = back_set+back_parallel
|
|
combine_set = forward_combine+back_combine
|
|
|
|
skip_forward = False
|
|
if world.owShuffle[player] == 'vanilla':
|
|
if any(edge in force_crossed for edge in combine_set):
|
|
if not any(edge in force_noncrossed for edge in combine_set):
|
|
if any(edge in force_crossed for edge in forward_combine):
|
|
world.owcrossededges[player].extend(forward_set)
|
|
count_crossed = count_crossed + 1
|
|
continue
|
|
else:
|
|
world.owcrossededges[player].extend(back_set)
|
|
count_crossed = count_crossed + 1
|
|
continue
|
|
else:
|
|
raise GenerationException('Conflict detected in force_crossed and force_noncrossed')
|
|
if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in combine_set):
|
|
continue
|
|
else:
|
|
skip_back = False
|
|
if any(edge in force_crossed for edge in forward_combine):
|
|
if not any(edge in force_noncrossed for edge in forward_combine):
|
|
world.owcrossededges[player].extend(forward_set)
|
|
count_crossed = count_crossed + 1
|
|
skip_forward = True
|
|
else:
|
|
raise GenerationException('Conflict detected in force_crossed and force_noncrossed')
|
|
if any(edge in force_crossed for edge in back_combine):
|
|
if not any(edge in force_noncrossed for edge in back_combine):
|
|
world.owcrossededges[player].extend(back_set)
|
|
count_crossed = count_crossed + 1
|
|
skip_back = True
|
|
else:
|
|
raise GenerationException('Conflict detected in force_crossed and force_noncrossed')
|
|
if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in forward_combine):
|
|
skip_forward = True
|
|
if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in back_combine):
|
|
skip_back = True
|
|
if not skip_back:
|
|
if limited_crossed > -1:
|
|
crossed_candidates.append(back_set)
|
|
elif random.randint(1, 100) <= undefined_chance:
|
|
world.owcrossededges[player].extend(back_set)
|
|
count_crossed = count_crossed + 1
|
|
if not skip_forward:
|
|
if limited_crossed > -1:
|
|
crossed_candidates.append(forward_set)
|
|
elif random.randint(1, 100) <= undefined_chance:
|
|
world.owcrossededges[player].extend(forward_set)
|
|
count_crossed = count_crossed + 1
|
|
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges"
|
|
|
|
if limited_crossed > -1:
|
|
limit = limited_crossed - count_crossed
|
|
if limit > 1:
|
|
random.shuffle(crossed_candidates)
|
|
for edge_set in crossed_candidates[:limit]:
|
|
world.owcrossededges[player].extend(edge_set)
|
|
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges"
|
|
|
|
for edge in copy.deepcopy(world.owcrossededges[player]):
|
|
if edge in parallel_links_new:
|
|
if parallel_links_new[edge] not in world.owcrossededges[player]:
|
|
world.owcrossededges[player].append(parallel_links_new[edge])
|
|
|
|
# after tile flip and crossed, determine edges that need to flip
|
|
edges_to_swap = [e for e in swapped_edges+world.owcrossededges[player] if (e not in swapped_edges) or (e not in world.owcrossededges[player])]
|
|
|
|
# whirlpool shuffle
|
|
logging.getLogger('').debug('Shuffling whirlpools')
|
|
|
|
if not world.owWhirlpoolShuffle[player]:
|
|
for (_, from_whirlpool, from_region), (_, to_whirlpool, to_region) in default_whirlpool_connections:
|
|
connect_simple(world, from_whirlpool, to_region, player)
|
|
connect_simple(world, to_whirlpool, from_region, player)
|
|
else:
|
|
def connect_whirlpool(from_whirlpool, to_whirlpool):
|
|
(from_owid, from_name, from_region) = from_whirlpool
|
|
(to_owid, to_name, to_region) = to_whirlpool
|
|
connect_simple(world, from_name, to_region, player)
|
|
connect_simple(world, to_name, from_region, player)
|
|
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid
|
|
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid
|
|
connected_whirlpools.append(tuple((from_name, to_name)))
|
|
world.spoiler.set_whirlpool(from_name, to_name, 'both', player)
|
|
|
|
whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ]
|
|
whirlpool_candidates = [[],[]]
|
|
connected_whirlpools = []
|
|
world.owwhirlpools[player] = [None] * 8
|
|
for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections:
|
|
if world.owCrossed[player] == 'polar' and world.owMixed[player] and from_owid == 0x55:
|
|
# connect the 2 DW whirlpools in Polar Mixed
|
|
connect_whirlpool((from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region))
|
|
else:
|
|
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \
|
|
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
|
|
or (world.owCrossed[player] == 'grouped' and ((world.get_region(from_region, player).type == RegionType.LightWorld) == (from_owid not in ow_crossed_tiles))):
|
|
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
|
|
else:
|
|
whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region)))
|
|
|
|
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(to_region, player).type == RegionType.LightWorld)) \
|
|
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
|
|
or (world.owCrossed[player] == 'grouped' and ((world.get_region(to_region, player).type == RegionType.LightWorld) == (to_owid not in ow_crossed_tiles))):
|
|
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
|
|
else:
|
|
whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region)))
|
|
|
|
# shuffle happens here
|
|
if world.customizer:
|
|
custom_whirlpools = world.customizer.get_whirlpools()
|
|
if custom_whirlpools and player in custom_whirlpools:
|
|
custom_whirlpools = custom_whirlpools[player]
|
|
if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0:
|
|
for whirlpools in whirlpool_candidates:
|
|
for whirlname1, whirlname2 in custom_whirlpools['two-way'].items():
|
|
whirl1 = next((w for w in whirlpools if w[1] == whirlname1), None)
|
|
whirl2 = next((w for w in whirlpools if w[1] == whirlname2), None)
|
|
if whirl1 and whirl2:
|
|
whirlpools.remove(whirl1)
|
|
whirlpools.remove(whirl2)
|
|
connect_whirlpool(whirl1, whirl2)
|
|
elif whirl1 != whirl2:
|
|
raise GenerationException('Attempting to connect whirlpools not in same pool: \'%s\' <-> \'%s\'', whirlname1, whirlname2)
|
|
elif any(w for w in connected_whirlpools if (whirlname1 in w) != (whirlname2 in w)):
|
|
raise GenerationException('Attempting to connect whirlpools already connected: \'%s\' <-> \'%s\'', whirlname1, whirlname2)
|
|
for whirlpools in whirlpool_candidates:
|
|
random.shuffle(whirlpools)
|
|
while len(whirlpools):
|
|
if len(whirlpools) % 2 == 1:
|
|
x=0
|
|
connect_whirlpool(whirlpools.pop(), whirlpools.pop())
|
|
|
|
# layout shuffle
|
|
logging.getLogger('').debug('Shuffling overworld layout')
|
|
|
|
if world.owShuffle[player] == 'vanilla':
|
|
# apply outstanding flips
|
|
trimmed_groups = performSwap(trimmed_groups, edges_to_swap)
|
|
assert len(edges_to_swap) == 0, 'Not all edges were flipped successfully: ' + ', '.join(edges_to_swap)
|
|
|
|
# vanilla transitions
|
|
groups = list(trimmed_groups.values())
|
|
for (forward_edge_sets, back_edge_sets) in groups:
|
|
assert len(forward_edge_sets) == len(back_edge_sets)
|
|
for (forward_set, back_set) in zip(forward_edge_sets, back_edge_sets):
|
|
assert len(forward_set) == len(back_set)
|
|
for (forward_edge, back_edge) in zip(forward_set, back_set):
|
|
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
|
|
|
world.owsectors[player] = build_sectors(world, player)
|
|
else:
|
|
if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel':
|
|
for exitname, destname in parallelsimilar_connections:
|
|
connect_two_way(world, exitname, destname, player, connected_edges)
|
|
|
|
#TODO: Remove, just for testing
|
|
for exitname, destname in test_connections:
|
|
connect_two_way(world, exitname, destname, player, connected_edges)
|
|
|
|
# layout shuffle
|
|
groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
|
|
|
|
connect_custom(world, connected_edges, groups, (force_crossed, force_noncrossed), player)
|
|
|
|
tries = 100
|
|
valid_layout = False
|
|
connected_edge_cache = connected_edges.copy()
|
|
groups_cache = copy.deepcopy(groups)
|
|
while not valid_layout and tries > 0:
|
|
def remove_connected(forward_sets, back_sets):
|
|
deleted_edges = []
|
|
def remove_from_sets(sets):
|
|
s = 0
|
|
while s < len(sets):
|
|
if sets[s][0] in connected_edges:
|
|
deleted_edges.extend(sets[s])
|
|
del sets[s]
|
|
continue
|
|
s += 1
|
|
remove_from_sets(forward_sets)
|
|
remove_from_sets(back_sets)
|
|
if len(forward_sets) != len(back_sets):
|
|
x=', '.join(deleted_edges)
|
|
x=0
|
|
assert len(forward_sets) == len(back_sets), "OW edge pool is uneven due to prior connections: " + ', '.join(deleted_edges)
|
|
|
|
def connect_set(forward_set, back_set, connected_edges):
|
|
if forward_set is not None and back_set is not None:
|
|
assert len(forward_set) == len(back_set)
|
|
for (forward_edge, back_edge) in zip(forward_set, back_set):
|
|
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
|
elif forward_set is not None:
|
|
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0])
|
|
elif back_set is not None:
|
|
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0])
|
|
|
|
connected_edges = connected_edge_cache.copy()
|
|
groups = copy.deepcopy(groups_cache)
|
|
groupKeys = list(groups.keys())
|
|
|
|
if world.mode[player] == 'standard':
|
|
subset = groupKeys[2:]
|
|
random.shuffle(subset) # keep first 2 groups (Standard) first
|
|
groupKeys[2:] = subset
|
|
else:
|
|
random.shuffle(groupKeys)
|
|
|
|
for key in groupKeys:
|
|
(mode, wrld, dir, terrain, parallel, count, _) = key
|
|
(forward_edge_sets, back_edge_sets) = groups[key]
|
|
remove_connected(forward_edge_sets, back_edge_sets)
|
|
random.shuffle(forward_edge_sets)
|
|
random.shuffle(back_edge_sets)
|
|
|
|
if wrld is None and len(force_crossed) + len(force_noncrossed) > 0:
|
|
# divide forward/back sets into LW/DW
|
|
forward_lw_sets, forward_dw_sets = [], []
|
|
back_lw_sets, back_dw_sets = [], []
|
|
forward_parallel_lw_sets, forward_parallel_dw_sets = [], []
|
|
back_parallel_lw_sets, back_parallel_dw_sets = [], []
|
|
|
|
def add_edgeset_to_worldsets(edge_set, sets, parallel_sets):
|
|
sets.append(edge_set)
|
|
if parallel == IsParallel.Yes:
|
|
parallel_sets.append([parallel_links_new[e] for e in edge_set])
|
|
for edge_set in forward_edge_sets:
|
|
if world.get_owedge(edge_set[0], player).is_lw(world):
|
|
add_edgeset_to_worldsets(edge_set, forward_lw_sets, forward_parallel_lw_sets)
|
|
else:
|
|
add_edgeset_to_worldsets(edge_set, forward_dw_sets, forward_parallel_dw_sets)
|
|
for edge_set in back_edge_sets:
|
|
if world.get_owedge(edge_set[0], player).is_lw(world):
|
|
add_edgeset_to_worldsets(edge_set, back_lw_sets, back_parallel_lw_sets)
|
|
else:
|
|
add_edgeset_to_worldsets(edge_set, back_dw_sets, back_parallel_dw_sets)
|
|
|
|
crossed_sets = []
|
|
noncrossed_sets = []
|
|
def add_to_crossed_sets(sets, parallel_sets):
|
|
for i in range(0, len(sets)):
|
|
affected_edges = set(sets[i]+(parallel_sets[i] if parallel == IsParallel.Yes else []))
|
|
if sets[i] not in crossed_sets and len(set.intersection(set(force_crossed), affected_edges)) > 0:
|
|
crossed_sets.append(sets[i])
|
|
if sets[i] not in noncrossed_sets and len(set.intersection(set(force_noncrossed), affected_edges)) > 0:
|
|
noncrossed_sets.append(sets[i])
|
|
if sets[i] in crossed_sets and sets[i] in noncrossed_sets:
|
|
raise GenerationException('Conflict in force crossed/non-crossed definition')
|
|
add_to_crossed_sets(forward_lw_sets, forward_parallel_lw_sets)
|
|
add_to_crossed_sets(forward_dw_sets, forward_parallel_dw_sets)
|
|
add_to_crossed_sets(back_lw_sets, back_parallel_lw_sets)
|
|
add_to_crossed_sets(back_dw_sets, back_parallel_dw_sets)
|
|
|
|
# random connect forced crossed/noncrossed
|
|
def connect_forced(forced_sets, is_crossed, opposite_sets=[]):
|
|
c = 0
|
|
while c < len(forced_sets):
|
|
if forced_sets[c] in forward_edge_sets:
|
|
forward_set = forced_sets[c]
|
|
if (forward_set in forward_lw_sets) != is_crossed:
|
|
back_set = next(s for s in back_lw_sets if s in back_edge_sets and s not in opposite_sets)
|
|
else:
|
|
back_set = next(s for s in back_dw_sets if s in back_edge_sets and s not in opposite_sets)
|
|
elif forced_sets[c] in back_edge_sets:
|
|
back_set = forced_sets[c]
|
|
if (back_set in back_lw_sets) != is_crossed:
|
|
forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets and s not in opposite_sets)
|
|
else:
|
|
forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets and s not in opposite_sets)
|
|
else:
|
|
c = c + 1
|
|
continue
|
|
connect_set(forward_set, back_set, connected_edges)
|
|
remove_connected(forward_edge_sets, back_edge_sets)
|
|
c = c + 1
|
|
connect_forced(noncrossed_sets, False, crossed_sets)
|
|
connect_forced(crossed_sets, True)
|
|
|
|
while len(forward_edge_sets) > 0 and len(back_edge_sets) > 0:
|
|
connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges)
|
|
remove_connected(forward_edge_sets, back_edge_sets)
|
|
assert len(connected_edges) == len(default_connections) * 2, connected_edges
|
|
|
|
world.owsectors[player] = build_sectors(world, player)
|
|
valid_layout = validate_layout(world, player)
|
|
|
|
tries -= 1
|
|
assert valid_layout, 'Could not find a valid OW layout'
|
|
|
|
# flute shuffle
|
|
logging.getLogger('').debug('Shuffling flute spots')
|
|
def connect_flutes(flute_destinations):
|
|
for o in range(0, len(flute_destinations)):
|
|
owid = flute_destinations[o]
|
|
regions = flute_data[owid][0]
|
|
if not world.is_tile_swapped(owid, player):
|
|
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player)
|
|
else:
|
|
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
|
|
|
if world.owFluteShuffle[player] == 'vanilla':
|
|
connect_flutes(default_flute_connections)
|
|
else:
|
|
flute_spots = 8
|
|
flute_pool = list(flute_data.keys())
|
|
new_spots = list()
|
|
ignored_regions = set()
|
|
used_flute_regions = []
|
|
forbidden_spots = []
|
|
forbidden_regions = []
|
|
|
|
def addSpot(owid, ignore_proximity, forced):
|
|
if world.owFluteShuffle[player] == 'balanced':
|
|
def getIgnored(regionname, base_owid, owid):
|
|
region = world.get_region(regionname, player)
|
|
for exit in region.exits:
|
|
if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored:
|
|
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid):
|
|
new_ignored.add(exit.connected_region.name)
|
|
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name])
|
|
if regionname in one_way_ledges:
|
|
for ledge_region in one_way_ledges[regionname]:
|
|
if ledge_region not in new_ignored:
|
|
new_ignored.add(ledge_region)
|
|
getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region])
|
|
|
|
if not world.is_tile_swapped(owid, player):
|
|
new_region = flute_data[owid][0][0]
|
|
else:
|
|
new_region = flute_data[owid][0][1]
|
|
|
|
if new_region in ignored_regions and not forced:
|
|
return False
|
|
|
|
new_ignored = {new_region}
|
|
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
|
|
if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
|
return False
|
|
ignored_regions.update(new_ignored)
|
|
if owid in flute_pool:
|
|
flute_pool.remove(owid)
|
|
if ignore_proximity and not forced:
|
|
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
|
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
|
|
new_spots.append(owid)
|
|
else:
|
|
# TODO: Inspect later, seems to happen only with 'random' flute shuffle
|
|
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
|
|
return True
|
|
|
|
if world.customizer:
|
|
custom_spots = world.customizer.get_owflutespots()
|
|
if custom_spots and player in custom_spots:
|
|
if 'force' in custom_spots[player]:
|
|
for id in custom_spots[player]['force']:
|
|
owid = id & 0xBF
|
|
addSpot(owid, True, True)
|
|
flute_spots -= 1
|
|
if not world.is_tile_swapped(owid, player):
|
|
used_flute_regions.append(flute_data[owid][0][0])
|
|
else:
|
|
used_flute_regions.append(flute_data[owid][0][1])
|
|
if 'forbid' in custom_spots[player]:
|
|
for id in custom_spots[player]['forbid']:
|
|
owid = id & 0xBF
|
|
if owid not in new_spots:
|
|
forbidden_spots.append(owid)
|
|
if not world.is_tile_swapped(owid, player):
|
|
forbidden_regions.append(flute_data[owid][0][0])
|
|
else:
|
|
forbidden_regions.append(flute_data[owid][0][1])
|
|
|
|
# determine sectors (isolated groups of regions) to place flute spots
|
|
flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots}
|
|
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
|
|
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
|
|
region_total = sum([c for c,_ in flute_sectors])
|
|
sector_total = len(flute_sectors)
|
|
empty_sector_total = 0
|
|
sector_has_spot = []
|
|
|
|
# determine which sectors still need a flute spot
|
|
for sector in flute_sectors:
|
|
already_has_spot = any(region in sector for region in used_flute_regions)
|
|
sector_has_spot.append(already_has_spot)
|
|
if not already_has_spot:
|
|
empty_sector_total += 1
|
|
if flute_spots < empty_sector_total:
|
|
logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail')
|
|
# pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable
|
|
for i in range(len(flute_sectors)):
|
|
if not sector_has_spot[i]:
|
|
sector_has_spot[i] = True
|
|
empty_sector_total -= 1
|
|
if flute_spots == empty_sector_total:
|
|
break
|
|
|
|
# distribute flute spots for each sector
|
|
for i in range(len(flute_sectors)):
|
|
sector = flute_sectors[i]
|
|
sector_total -= 1
|
|
if not sector_has_spot[i]:
|
|
empty_sector_total -= 1
|
|
spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
|
target_spots = len(new_spots) + spots_to_place
|
|
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
|
|
|
|
if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]):
|
|
addSpot(0x30, True, True) # guarantee desert/mire access
|
|
|
|
random.shuffle(sector[1])
|
|
f = 0
|
|
t = 0
|
|
while len(new_spots) < target_spots:
|
|
if f >= len(sector[1]):
|
|
f = 0
|
|
t += 1
|
|
if t > 5:
|
|
raise GenerationException('Infinite loop detected in flute shuffle')
|
|
owid = flute_regions[sector[1][f]]
|
|
if owid not in new_spots and owid not in forbidden_spots:
|
|
addSpot(owid, t > 0, False)
|
|
f += 1
|
|
|
|
region_total -= sector[0]
|
|
flute_spots -= spots_to_place
|
|
|
|
# connect new flute spots
|
|
new_spots.sort()
|
|
world.owflutespots[player] = new_spots
|
|
connect_flutes(new_spots)
|
|
|
|
# update spoiler
|
|
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
|
|
text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
|
s[0x00], s[0x03], s[0x05],
|
|
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
|
s[0x0a], s[0x0f],
|
|
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
|
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
|
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
|
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
|
s[0x32],s[0x33],s[0x34], s[0x37],
|
|
s[0x30], s[0x35],
|
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
|
world.spoiler.set_map('flute', text_output, new_spots, player)
|
|
|
|
def connect_custom(world, connected_edges, groups, forced, player):
|
|
forced_crossed, forced_noncrossed = forced
|
|
def remove_pair_from_pool(edgename1, edgename2, is_crossed):
|
|
def add_to_unresolved(forward_set, back_set):
|
|
if len(forward_set) > 1:
|
|
if edgename1 in forward_set:
|
|
forward_set.remove(edgename1)
|
|
back_set.remove(edgename2)
|
|
else:
|
|
back_set.remove(edgename1)
|
|
forward_set.remove(edgename2)
|
|
unresolved_similars.append(tuple((forward_set, back_set, is_crossed)))
|
|
for forward_pool, back_pool in groups.values():
|
|
if not len(forward_pool):
|
|
continue
|
|
if len(forward_pool[0]) == 1:
|
|
if [edgename1] in forward_pool:
|
|
if [edgename2] in back_pool:
|
|
forward_pool.remove([edgename1])
|
|
back_pool.remove([edgename2])
|
|
return
|
|
else:
|
|
break
|
|
elif [edgename1] in back_pool:
|
|
if [edgename2] in forward_pool:
|
|
back_pool.remove([edgename1])
|
|
forward_pool.remove([edgename2])
|
|
return
|
|
else:
|
|
break
|
|
else:
|
|
forward_similar = next((x for x in forward_pool if edgename1 in x), None)
|
|
if forward_similar:
|
|
back_similar = next((x for x in back_pool if edgename2 in x), None)
|
|
if back_similar:
|
|
forward_pool.remove(forward_similar)
|
|
back_pool.remove(back_similar)
|
|
add_to_unresolved(forward_similar, back_similar)
|
|
return
|
|
else:
|
|
break
|
|
else:
|
|
back_similar = next((x for x in back_pool if edgename1 in x), None)
|
|
if back_similar:
|
|
forward_similar = next((x for x in forward_pool if edgename2 in x), None)
|
|
if forward_similar:
|
|
back_pool.remove(forward_similar)
|
|
forward_pool.remove(back_similar)
|
|
add_to_unresolved(forward_similar, back_similar)
|
|
return
|
|
else:
|
|
break
|
|
for pair in unresolved_similars:
|
|
forward_set, back_set, _ = pair
|
|
if edgename1 in forward_set:
|
|
if edgename2 in back_set:
|
|
unresolved_similars.remove(pair)
|
|
add_to_unresolved(forward_set, back_set)
|
|
return
|
|
else:
|
|
break
|
|
else:
|
|
if edgename1 in back_set:
|
|
if edgename2 in forward_set:
|
|
unresolved_similars.remove(pair)
|
|
add_to_unresolved(forward_set, back_set)
|
|
return
|
|
else:
|
|
break
|
|
raise GenerationException('Could not find both OW edges in same pool: \'%s\' <-> \'%s\'', edgename1, edgename2)
|
|
|
|
if world.customizer:
|
|
custom_edges = world.customizer.get_owedges()
|
|
if custom_edges and player in custom_edges:
|
|
custom_edges = custom_edges[player]
|
|
if 'two-way' in custom_edges:
|
|
unresolved_similars = []
|
|
def validate_crossed_allowed(edge1, edge2, is_crossed):
|
|
return not ((not is_crossed and (edge1 in forced_crossed or edge2 in forced_crossed))
|
|
or (is_crossed and (edge1 in forced_noncrossed or edge2 in forced_noncrossed)))
|
|
for edgename1, edgename2 in custom_edges['two-way'].items():
|
|
edge1 = world.get_owedge(edgename1, player)
|
|
edge2 = world.get_owedge(edgename2, player)
|
|
is_crossed = edge1.is_lw(world) != edge2.is_lw(world)
|
|
if not validate_crossed_allowed(edge1.name, edge2.name, is_crossed):
|
|
if edgename2[-1] == '*':
|
|
edge2 = world.get_owedge(edge2.name + '*', player)
|
|
is_crossed = not is_crossed
|
|
else:
|
|
raise GenerationException('Violation of force crossed rules: \'%s\' <-> \'%s\'', edgename1, edgename2)
|
|
if edge1.name not in connected_edges and edge2.name not in connected_edges:
|
|
# attempt connection
|
|
remove_pair_from_pool(edge1.name, edge2.name, is_crossed)
|
|
connect_two_way(world, edge1.name, edge2.name, player, connected_edges)
|
|
# resolve parallel
|
|
if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new:
|
|
parallel_forward_edge = parallel_links_new[edge1.name]
|
|
parallel_back_edge = parallel_links_new[edge2.name]
|
|
if validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed):
|
|
remove_pair_from_pool(parallel_forward_edge, parallel_back_edge, is_crossed)
|
|
else:
|
|
raise GenerationException('Violation of force crossed rules on parallel connection: \'%s\' <-> \'%s\'', edgename1, edgename2)
|
|
elif not edge1.dest or not edge2.dest or edge1.dest.name != edge2.name or edge2.dest.name != edge1.name:
|
|
raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2)
|
|
# connect leftover similars
|
|
for forward_pool, back_pool, is_crossed in unresolved_similars:
|
|
for (forward_edge, back_edge) in zip(forward_pool, back_pool):
|
|
if validate_crossed_allowed(forward_edge, back_edge, is_crossed):
|
|
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
|
else:
|
|
raise GenerationException('Violation of force crossed rules on unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge)
|
|
if world.owShuffle[player] == 'parallel' and forward_edge in parallel_links_new:
|
|
parallel_forward_edge = parallel_links_new[forward_edge]
|
|
parallel_back_edge = parallel_links_new[back_edge]
|
|
if not validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed):
|
|
raise GenerationException('Violation of force crossed rules on parallel unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge)
|
|
|
|
def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
|
|
edge1 = world.get_entrance(edgename1, player)
|
|
edge2 = world.get_entrance(edgename2, player)
|
|
x = world.get_owedge(edgename1, player)
|
|
y = world.get_owedge(edgename2, player)
|
|
|
|
if connected_edges is not None:
|
|
if edgename1 in connected_edges or edgename2 in connected_edges:
|
|
if (x.dest and x.dest.name == edgename2) and (y.dest and y.dest.name == edgename1):
|
|
return
|
|
else:
|
|
raise Exception('Edges \'%s\' and \'%s\' already connected elsewhere', edgename1, edgename2)
|
|
|
|
# if these were already connected somewhere, remove the backreference
|
|
if edge1.connected_region is not None:
|
|
edge1.connected_region.entrances.remove(edge1)
|
|
if edge2.connected_region is not None:
|
|
edge2.connected_region.entrances.remove(edge2)
|
|
|
|
edge1.connect(edge2.parent_region)
|
|
edge2.connect(edge1.parent_region)
|
|
x.dest = y
|
|
y.dest = x
|
|
|
|
if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none':
|
|
world.spoiler.set_overworld(edgename2, edgename1, 'both', player)
|
|
|
|
if connected_edges is not None:
|
|
connected_edges.append(edgename1)
|
|
connected_edges.append(edgename2)
|
|
|
|
# connecting parallel connections
|
|
if world.owShuffle[player] in ['vanilla', 'parallel']:
|
|
if edgename1 in parallel_links_new:
|
|
try:
|
|
parallel_forward_edge = parallel_links_new[edgename1]
|
|
parallel_back_edge = parallel_links_new[edgename2]
|
|
if not (parallel_forward_edge in connected_edges) and not (parallel_back_edge in connected_edges):
|
|
connect_two_way(world, parallel_forward_edge, parallel_back_edge, player, connected_edges)
|
|
except KeyError:
|
|
raise KeyError('No parallel edge for edge %s' % edgename2)
|
|
|
|
def determine_forced_flips(world, tile_ow_groups, do_grouped, player):
|
|
undefined_chance = 50
|
|
allow_flip_sanc = do_grouped
|
|
flipped_groups = list()
|
|
nonflipped_groups = list()
|
|
merged_owids = list()
|
|
if world.customizer:
|
|
if do_grouped:
|
|
custom_flips = world.customizer.get_owcrossed()
|
|
else:
|
|
custom_flips = world.customizer.get_owtileflips()
|
|
if custom_flips and player in custom_flips:
|
|
custom_flips = custom_flips[player]
|
|
forced_flips = list()
|
|
forced_nonflips = list()
|
|
if 'undefined_chance' in custom_flips:
|
|
undefined_chance = custom_flips['undefined_chance']
|
|
if not do_grouped and 'always_allow_flipped_sanctuary' in custom_flips:
|
|
allow_flip_sanc = custom_flips['always_allow_flipped_sanctuary'] in [1, True, "True", "true"]
|
|
if 'force_flip' in custom_flips:
|
|
forced_flips = custom_flips['force_flip']
|
|
if 'force_no_flip' in custom_flips:
|
|
forced_nonflips = custom_flips['force_no_flip']
|
|
if 'force_together' in custom_flips:
|
|
merged_owids = list(custom_flips['force_together'].values())
|
|
|
|
for group in tile_ow_groups:
|
|
if any(owid in group for owid in forced_nonflips):
|
|
nonflipped_groups.append(group)
|
|
if any(owid in group for owid in forced_flips):
|
|
flipped_groups.append(group)
|
|
|
|
if undefined_chance == 0:
|
|
nonflipped_groups.extend([g for g in tile_ow_groups if g not in flipped_groups + nonflipped_groups])
|
|
|
|
# ensure any customized connections don't end up crossworld
|
|
if world.owCrossed[player] == 'none':
|
|
def should_merge_group(s1_owid, s2_owid):
|
|
flip_together = (s1_owid & 0x40) == (s2_owid & 0x40)
|
|
s1_nonflipped = any(g for g in nonflipped_groups if s1_owid in g)
|
|
s1_flipped = any(g for g in flipped_groups if s1_owid in g)
|
|
if s1_nonflipped or s1_flipped:
|
|
group = next(g for g in tile_ow_groups if s2_owid in g)
|
|
if s1_nonflipped == flip_together:
|
|
nonflipped_groups.append(group)
|
|
else:
|
|
flipped_groups.append(group)
|
|
else:
|
|
s2_nonflipped = any(g for g in nonflipped_groups if s2_owid in g)
|
|
s2_flipped = any(g for g in flipped_groups if s2_owid in g)
|
|
if s2_nonflipped or s2_flipped:
|
|
group = next(g for g in tile_ow_groups if s1_owid in g)
|
|
if s2_nonflipped == flip_together:
|
|
nonflipped_groups.append(group)
|
|
else:
|
|
flipped_groups.append(group)
|
|
else:
|
|
s1_group = next(g for g in tile_ow_groups if s1_owid in g)
|
|
s2_group = next(g for g in tile_ow_groups if s2_owid in g)
|
|
if not flip_together:
|
|
if random.randint(0, 1) > 0:
|
|
nonflipped_groups.append(s1_group)
|
|
flipped_groups.append(s2_group)
|
|
else:
|
|
flipped_groups.append(s1_group)
|
|
nonflipped_groups.append(s2_group)
|
|
else:
|
|
return True
|
|
return False
|
|
if world.owWhirlpoolShuffle[player]:
|
|
custom_whirlpools = world.customizer.get_whirlpools()
|
|
if custom_whirlpools and player in custom_whirlpools:
|
|
custom_whirlpools = custom_whirlpools[player]
|
|
if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0:
|
|
custom_whirlpools = custom_whirlpools['two-way']
|
|
whirlpool_map = {name:owid for wc in default_whirlpool_connections for (owid, name, _) in wc}
|
|
for whirl1, whirl2 in custom_whirlpools.items():
|
|
if [whirlpool_map[whirl1], whirlpool_map[whirl2]] not in merged_owids and should_merge_group(whirlpool_map[whirl1], whirlpool_map[whirl2]):
|
|
merged_owids.append([whirlpool_map[whirl1], whirlpool_map[whirl2]])
|
|
if world.owShuffle[player] != 'vanilla':
|
|
custom_edges = world.customizer.get_owedges()
|
|
if custom_edges and player in custom_edges:
|
|
custom_edges = custom_edges[player]
|
|
if 'two-way' in custom_edges and len(custom_edges['two-way']) > 0:
|
|
custom_edges = custom_edges['two-way']
|
|
for edgename1, edgename2 in custom_edges.items():
|
|
if edgename1[-1] != '*' and edgename2[-1] != '*':
|
|
edge1 = world.get_owedge(edgename1, player)
|
|
edge2 = world.get_owedge(edgename2, player)
|
|
if [edge1.owIndex, edge2.owIndex] not in merged_owids and should_merge_group(edge1.owIndex, edge2.owIndex):
|
|
merged_owids.append([edge1.owIndex, edge2.owIndex])
|
|
# Check if there are any groups that appear in both sets
|
|
if any(group in flipped_groups for group in nonflipped_groups):
|
|
raise GenerationException('Conflict found when flipping tiles')
|
|
return flipped_groups, nonflipped_groups, undefined_chance, allow_flip_sanc, merged_owids
|
|
|
|
def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player):
|
|
(flipped_groups, nonflipped_groups, undefined_chance) = forced_flips
|
|
swapped_edges = list()
|
|
group_parity = {}
|
|
for group_data in groups:
|
|
group = group_data[0]
|
|
parity = [0, 0, 0, 0, 0, 0]
|
|
# 0: vertical
|
|
if 0x00 in group:
|
|
parity[0] += 1
|
|
if 0x0f in group:
|
|
parity[0] += 1
|
|
if 0x80 in group:
|
|
parity[0] -= 1
|
|
if 0x81 in group:
|
|
parity[0] -= 1
|
|
# 1: horizontal land single
|
|
if 0x1a in group:
|
|
parity[1] -= 1
|
|
if 0x1b in group:
|
|
parity[1] += 1
|
|
if 0x28 in group:
|
|
parity[1] -= 1
|
|
if 0x29 in group:
|
|
parity[1] += 1
|
|
# 2: horizontal land double
|
|
if 0x28 in group:
|
|
parity[2] += 1
|
|
if 0x29 in group:
|
|
parity[2] -= 1
|
|
if 0x30 in group:
|
|
parity[2] -= 1
|
|
if 0x3a in group:
|
|
parity[2] += 1
|
|
# 3: horizontal water
|
|
if 0x2d in group:
|
|
parity[3] += 1
|
|
if 0x80 in group:
|
|
parity[3] -= 1
|
|
# 4: whirlpool
|
|
if 0x0f in group:
|
|
parity[4] += 1
|
|
if 0x12 in group:
|
|
parity[4] += 1
|
|
if 0x33 in group:
|
|
parity[4] += 1
|
|
if 0x35 in group:
|
|
parity[4] += 1
|
|
# 5: dropdown exit
|
|
for id in [0x00, 0x02, 0x13, 0x15, 0x18, 0x22]:
|
|
if id in group:
|
|
parity[5] += 1
|
|
if 0x1b in group and world.mode[player] != 'standard':
|
|
parity[5] += 1
|
|
if 0x1b in group and world.shuffle_ganon[player]:
|
|
parity[5] -= 1
|
|
group_parity[group[0]] = parity
|
|
|
|
attempts = 1
|
|
if 0 < undefined_chance < 100:
|
|
# do roughly 1000 attempts at a full list
|
|
attempts = len(groups) - len(nonflipped_groups)
|
|
attempts = (attempts ** 1.9) + (attempts * 10) + 1
|
|
while True:
|
|
if attempts == 0: # expected to only occur with custom flips
|
|
raise GenerationException('Could not find valid tile flips')
|
|
|
|
# tile shuffle happens here
|
|
removed = []
|
|
for group in groups:
|
|
if group[0] in nonflipped_groups:
|
|
removed.append(group)
|
|
else:
|
|
if group[0] in flipped_groups or undefined_chance >= 100:
|
|
continue
|
|
if undefined_chance == 0 or random.randint(1, 100) > undefined_chance:
|
|
removed.append(group)
|
|
|
|
# save shuffled tiles to list
|
|
new_results = [[],[],[]]
|
|
for group in groups:
|
|
if group not in removed:
|
|
(owids, lw_regions, dw_regions) = group
|
|
(exist_owids, exist_lw_regions, exist_dw_regions) = new_results
|
|
exist_owids.extend(owids)
|
|
exist_lw_regions.extend(lw_regions)
|
|
exist_dw_regions.extend(dw_regions)
|
|
|
|
parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)]
|
|
if not world.owKeepSimilar[player]:
|
|
parity[1] += 2*parity[2]
|
|
parity[2] = 0
|
|
if world.owTerrain[player]:
|
|
parity[1] += parity[3]
|
|
parity[3] = 0
|
|
parity[4] %= 2 # actual parity
|
|
if (world.owCrossed[player] == 'none' or do_grouped) and parity[:5] != [0, 0, 0, 0, 0]:
|
|
attempts -= 1
|
|
continue
|
|
# ensure sanc can be placed in LW in certain modes
|
|
if not do_grouped and world.shuffle[player] in ['simple', 'restricted', 'full', 'district'] and not world.is_dark_chapel_start(player) and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
|
|
free_dw_drops = parity[5] + (1 if world.shuffle_ganon[player] else 0)
|
|
free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon[player] else 0)
|
|
if free_dw_drops == free_drops:
|
|
attempts -= 1
|
|
continue
|
|
break
|
|
|
|
(exist_owids, exist_lw_regions, exist_dw_regions) = result_list
|
|
exist_owids.extend(new_results[0])
|
|
exist_lw_regions.extend(new_results[1])
|
|
exist_dw_regions.extend(new_results[2])
|
|
|
|
# replace LW edges with DW
|
|
if world.owCrossed[player] == 'none' or do_grouped:
|
|
# in polar, the actual edge connections remain vanilla
|
|
def getSwappedEdges(world, lst, player):
|
|
for regionname in lst:
|
|
region = world.get_region(regionname, player)
|
|
for exit in region.exits:
|
|
if exit.spot_type == 'OWEdge':
|
|
swapped_edges.append(exit.name)
|
|
|
|
getSwappedEdges(world, result_list[1], player)
|
|
getSwappedEdges(world, result_list[2], player)
|
|
|
|
return swapped_edges
|
|
|
|
def define_tile_groups(world, do_grouped, player):
|
|
groups = [[i, i + 0x40] for i in range(0x40)]
|
|
|
|
def get_group(id):
|
|
for group in groups:
|
|
if id in group:
|
|
return group
|
|
|
|
def merge_groups(tile_links):
|
|
for link in tile_links:
|
|
merged_group = []
|
|
for id in link:
|
|
if id not in merged_group:
|
|
group = get_group(id)
|
|
groups.remove(group)
|
|
merged_group += group
|
|
groups.append(merged_group)
|
|
|
|
def can_shuffle_group(group):
|
|
# escape sequence should stay normal in standard
|
|
if world.mode[player] == 'standard' and (0x1b in group or 0x2b in group or 0x2c in group):
|
|
return False
|
|
|
|
# sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen
|
|
if 0x13 in group and not allow_flip_sanc and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \
|
|
and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] not in ['partitioned', 'crossed'] \
|
|
or world.intensity[player] < 3)) or (world.shuffle[player] in ['lite', 'lean'] and world.is_dark_chapel_start(player))):
|
|
return False
|
|
|
|
return True
|
|
|
|
for i in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]:
|
|
groups.remove(get_group(i + 1))
|
|
groups.remove(get_group(i + 8))
|
|
groups.remove(get_group(i + 9))
|
|
groups.append([0x80])
|
|
groups.append([0x81])
|
|
|
|
# hyrule castle and sanctuary connector
|
|
if world.shuffle[player] in ['vanilla', 'district'] or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']):
|
|
merge_groups([[0x13, 0x14, 0x1b]])
|
|
|
|
# sanctuary and grave connector
|
|
if world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'district']:
|
|
merge_groups([[0x13, 0x14]])
|
|
|
|
# cross-screen connector
|
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'district']:
|
|
merge_groups([[0x03, 0x0a], [0x28, 0x29]])
|
|
|
|
# turtle rock connector
|
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']:
|
|
merge_groups([[0x05, 0x07]])
|
|
|
|
# all non-parallel screens
|
|
if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
|
|
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
|
|
|
|
# special case: non-parallel keep similar
|
|
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
|
merge_groups([[0x28, 0x29]])
|
|
|
|
# whirlpool screens
|
|
if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
|
merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]])
|
|
|
|
# customizer adjustments
|
|
flipped_groups, nonflipped_groups, undefined_chance, allow_flip_sanc, merged_owids = determine_forced_flips(world, groups, do_grouped, player)
|
|
for owids in merged_owids:
|
|
merge_groups([owids])
|
|
|
|
tile_groups = []
|
|
for group in groups:
|
|
if can_shuffle_group(group):
|
|
lw_regions = []
|
|
dw_regions = []
|
|
for id in group:
|
|
(lw_regions if id < 0x40 or id >= 0x80 else dw_regions).extend(OWTileRegions.inverse[id])
|
|
tile_groups.append((group, lw_regions, dw_regions))
|
|
|
|
random.shuffle(tile_groups)
|
|
return tile_groups, flipped_groups, nonflipped_groups, undefined_chance, allow_flip_sanc
|
|
|
|
def remove_reserved(world, groupedlist, connected_edges, player):
|
|
new_grouping = {}
|
|
for group in groupedlist.keys():
|
|
new_grouping[group] = ([], [])
|
|
|
|
for group in groupedlist.keys():
|
|
(forward_edges, back_edges) = groupedlist[group]
|
|
|
|
# remove edges already connected (thru plando and other forced connections)
|
|
for edge in connected_edges:
|
|
forward_edges = list(list(filter((edge).__ne__, i)) for i in forward_edges)
|
|
back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges)
|
|
|
|
forward_edges = list(filter(([]).__ne__, forward_edges))
|
|
back_edges = list(filter(([]).__ne__, back_edges))
|
|
|
|
(exist_forward_edges, exist_back_edges) = new_grouping[group]
|
|
exist_forward_edges.extend(forward_edges)
|
|
exist_back_edges.extend(back_edges)
|
|
if len(exist_forward_edges) > 0:
|
|
new_grouping[group] = (exist_forward_edges, exist_back_edges)
|
|
|
|
return new_grouping
|
|
|
|
def reorganize_groups(world, groups, player):
|
|
def get_group_key(group):
|
|
#(std, region, axis, terrain, parallel, count) = group
|
|
new_group = list(group)
|
|
if world.mode[player] != "standard":
|
|
new_group[0] = None
|
|
if world.owTerrain[player]:
|
|
new_group[3] = None
|
|
if world.owShuffle[player] != 'parallel':
|
|
new_group[4] = None
|
|
if not world.owKeepSimilar[player]:
|
|
new_group[5] = None
|
|
return tuple(new_group)
|
|
|
|
# predefined shuffle groups get reorganized here
|
|
# this restructures the candidate pool based on the chosen settings
|
|
for grouping in (groups,):
|
|
new_grouping = {}
|
|
|
|
for group in grouping.keys():
|
|
new_grouping[get_group_key(group)] = ([], [])
|
|
|
|
for group in grouping.keys():
|
|
new_group = get_group_key(group)
|
|
(forward_edges, back_edges) = grouping[group]
|
|
if not world.owKeepSimilar[player]:
|
|
forward_edges = [[i] for l in forward_edges for i in l]
|
|
back_edges = [[i] for l in back_edges for i in l]
|
|
(exist_forward_edges, exist_back_edges) = new_grouping[new_group]
|
|
exist_forward_edges.extend(forward_edges)
|
|
exist_back_edges.extend(back_edges)
|
|
new_grouping[new_group] = (exist_forward_edges, exist_back_edges)
|
|
|
|
return new_grouping
|
|
|
|
def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
|
|
groups = defaultdict(lambda: ([],[]))
|
|
limited_crossed = False
|
|
custom_groups = dict()
|
|
if world.customizer:
|
|
custom_crossed = world.customizer.get_owcrossed()
|
|
limited_crossed = custom_crossed and (player in custom_crossed) and ('limit_crossed' in custom_crossed[player])
|
|
limited_crossed = limited_crossed and world.owCrossed[player] == 'unrestricted'
|
|
custom_edge_groups = world.customizer.get_owedges()
|
|
if custom_edge_groups and player in custom_edge_groups:
|
|
custom_edge_groups = custom_edge_groups[player]
|
|
if 'groups' in custom_edge_groups:
|
|
custom_groups = dict(custom_edge_groups['groups'])
|
|
for name, edges in custom_groups.items():
|
|
custom_groups[name] = [world.get_owedge(e, player).name if e[-1] == '*' else e for e in edges]
|
|
for (key, group) in trimmed_groups.items():
|
|
(mode, wrld, dir, terrain, parallel, count, custom) = key
|
|
if mode == OpenStd.Standard:
|
|
groups[key] = group
|
|
else:
|
|
if world.owCrossed[player] == 'unrestricted' and not limited_crossed:
|
|
groups[(mode, None, dir, terrain, parallel, count, custom)][0].extend(group[0])
|
|
groups[(mode, None, dir, terrain, parallel, count, custom)][1].extend(group[1])
|
|
else:
|
|
for i in range(2):
|
|
for edge_set in group[i]:
|
|
new_world = int(wrld)
|
|
if edge_set[0] in edges_to_swap:
|
|
new_world += 1
|
|
groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count, custom)][i].append(edge_set)
|
|
for (key, group) in groups.copy().items():
|
|
(mode, wrld, dir, terrain, parallel, count, custom) = key
|
|
if mode != OpenStd.Standard:
|
|
for group_name, edges in custom_groups.items():
|
|
for i in range(0, 2):
|
|
matches = [s for s in groups[key][i] if any(e in s for e in edges)]
|
|
if len(matches) > 0:
|
|
for m in matches:
|
|
groups[key][i].remove(m)
|
|
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
|
return groups
|
|
|
|
def create_flute_exits(world, player):
|
|
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
|
if not flute_in_pool:
|
|
return
|
|
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
|
if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
|
|
exitname = 'Flute From ' + region.name
|
|
exit = Entrance(region.player, exitname, region)
|
|
exit.spot_type = 'Flute'
|
|
exit.connect(world.get_region('Flute Sky', player))
|
|
region.exits.append(exit)
|
|
|
|
def get_mirror_exit_name(from_region, to_region):
|
|
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
|
if len(mirror_connections[from_region]) == 1:
|
|
return f'Mirror From {from_region}'
|
|
else:
|
|
return f'Mirror To {to_region}'
|
|
return None
|
|
|
|
def get_mirror_edges(world, region, player):
|
|
mirror_exits = list()
|
|
if (world.mode[player] != 'inverted') == (region.type == RegionType.DarkWorld):
|
|
# get mirror edges leaving the region
|
|
if region.name in mirror_connections:
|
|
for dest_region_name in mirror_connections[region.name]:
|
|
mirror_exits.append(tuple([get_mirror_exit_name(region.name, dest_region_name), dest_region_name]))
|
|
else:
|
|
# get mirror edges leading into the region
|
|
owid = OWTileRegions[region.name]
|
|
for other_world_region_name in OWTileRegions.inverse[(owid + 0x40) % 0x80]:
|
|
if other_world_region_name in mirror_connections:
|
|
for dest_region_name in mirror_connections[other_world_region_name]:
|
|
if dest_region_name == region.name:
|
|
mirror_exits.append(tuple([get_mirror_exit_name(other_world_region_name, region.name), region.name]))
|
|
return mirror_exits
|
|
|
|
def create_mirror_exits(world, player):
|
|
mirror_exits = set()
|
|
for region in (r for r in world.regions if r.player == player and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
|
if region.type == (RegionType.DarkWorld if world.mode[player] != 'inverted' else RegionType.LightWorld):
|
|
if region.name in mirror_connections:
|
|
for region_dest_name in mirror_connections[region.name]:
|
|
exitname = get_mirror_exit_name(region.name, region_dest_name)
|
|
|
|
assert exitname not in mirror_exits, f'Mirror Exit with name already exists: {exitname}'
|
|
|
|
exit = Entrance(region.player, exitname, region)
|
|
exit.spot_type = 'Mirror'
|
|
to_region = world.get_region(region_dest_name, player)
|
|
if region.terrain == Terrain.Water or to_region.terrain == Terrain.Water:
|
|
exit.access_rule = lambda state: state.has('Flippers', player) and state.has_Pearl(player) and state.has_Mirror(player)
|
|
else:
|
|
exit.access_rule = lambda state: state.has_Mirror(player)
|
|
exit.connect(to_region)
|
|
region.exits.append(exit)
|
|
|
|
mirror_exits.add(exitname)
|
|
elif region.terrain == Terrain.Land:
|
|
pass
|
|
|
|
def create_dynamic_exits(world, player):
|
|
create_flute_exits(world, player)
|
|
create_mirror_exits(world, player)
|
|
world.initialize_regions()
|
|
|
|
def categorize_world_regions(world, player):
|
|
for type in OWExitTypes:
|
|
for exitname in OWExitTypes[type]:
|
|
world.get_entrance(exitname, player).spot_type = type
|
|
|
|
mark_light_dark_world_regions(world, player)
|
|
|
|
def update_world_regions(world, player):
|
|
if world.owMixed[player]:
|
|
for name in world.owswaps[player][1]:
|
|
world.get_region(name, player).type = RegionType.DarkWorld
|
|
for name in world.owswaps[player][2]:
|
|
world.get_region(name, player).type = RegionType.LightWorld
|
|
|
|
def can_reach_smith(world, player):
|
|
from Items import ItemFactory
|
|
from BaseClasses import CollectionState
|
|
|
|
def explore_region(region_name, region=None):
|
|
nonlocal found
|
|
explored_regions.append(region_name)
|
|
if not found:
|
|
if not region:
|
|
region = world.get_region(region_name, player)
|
|
for exit in region.exits:
|
|
if not found and exit.connected_region is not None:
|
|
if starting_flute and exit.spot_type == 'Flute':
|
|
for flutespot in exit.connected_region.exits:
|
|
if flutespot.connected_region and flutespot.connected_region.name not in explored_regions:
|
|
explore_region(flutespot.connected_region.name, flutespot.connected_region)
|
|
elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state):
|
|
found = True
|
|
return
|
|
elif exit.connected_region.name not in explored_regions and exit.name != "Dig Game To Ledge Drop":
|
|
if (region.type == RegionType.Dungeon and exit.connected_region.name.endswith(' Portal')) \
|
|
or (exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \
|
|
and exit.access_rule(blank_state)):
|
|
explore_region(exit.connected_region.name, exit.connected_region)
|
|
|
|
blank_state = CollectionState(world)
|
|
if world.mode[player] == 'standard':
|
|
blank_state.collect(ItemFactory('Zelda Delivered', player), True)
|
|
if world.logic[player] in ['noglitches', 'minorglitches'] and not world.is_tile_swapped(0x29, player):
|
|
blank_state.collect(ItemFactory('Titans Mitts', player), True)
|
|
blank_state.collect(ItemFactory('Moon Pearl', player), True)
|
|
if not world.bombbag[player]:
|
|
blank_state.collect(ItemFactory('Farmable Bombs', player), True)
|
|
blank_state.collect(ItemFactory('Farmable Rupees', player), True)
|
|
|
|
found = False
|
|
explored_regions = list()
|
|
starting_flute = any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, world.precollected_items))
|
|
if not world.is_bombshop_start(player):
|
|
start_region = 'Links House'
|
|
else:
|
|
start_region = 'Big Bomb Shop'
|
|
explore_region(start_region)
|
|
if not found:
|
|
if not world.is_dark_chapel_start(player):
|
|
if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla':
|
|
sanc_mirror = world.get_entrance('Sanctuary Mirror Route', player)
|
|
explore_region(sanc_mirror.connected_region.name, sanc_mirror.connected_region)
|
|
else:
|
|
explore_region('Sanctuary')
|
|
else:
|
|
explore_region('Dark Sanctuary Hint')
|
|
return found
|
|
|
|
def build_sectors(world, player):
|
|
from Main import copy_world_premature
|
|
from OWEdges import OWTileRegions
|
|
|
|
# perform accessibility check on duplicate world
|
|
for p in range(1, world.players + 1):
|
|
world.key_logic[p] = {}
|
|
base_world = copy_world_premature(world, player)
|
|
|
|
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
|
regions = list(OWTileRegions.copy().keys())
|
|
sectors = list()
|
|
while(len(regions) > 0):
|
|
explored_regions = build_accessible_region_list(base_world, regions[0], player, False, False, False, False)
|
|
regions = [r for r in regions if r not in explored_regions]
|
|
unique_regions = [_ for i in range(len(sectors)) for _ in sectors[i]]
|
|
if (any(r in unique_regions for r in explored_regions)):
|
|
for s in range(len(sectors)):
|
|
if (any(r in sectors[s] for r in explored_regions)):
|
|
sectors[s] = list(dict.fromkeys(list(sectors[s]) + list(explored_regions)))
|
|
break
|
|
else:
|
|
sectors.append(explored_regions)
|
|
|
|
# remove water regions if Flippers not in starting inventory
|
|
if not any(map(lambda i: i.name == 'Flippers', world.precollected_items)):
|
|
for s in range(len(sectors)):
|
|
terrains = list()
|
|
for regionname in sectors[s]:
|
|
region = world.get_region(regionname, player)
|
|
if region.terrain == Terrain.Land:
|
|
terrains.append(regionname)
|
|
sectors[s] = terrains
|
|
|
|
# within each group, split into contiguous regions accessible only with starting inventory
|
|
for s in range(len(sectors)):
|
|
regions = list(sectors[s]).copy()
|
|
sectors2 = list()
|
|
while(len(regions) > 0):
|
|
explored_regions = build_accessible_region_list(base_world, regions[0], player, False, False, True, False)
|
|
regions = [r for r in regions if r not in explored_regions]
|
|
unique_regions = [_ for i in range(len(sectors2)) for _ in sectors2[i]]
|
|
if (any(r in unique_regions for r in explored_regions)):
|
|
for s2 in range(len(sectors2)):
|
|
if (any(r in sectors2[s2] for r in explored_regions)):
|
|
sectors2[s2] = list(dict.fromkeys(sectors2[s2] + explored_regions))
|
|
break
|
|
else:
|
|
sectors2.append(explored_regions)
|
|
sectors[s] = sectors2
|
|
|
|
return sectors
|
|
|
|
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges=False, restrictive_follower=False):
|
|
from BaseClasses import CollectionState
|
|
from Main import copy_world_premature
|
|
from Items import ItemFactory
|
|
from Utils import stack_size3a
|
|
|
|
def explore_region(region_name, region=None):
|
|
if stack_size3a() > 500:
|
|
raise GenerationException(f'Infinite loop detected for "{start_region}" located at \'build_accessible_region_list\'')
|
|
|
|
explored_regions.append(region_name)
|
|
if not region:
|
|
region = base_world.get_region(region_name, player)
|
|
for exit in region.exits:
|
|
if exit.connected_region is not None:
|
|
if starting_flute and exit.spot_type == 'Flute':
|
|
fluteregion = exit.connected_region
|
|
for flutespot in fluteregion.exits:
|
|
if flutespot.connected_region and flutespot.connected_region.name not in explored_regions:
|
|
explore_region(flutespot.connected_region.name, flutespot.connected_region)
|
|
elif exit.connected_region.name not in explored_regions \
|
|
and (exit.connected_region.type == region.type or exit.name in OWExitTypes['OWEdge']
|
|
or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \
|
|
and (not region_rules or exit.access_rule(blank_state)) \
|
|
and (not restrictive_follower or exit.spot_type != 'OWG') \
|
|
and (not ignore_ledges or not (exit.spot_type == 'OWG' or exit.name in OWExitTypes['Ledge'])):
|
|
explore_region(exit.connected_region.name, exit.connected_region)
|
|
|
|
if build_copy_world:
|
|
for p in range(1, world.players + 1):
|
|
world.key_logic[p] = {}
|
|
base_world = copy_world_premature(world, player)
|
|
base_world.override_bomb_check = True
|
|
else:
|
|
base_world = world
|
|
|
|
connect_simple(base_world, 'Links House S&Q', start_region, player)
|
|
blank_state = CollectionState(base_world)
|
|
if base_world.mode[player] == 'standard':
|
|
blank_state.collect(ItemFactory('Zelda Delivered', player), True)
|
|
explored_regions = list()
|
|
starting_flute = any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, base_world.precollected_items))
|
|
explore_region(start_region)
|
|
|
|
return explored_regions
|
|
|
|
def validate_layout(world, player):
|
|
if world.accessibility[player] == 'none':
|
|
return True
|
|
|
|
entrance_connectors = {
|
|
'East Death Mountain (Bottom)': ['East Death Mountain (Top East)'],
|
|
'Kakariko Suburb Area': ['Maze Race Ledge'],
|
|
'Maze Race Ledge': ['Kakariko Suburb Area'],
|
|
'Desert Area': ['Desert Ledge', 'Desert Mouth'],
|
|
'East Dark Death Mountain (Top)': ['Dark Death Mountain Floating Island'],
|
|
'East Dark Death Mountain (Bottom)': ['East Dark Death Mountain (Top)'],
|
|
'Turtle Rock Area': ['Dark Death Mountain Ledge',
|
|
'Dark Death Mountain Isolated Ledge'],
|
|
'Dark Death Mountain Ledge': ['Turtle Rock Area'],
|
|
'Dark Death Mountain Isolated Ledge': ['Turtle Rock Area'],
|
|
'Mountain Pass Entry': ['West Death Mountain (Bottom)'],
|
|
'Mountain Pass Ledge': ['West Death Mountain (Bottom)'],
|
|
'West Death Mountain (Bottom)': ['Mountain Pass Ledge'],
|
|
'Bumper Cave Entry': ['Bumper Cave Ledge']
|
|
}
|
|
sane_connectors = {
|
|
# guaranteed dungeon access
|
|
'Skull Woods Forest': ['Skull Woods Forest (West)'],
|
|
'Skull Woods Forest (West)': ['Skull Woods Forest'],
|
|
# guaranteed dropdown access
|
|
'Graveyard Area': ['Sanctuary Area'],
|
|
'Pyramid Area': ['Pyramid Exit Ledge']
|
|
}
|
|
|
|
from Main import copy_world_premature
|
|
from Utils import stack_size3a
|
|
# TODO: Find a better source for the below lists, original sourced was deprecated
|
|
from source.overworld.EntranceData import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
|
|
|
|
dungeon_entrances = list(zip(*default_dungeon_connections + [('Ganons Tower', '')]))[0]
|
|
connector_entrances = list(zip(*default_connector_connections))[0]
|
|
item_entrances = list(zip(*default_item_connections))[0]
|
|
shop_entrances = list(zip(*default_shop_connections))[0]
|
|
drop_entrances = list(zip(*default_drop_connections + default_dropexit_connections))[0]
|
|
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
|
|
|
def explore_region(region_name, region=None):
|
|
if stack_size3a() > 500:
|
|
raise GenerationException(f'Infinite loop detected for "{region_name}" located at \'validate_layout\'')
|
|
|
|
explored_regions.append(region_name)
|
|
if not region:
|
|
region = base_world.get_region(region_name, player)
|
|
for exit in region.exits:
|
|
if exit.connected_region is not None and exit.connected_region.name not in explored_regions \
|
|
and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
|
|
explore_region(exit.connected_region.name, exit.connected_region)
|
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple'] \
|
|
and region_name in entrance_connectors:
|
|
for dest_region in entrance_connectors[region_name]:
|
|
if dest_region not in explored_regions:
|
|
explore_region(dest_region)
|
|
if world.shuffle[player] not in ['district', 'insanity'] and region_name in sane_connectors:
|
|
for dest_region in sane_connectors[region_name]:
|
|
if dest_region not in explored_regions:
|
|
explore_region(dest_region)
|
|
|
|
for p in range(1, world.players + 1):
|
|
world.key_logic[p] = {}
|
|
base_world = copy_world_premature(world, player)
|
|
explored_regions = list()
|
|
|
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
|
if not world.is_bombshop_start(player):
|
|
start_region = 'Links House Area'
|
|
else:
|
|
start_region = 'Big Bomb Shop Area'
|
|
explore_region(start_region)
|
|
|
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean'] and world.is_dark_chapel_start(player):
|
|
start_region = 'Dark Chapel Area'
|
|
explore_region(start_region)
|
|
|
|
if flute_in_pool:
|
|
if not world.is_tile_swapped(0x30, player):
|
|
start_region = 'Desert Teleporter Ledge'
|
|
else:
|
|
start_region = 'Mire Teleporter Ledge'
|
|
explore_region(start_region)
|
|
|
|
if not world.is_tile_swapped(0x1b, player):
|
|
start_region = 'Pyramid Area'
|
|
else:
|
|
start_region = 'Hyrule Castle Ledge'
|
|
explore_region(start_region)
|
|
|
|
unreachable_regions = OrderedDict()
|
|
unreachable_count = -1
|
|
while unreachable_count != len(unreachable_regions):
|
|
# find unreachable regions
|
|
unreachable_regions = {}
|
|
for region_name in list(OWTileRegions.copy().keys()):
|
|
if region_name not in explored_regions and region_name not in isolated_regions:
|
|
region = base_world.get_region(region_name, player)
|
|
unreachable_regions[region_name] = region
|
|
|
|
# loop thru unreachable regions to check if some can be excluded
|
|
unreachable_count = len(unreachable_regions)
|
|
for region_name in reversed(unreachable_regions):
|
|
# check if can be accessed flute
|
|
if flute_in_pool and unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
|
|
owid = OWTileRegions[region_name]
|
|
if owid < 0x80 and owid % 0x40 in flute_data and region_name in flute_data[owid % 0x40][0]:
|
|
if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections:
|
|
unreachable_regions.pop(region_name)
|
|
explore_region(region_name)
|
|
break
|
|
# check if entrances in region could be used to access region
|
|
if world.shuffle[player] != 'vanilla':
|
|
# TODO: For District ER, we need to check if there is a dropdown or connector that is able to connect
|
|
for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']:
|
|
if (entrance.name == 'Links House' and ((not world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \
|
|
or (entrance.name == 'Big Bomb Shop' and ((world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \
|
|
or (entrance.name == 'Ganons Tower' and (not world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \
|
|
or (entrance.name == 'Agahnims Tower' and (world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \
|
|
or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['district', 'insanity']) \
|
|
or (entrance.name == 'Tavern North' and not world.shuffletavern[player]):
|
|
continue # these are fixed entrances and cannot be used for gaining access to region
|
|
if entrance.name not in drop_entrances \
|
|
and ((entrance.name in dungeon_entrances and world.shuffle[player] not in ['dungeonssimple', 'simple', 'restricted']) \
|
|
or (entrance.name in connector_entrances and world.shuffle[player] not in ['dungeonssimple', 'dungeonsfull', 'simple']) \
|
|
or (entrance.name in item_entrances + (tuple() if world.shopsanity[player] else shop_entrances) and world.shuffle[player] not in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])):
|
|
unreachable_regions.pop(region_name)
|
|
explore_region(region_name)
|
|
break
|
|
if unreachable_count != len(unreachable_regions):
|
|
break
|
|
|
|
if not flute_in_pool:
|
|
unreachable_regions.pop('Desert Teleporter Ledge')
|
|
unreachable_regions.pop('Mire Teleporter Ledge')
|
|
|
|
if len(unreachable_regions):
|
|
return False
|
|
|
|
return True
|
|
|
|
test_connections = [
|
|
#('Links House ES', 'Octoballoon WS'),
|
|
#('Links House NE', 'Lost Woods Pass SW')
|
|
]
|
|
|
|
# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
|
|
mandatory_connections = [
|
|
('Old Man S&Q', 'Old Man House'),
|
|
|
|
# Intra-tile OW Connections
|
|
('Lost Woods Bush (West)', 'Lost Woods East Area'), #pearl
|
|
('Lost Woods Bush (East)', 'Lost Woods West Area'), #pearl
|
|
('West Death Mountain Drop', 'West Death Mountain (Bottom)'),
|
|
('Spectacle Rock Ledge Drop', 'West Death Mountain (Top)'),
|
|
('Old Man Drop Off', 'Old Man Drop Off'),
|
|
('DM Hammer Bridge (West)', 'East Death Mountain (Top East)'), #hammer
|
|
('DM Hammer Bridge (East)', 'East Death Mountain (Top West)'), #hammer
|
|
('EDM To Spiral Ledge Drop', 'Spiral Cave Ledge'),
|
|
('EDM Ledge Drop', 'East Death Mountain (Bottom)'),
|
|
('Spiral Ledge Drop', 'East Death Mountain (Bottom)'),
|
|
('Fairy Ascension Ledge Drop', 'Fairy Ascension Plateau'),
|
|
('Fairy Ascension Plateau Ledge Drop', 'East Death Mountain (Bottom)'),
|
|
('Fairy Ascension Rocks (Inner)', 'East Death Mountain (Bottom)'), #mitts
|
|
('Fairy Ascension Rocks (Outer)', 'Fairy Ascension Plateau'), #mitts
|
|
('DM Broken Bridge (West)', 'East Death Mountain (Bottom)'), #hookshot
|
|
('DM Broken Bridge (East)', 'East Death Mountain (Bottom Left)'), #hookshot
|
|
('TR Pegs Ledge Entry', 'Death Mountain TR Pegs Ledge'), #mitts
|
|
('TR Pegs Ledge Leave', 'Death Mountain TR Pegs Area'), #mitts
|
|
('Mountain Pass Rock (Outer)', 'Mountain Pass Entry'), #glove
|
|
('Mountain Pass Rock (Inner)', 'Mountain Pass Area'), #glove
|
|
('Mountain Pass Entry Ledge Drop', 'Mountain Pass Area'),
|
|
('Mountain Pass Ledge Drop', 'Mountain Pass Area'),
|
|
('Zora Waterfall Landing', 'Zora Waterfall Area'),
|
|
('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers
|
|
('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers
|
|
('Zora Waterfall Approach', 'Zora Waterfall Entryway'), #flippers
|
|
('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer
|
|
('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer
|
|
('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts
|
|
('Lost Woods Pass Rock (South)', 'Lost Woods Pass Portal Area'), #mitts
|
|
('Bonk Rock Ledge Drop', 'Sanctuary Area'),
|
|
('Graveyard Ledge Drop', 'Graveyard Area'),
|
|
('Kings Grave Rocks (Outer)', 'Kings Grave Area'), #mitts
|
|
('Kings Grave Rocks (Inner)', 'Graveyard Area'), #mitts
|
|
('River Bend Water Drop', 'River Bend Water'), #flippers
|
|
('River Bend East Water Drop', 'River Bend Water'), #flippers
|
|
('River Bend West Pier', 'River Bend Area'),
|
|
('River Bend East Pier', 'River Bend East Bank'),
|
|
('Potion Shop Water Drop', 'Potion Shop Water'), #flippers
|
|
('Potion Shop Northeast Water Drop', 'Potion Shop Water'), #flippers
|
|
('Potion Shop Rock (South)', 'Potion Shop Northeast'), #glove
|
|
('Potion Shop Rock (North)', 'Potion Shop Area'), #glove
|
|
('Zora Approach Water Drop', 'Zora Approach Water'), #flippers
|
|
('Zora Approach Rocks (West)', 'Zora Approach Ledge'), #mitts/boots
|
|
('Zora Approach Rocks (East)', 'Zora Approach Area'), #mitts/boots
|
|
('Zora Approach Bottom Ledge Drop', 'Zora Approach Ledge'),
|
|
('Zora Approach Ledge Drop', 'Zora Approach Area'),
|
|
('Kakariko Southwest Bush (North)', 'Kakariko Southwest'), #pearl
|
|
('Kakariko Southwest Bush (South)', 'Kakariko Village'), #pearl
|
|
('Kakariko Yard Bush (South)', 'Kakariko Bush Yard'), #pearl
|
|
('Kakariko Yard Bush (North)', 'Kakariko Village'), #pearl
|
|
('Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest'), #pearl
|
|
('Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Area'), #pearl
|
|
('Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Courtyard'), #pearl
|
|
('Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Courtyard Northeast'), #pearl
|
|
('Hyrule Castle Main Gate (South)', 'Hyrule Castle Courtyard'), #aga+mirror
|
|
('Hyrule Castle Main Gate (North)', 'Hyrule Castle Area'), #aga+mirror
|
|
('Hyrule Castle Ledge Drop', 'Hyrule Castle Area'),
|
|
('Hyrule Castle Ledge Courtyard Drop', 'Hyrule Castle Courtyard'),
|
|
('Hyrule Castle East Rock (Inner)', 'Hyrule Castle East Entry'), #glove
|
|
('Hyrule Castle East Rock (Outer)', 'Hyrule Castle Area'), #glove
|
|
('Wooden Bridge Bush (South)', 'Wooden Bridge Northeast'), #pearl
|
|
('Wooden Bridge Bush (North)', 'Wooden Bridge Area'), #pearl
|
|
('Wooden Bridge Water Drop', 'Wooden Bridge Water'), #flippers
|
|
('Wooden Bridge Northeast Water Drop', 'Wooden Bridge Water'), #flippers
|
|
('Blacksmith Ledge Peg (West)', 'Blacksmith Ledge'), #hammer
|
|
('Blacksmith Ledge Peg (East)', 'Blacksmith Area'), #hammer
|
|
('Maze Race Game', 'Maze Race Prize'), #pearl
|
|
('Maze Race Ledge Drop', 'Maze Race Area'),
|
|
('Stone Bridge (Southbound)', 'Stone Bridge South Area'),
|
|
('Stone Bridge (Northbound)', 'Stone Bridge North Area'),
|
|
('Desert Statue Move', 'Desert Stairs'), #book
|
|
('Desert Ledge Drop', 'Desert Area'),
|
|
('Desert Ledge Rocks (Outer)', 'Desert Ledge Keep'), #glove
|
|
('Desert Ledge Rocks (Inner)', 'Desert Ledge'), #glove
|
|
('Checkerboard Ledge Drop', 'Desert Area'),
|
|
('Desert Mouth Drop', 'Desert Area'),
|
|
('Desert Teleporter Drop', 'Desert Area'),
|
|
('Bombos Tablet Drop', 'Desert Area'),
|
|
('Flute Boy Bush (North)', 'Flute Boy Approach Area'), #pearl
|
|
('Flute Boy Bush (South)', 'Flute Boy Bush Entry'), #pearl
|
|
('C Whirlpool Water Entry', 'C Whirlpool Water'), #flippers
|
|
('C Whirlpool Landing', 'C Whirlpool Area'),
|
|
('C Whirlpool Rock (Bottom)', 'C Whirlpool Outer Area'), #glove
|
|
('C Whirlpool Rock (Top)', 'C Whirlpool Area'), #glove
|
|
('C Whirlpool Pegs (Outer)', 'C Whirlpool Portal Area'), #hammer
|
|
('C Whirlpool Pegs (Inner)', 'C Whirlpool Area'), #hammer
|
|
('Statues Water Entry', 'Statues Water'), #flippers
|
|
('Statues Landing', 'Statues Area'),
|
|
('Lake Hylia Water Drop', 'Lake Hylia Water'), #flippers
|
|
('Lake Hylia South Water Drop', 'Lake Hylia Water'), #flippers
|
|
('Lake Hylia Northeast Water Drop', 'Lake Hylia Water'), #flippers
|
|
('Lake Hylia Central Water Drop', 'Lake Hylia Water'), #flippers
|
|
('Lake Hylia Island Water Drop', 'Lake Hylia Water'), #flippers
|
|
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
|
|
('Lake Hylia West Pier', 'Lake Hylia Northwest Bank'),
|
|
('Lake Hylia East Pier', 'Lake Hylia Northeast Bank'),
|
|
('Lake Hylia Water D Approach', 'Lake Hylia Water D'),
|
|
('Lake Hylia Water D Leave', 'Lake Hylia Water'), #flippers
|
|
('Ice Cave Water Drop', 'Ice Cave Water'), #flippers
|
|
('Ice Cave Pier', 'Ice Cave Area'),
|
|
('Desert Pass Ledge Drop', 'Desert Pass Area'),
|
|
('Desert Pass Rocks (North)', 'Desert Pass Southeast'), #glove
|
|
('Desert Pass Rocks (South)', 'Desert Pass Area'), #glove
|
|
('Middle Aged Man', 'Middle Aged Man'),
|
|
('Octoballoon Water Drop', 'Octoballoon Water'), #flippers
|
|
('Octoballoon Waterfall Water Drop', 'Octoballoon Water'), #flippers
|
|
('Octoballoon Pier', 'Octoballoon Area'),
|
|
|
|
('Skull Woods Rock (West)', 'Skull Woods Forest'), #glove
|
|
('Skull Woods Rock (East)', 'Skull Woods Portal Entry'), #glove
|
|
('Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Path (Northeast)'), #pearl
|
|
('Skull Woods Forgotten Bush (East)', 'Skull Woods Forgotten Path (Southwest)'), #pearl
|
|
('West Dark Death Mountain Drop', 'West Dark Death Mountain (Bottom)'),
|
|
('GT Approach', 'GT Stairs'),
|
|
('GT Leave', 'West Dark Death Mountain (Top)'),
|
|
('Floating Island Drop', 'East Dark Death Mountain (Top)'),
|
|
('East Dark Death Mountain Drop', 'East Dark Death Mountain (Bottom)'),
|
|
('East Dark Death Mountain Bushes', 'East Dark Death Mountain (Bushes)'),
|
|
('Turtle Rock Ledge Drop', 'Turtle Rock Area'),
|
|
('Bumper Cave Rock (Outer)', 'Bumper Cave Entry'), #glove
|
|
('Bumper Cave Rock (Inner)', 'Bumper Cave Area'), #glove
|
|
('Bumper Cave Ledge Drop', 'Bumper Cave Area'),
|
|
('Bumper Cave Entry Drop', 'Bumper Cave Area'),
|
|
('Skull Woods Pass Bush Row (West)', 'Skull Woods Pass East Top Area'), #pearl
|
|
('Skull Woods Pass Bush Row (East)', 'Skull Woods Pass West Area'), #pearl
|
|
('Skull Woods Pass Bush (North)', 'Skull Woods Pass Portal Area'), #pearl
|
|
('Skull Woods Pass Bush (South)', 'Skull Woods Pass East Top Area'), #pearl
|
|
('Skull Woods Pass Rock (North)', 'Skull Woods Pass East Bottom Area'), #mitts
|
|
('Skull Woods Pass Rock (South)', 'Skull Woods Pass Portal Area'), #mitts
|
|
('Dark Graveyard Bush (South)', 'Dark Graveyard North'), #pearl
|
|
('Dark Graveyard Bush (North)', 'Dark Graveyard Area'), #pearl
|
|
('Qirn Jump Water Drop', 'Qirn Jump Water'), #flippers
|
|
('Qirn Jump East Water Drop', 'Qirn Jump Water'), #flippers
|
|
('Qirn Jump Pier', 'Qirn Jump East Bank'),
|
|
('Dark Witch Water Drop', 'Dark Witch Water'), #flippers
|
|
('Dark Witch Northeast Water Drop', 'Dark Witch Water'), #flippers
|
|
('Dark Witch Rock (North)', 'Dark Witch Area'), #glove
|
|
('Dark Witch Rock (South)', 'Dark Witch Northeast'), #glove
|
|
('Catfish Approach Water Drop', 'Catfish Approach Water'), #flippers
|
|
('Catfish Approach Rocks (West)', 'Catfish Approach Ledge'), #mitts/boots
|
|
('Catfish Approach Rocks (East)', 'Catfish Approach Area'), #mitts/boots
|
|
('Catfish Approach Bottom Ledge Drop', 'Catfish Approach Ledge'),
|
|
('Catfish Approach Ledge Drop', 'Catfish Approach Area'),
|
|
('Bush Yard Pegs (Outer)', 'Village of Outcasts Bush Yard'), #hammer
|
|
('Bush Yard Pegs (Inner)', 'Village of Outcasts'), #hammer
|
|
('Shield Shop Fence Drop (Outer)', 'Shield Shop Fence'),
|
|
('Shield Shop Fence Drop (Inner)', 'Shield Shop Area'),
|
|
('Pyramid Exit Ledge Drop', 'Pyramid Area'),
|
|
('Pyramid Crack', 'Pyramid Crack'),
|
|
('Broken Bridge Hammer Rock (South)', 'Broken Bridge Northeast'), #hammer/glove
|
|
('Broken Bridge Hammer Rock (North)', 'Broken Bridge Area'), #hammer/glove
|
|
('Broken Bridge Hookshot Gap', 'Broken Bridge West'), #hookshot
|
|
('Broken Bridge Water Drop', 'Broken Bridge Water'), #flippers
|
|
('Broken Bridge Northeast Water Drop', 'Broken Bridge Water'), #flippers
|
|
('Broken Bridge West Water Drop', 'Broken Bridge Water'), #flippers
|
|
('Kiki Assistance', 'Dark Palace Button'),
|
|
('Peg Area Rocks (West)', 'Hammer Pegs Area'), #mitts
|
|
('Peg Area Rocks (East)', 'Hammer Pegs Entry'), #mitts
|
|
('Dig Game To Ledge Drop', 'Dig Game Ledge'), #mitts
|
|
('Dig Game Ledge Drop', 'Dig Game Area'),
|
|
('Frog Ledge Drop', 'Archery Game Area'),
|
|
('Frog Rock (Inner)', 'Frog Area'), #mitts
|
|
('Frog Rock (Outer)', 'Frog Prison'), #mitts
|
|
('Archery Game Rock (North)', 'Archery Game Area'), #mitts
|
|
('Archery Game Rock (South)', 'Frog Area'), #mitts
|
|
('Hammer Bridge Pegs (North)', 'Hammer Bridge South Area'), #hammer
|
|
('Hammer Bridge Pegs (South)', 'Hammer Bridge North Area'), #hammer
|
|
('Hammer Bridge Water Drop', 'Hammer Bridge Water'), #flippers
|
|
('Hammer Bridge Pier', 'Hammer Bridge North Area'),
|
|
('Mire Teleporter Ledge Drop', 'Mire Area'),
|
|
('Stumpy Approach Bush (North)', 'Stumpy Approach Area'), #pearl
|
|
('Stumpy Approach Bush (South)', 'Stumpy Approach Bush Entry'), #pearl
|
|
('Dark C Whirlpool Water Entry', 'Dark C Whirlpool Water'), #flippers
|
|
('Dark C Whirlpool Landing', 'Dark C Whirlpool Area'),
|
|
('Dark C Whirlpool Rock (Bottom)', 'Dark C Whirlpool Outer Area'), #glove
|
|
('Dark C Whirlpool Rock (Top)', 'Dark C Whirlpool Area'), #glove
|
|
('Dark C Whirlpool Pegs (Outer)', 'Dark C Whirlpool Portal Area'), #hammer
|
|
('Dark C Whirlpool Pegs (Inner)', 'Dark C Whirlpool Area'), #hammer
|
|
('Hype Cave Water Entry', 'Hype Cave Water'), #flippers
|
|
('Hype Cave Landing', 'Hype Cave Area'),
|
|
('Ice Lake Water Drop', 'Ice Lake Water'), #flippers
|
|
('Ice Lake Northeast Water Drop', 'Ice Lake Water'), #flippers
|
|
('Ice Lake Southwest Water Drop', 'Ice Lake Water'), #flippers
|
|
('Ice Lake Southeast Water Drop', 'Ice Lake Water'), #flippers
|
|
('Ice Lake Iceberg Water Entry', 'Ice Lake Water'), #flippers
|
|
('Ice Lake Northeast Pier', 'Ice Lake Northeast Bank'),
|
|
('Shopping Mall Water Drop', 'Shopping Mall Water'), #flippers
|
|
('Shopping Mall Pier', 'Shopping Mall Area'),
|
|
('Bomber Corner Water Drop', 'Bomber Corner Water'), #flippers
|
|
('Bomber Corner Waterfall Water Drop', 'Bomber Corner Water'), #flippers
|
|
('Bomber Corner Pier', 'Bomber Corner Area'),
|
|
|
|
# OWG In-Bounds Connections
|
|
('Ice Lake Northeast Pier Hop', 'Ice Lake Northeast Bank'),
|
|
('Ice Lake Iceberg Bomb Jump', 'Ice Lake Iceberg')
|
|
]
|
|
|
|
default_whirlpool_connections = [
|
|
((0x33, 'C Whirlpool', 'C Whirlpool Water'), (0x15, 'River Bend Whirlpool', 'River Bend Water')),
|
|
((0x35, 'Lake Hylia Whirlpool', 'Lake Hylia Water'), (0x0f, 'Zora Whirlpool', 'Zora Waterfall Water')),
|
|
((0x12, 'Kakariko Pond Whirlpool', 'Kakariko Pond Area'), (0x3f, 'Octoballoon Whirlpool', 'Octoballoon Water')),
|
|
((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water'))
|
|
]
|
|
|
|
default_flute_connections = [
|
|
0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f
|
|
]
|
|
|
|
ow_connections = {
|
|
0x03: ([
|
|
('West Death Mountain Teleporter', 'West Dark Death Mountain (Bottom)')
|
|
], [
|
|
('Spectacle Rock Leave', 'West Death Mountain (Top)'),
|
|
('Spectacle Rock Approach', 'Spectacle Rock Ledge'),
|
|
('Dark Death Mountain Teleporter (West)', 'West Death Mountain (Bottom)')
|
|
]),
|
|
0x05: ([
|
|
('EDM To Fairy Ledge Drop', 'Fairy Ascension Ledge'),
|
|
('East Death Mountain Teleporter', 'East Dark Death Mountain (Bottom)')
|
|
], [
|
|
('Floating Island Bridge (West)', 'East Death Mountain (Top East)'),
|
|
('Floating Island Bridge (East)', 'Death Mountain Floating Island'),
|
|
('EDM To Mimic Ledge Drop', 'Mimic Cave Ledge'),
|
|
('Spiral Mimic Bridge (West)', 'Spiral Mimic Ledge Extend'),
|
|
('Spiral Mimic Bridge (East)', 'Spiral Mimic Ledge Extend'),
|
|
('Spiral Ledge Approach', 'Spiral Cave Ledge'),
|
|
('Mimic Ledge Approach', 'Mimic Cave Ledge'),
|
|
('Spiral Mimic Ledge Drop', 'Fairy Ascension Ledge'),
|
|
('East Dark Death Mountain Teleporter', 'East Death Mountain (Bottom)')
|
|
]),
|
|
0x07: ([
|
|
('TR Pegs Teleporter', 'Turtle Rock Ledge'),
|
|
('TR Pegs Ledge Drop', 'Death Mountain TR Pegs Area')
|
|
], [
|
|
('Turtle Rock Tail Ledge Drop', 'Turtle Rock Ledge'),
|
|
('Turtle Rock Teleporter', 'Death Mountain TR Pegs Ledge')
|
|
]),
|
|
0x10: ([
|
|
('Kakariko Teleporter', 'Skull Woods Pass Portal Area')
|
|
], [
|
|
('West Dark World Teleporter', 'Lost Woods Pass Portal Area')
|
|
]),
|
|
0x14: ([
|
|
|
|
], [
|
|
('Graveyard Ladder (Top)', 'Graveyard Area'),
|
|
('Graveyard Ladder (Bottom)', 'Graveyard Ledge')
|
|
]),
|
|
0x1b: ([
|
|
('Castle Gate Teleporter', 'Pyramid Area'),
|
|
('Castle Gate Teleporter (Inner)', 'Pyramid Area')
|
|
], [
|
|
('Post Aga Teleporter', 'Hyrule Castle Area')
|
|
]),
|
|
0x1e: ([
|
|
('Eastern Palace Cliff Ledge Drop', 'Eastern Palace Area'), # OWG
|
|
('Palace of Darkness Cliff Ledge Drop', 'Palace of Darkness Area') # OWG
|
|
], [
|
|
('Eastern Palace Cliff Ledge Drop', 'Palace of Darkness Area'), # OWG
|
|
('Palace of Darkness Cliff Ledge Drop', 'Eastern Palace Area') # OWG
|
|
]),
|
|
0x25: ([
|
|
('Sand Dunes Cliff Ledge Drop', 'Sand Dunes Area'), # OWG
|
|
('Dark Dunes Cliff Ledge Drop', 'Dark Dunes Area') # OWG
|
|
], [
|
|
('Sand Dunes Cliff Ledge Drop', 'Dark Dunes Area'), # OWG
|
|
('Dark Dunes Cliff Ledge Drop', 'Sand Dunes Area') # OWG
|
|
]),
|
|
0x29: ([
|
|
('Suburb Cliff Ledge Drop', 'Kakariko Suburb Area'), # OWG
|
|
('Archery Game Cliff Ledge Drop', 'Archery Game Area') # OWG
|
|
], [
|
|
('Suburb Cliff Ledge Drop', 'Archery Game Area'), # OWG
|
|
('Archery Game Cliff Ledge Drop', 'Kakariko Suburb Area') # OWG
|
|
]),
|
|
0x2b: ([
|
|
('Central Bonk Rocks Cliff Ledge Drop', 'Central Bonk Rocks Area'), # OWG
|
|
('Dark Bonk Rocks Cliff Ledge Drop', 'Dark Bonk Rocks Area') # OWG
|
|
], [
|
|
('Central Bonk Rocks Cliff Ledge Drop', 'Dark Bonk Rocks Area'), # OWG
|
|
('Dark Bonk Rocks Cliff Ledge Drop', 'Central Bonk Rocks Area') # OWG
|
|
]),
|
|
0x2c: ([
|
|
('Links House Cliff Ledge Drop', 'Links House Area'), # OWG
|
|
('Bomb Shop Cliff Ledge Drop', 'Big Bomb Shop Area') # OWG
|
|
], [
|
|
('Links House Cliff Ledge Drop', 'Big Bomb Shop Area'), # OWG
|
|
('Bomb Shop Cliff Ledge Drop', 'Links House Area') # OWG
|
|
]),
|
|
0x2d: ([
|
|
('Stone Bridge East Cliff Ledge Drop', 'Stone Bridge North Area'), # OWG
|
|
('Hammer Bridge North Cliff Ledge Drop', 'Hammer Bridge North Area'), # OWG
|
|
('Stone Bridge Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG
|
|
('Hammer Bridge South Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG
|
|
('Stone Bridge EC Cliff Water Drop', 'Stone Bridge Water'), # fake flipper
|
|
('Hammer Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), # fake flipper
|
|
('Tree Line WC Cliff Water Drop', 'Tree Line Water'), # fake flipper
|
|
('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') # fake flipper
|
|
], [
|
|
('Stone Bridge East Cliff Ledge Drop', 'Hammer Bridge North Area'), # OWG
|
|
('Hammer Bridge North Cliff Ledge Drop', 'Stone Bridge North Area'), # OWG
|
|
('Stone Bridge Cliff Ledge Drop', 'Hammer Bridge South Area'), # OWG
|
|
('Hammer Bridge South Cliff Ledge Drop', 'Stone Bridge South Area'), # OWG
|
|
('Stone Bridge EC Cliff Water Drop', 'Hammer Bridge Water'), # fake flipper
|
|
('Hammer Bridge EC Cliff Water Drop', 'Stone Bridge Water'), # fake flipper
|
|
('Tree Line WC Cliff Water Drop', 'Dark Tree Line Water'), # fake flipper
|
|
('Dark Tree Line WC Cliff Water Drop', 'Tree Line Water') # fake flipper
|
|
]),
|
|
0x2e: ([
|
|
('Tree Line Cliff Ledge Drop', 'Tree Line Area'), # OWG
|
|
('Dark Tree Line Cliff Ledge Drop', 'Dark Tree Line Area') # OWG
|
|
], [
|
|
('Tree Line Cliff Ledge Drop', 'Dark Tree Line Area'), # OWG
|
|
('Dark Tree Line Cliff Ledge Drop', 'Tree Line Area') # OWG
|
|
]),
|
|
0x2f: ([
|
|
('East Hyrule Teleporter', 'Darkness Nook Area')
|
|
], [
|
|
('East Dark World Teleporter', 'Eastern Nook Area')
|
|
]),
|
|
0x30: ([
|
|
('Mirror To Bombos Tablet Ledge', 'Bombos Tablet Ledge'), # OWG
|
|
('Desert Teleporter', 'Mire Teleporter Ledge'),
|
|
('Mire Cliff Ledge Drop', 'Mire Area'), # OWG
|
|
('Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge') # OWG
|
|
], [
|
|
('Checkerboard Ledge Approach', 'Desert Checkerboard Ledge'),
|
|
('Checkerboard Ledge Leave', 'Desert Area'),
|
|
('Mire Teleporter', 'Desert Teleporter Ledge'),
|
|
('Mire Cliff Ledge Drop', 'Desert Ledge Keep'), # OWG
|
|
('Dark Checkerboard Cliff Ledge Drop', 'Desert Checkerboard Ledge') # OWG
|
|
]),
|
|
0x32: ([
|
|
('Cave 45 Ledge Drop', 'Flute Boy Approach Area'),
|
|
('Cave 45 Cliff Ledge Drop', 'Cave 45 Ledge'), # OWG
|
|
('Stumpy Approach Cliff Ledge Drop', 'Stumpy Approach Area') # OWG
|
|
], [
|
|
('Cave 45 Leave', 'Flute Boy Approach Area'),
|
|
('Cave 45 Approach', 'Cave 45 Ledge'),
|
|
('Cave 45 Cliff Ledge Drop', 'Stumpy Approach Area'), # OWG
|
|
('Stumpy Approach Cliff Ledge Drop', 'Cave 45 Ledge') # OWG
|
|
]),
|
|
0x33: ([
|
|
('South Hyrule Teleporter', 'Dark C Whirlpool Portal Area'),
|
|
('C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), # OWG
|
|
('Dark C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), # OWG
|
|
('C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG
|
|
('Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG
|
|
('C Whirlpool Portal Cliff Ledge Drop', 'C Whirlpool Portal Area'), #OWG
|
|
('Dark C Whirlpool Portal Cliff Ledge Drop', 'Dark C Whirlpool Portal Area'), #OWG
|
|
('Desert C Whirlpool Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG
|
|
('Mire C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Outer Area') # OWG
|
|
], [
|
|
('South Dark World Teleporter', 'C Whirlpool Portal Area'),
|
|
('C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Area'), # OWG
|
|
('Dark C Whirlpool Cliff Ledge Drop', 'C Whirlpool Area'), # OWG
|
|
('C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG
|
|
('Dark C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Outer Area'), # OWG
|
|
('C Whirlpool Portal Cliff Ledge Drop', 'Dark C Whirlpool Portal Area'), #OWG
|
|
('Dark C Whirlpool Portal Cliff Ledge Drop', 'C Whirlpool Portal Area'), #OWG
|
|
('Desert C Whirlpool Cliff Ledge Drop', 'Dark C Whirlpool Outer Area'), # OWG
|
|
('Mire C Whirlpool Cliff Ledge Drop', 'C Whirlpool Outer Area') # OWG
|
|
]),
|
|
0x34: ([
|
|
('Statues Cliff Ledge Drop', 'Statues Area'), # OWG
|
|
('Hype Cliff Ledge Drop', 'Hype Cave Area') # OWG
|
|
], [
|
|
('Statues Cliff Ledge Drop', 'Hype Cave Area'), # OWG
|
|
('Hype Cliff Ledge Drop', 'Statues Area') # OWG
|
|
]),
|
|
0x35: ([
|
|
('Lake Hylia Teleporter', 'Ice Palace Area'),
|
|
('Lake Hylia Northwest Cliff Ledge Drop', 'Lake Hylia Northwest Bank'), # OWG
|
|
('Ice Lake Northwest Cliff Ledge Drop', 'Ice Lake Northwest Bank'), # OWG
|
|
('Lake Hylia Island FAWT Ledge Drop', 'Lake Hylia Island'), # OWG
|
|
('Ice Palace Island FAWT Ledge Drop', 'Ice Lake Iceberg') # OWG
|
|
], [
|
|
('Lake Hylia Island Pier', 'Lake Hylia Island'),
|
|
('Ice Lake Teleporter', 'Lake Hylia Water D'),
|
|
('Lake Hylia Northwest Cliff Ledge Drop', 'Ice Lake Northwest Bank'), # OWG
|
|
('Ice Lake Northwest Cliff Ledge Drop', 'Lake Hylia Northwest Bank'), # OWG
|
|
('Lake Hylia Island FAWT Ledge Drop', 'Ice Lake Iceberg'), # OWG
|
|
('Ice Palace Island FAWT Ledge Drop', 'Lake Hylia Island') # OWG
|
|
]),
|
|
0x3a: ([
|
|
('Desert Pass Cliff Ledge Drop', 'Desert Pass Area'), # OWG
|
|
('Swamp Nook Cliff Ledge Drop', 'Swamp Nook Area') # OWG
|
|
], [
|
|
('Desert Pass Ladder (North)', 'Desert Pass Area'),
|
|
('Desert Pass Ladder (South)', 'Desert Pass Ledge'),
|
|
('Desert Pass Cliff Ledge Drop', 'Swamp Nook Area'), # OWG
|
|
('Swamp Nook Cliff Ledge Drop', 'Desert Pass Area') # OWG
|
|
]),
|
|
0x3b: ([
|
|
('Dam Cliff Ledge Drop', 'Dam Area'), # OWG
|
|
('Swamp Cliff Ledge Drop', 'Swamp Area') # OWG
|
|
], [
|
|
('Dam Cliff Ledge Drop', 'Swamp Area'), # OWG
|
|
('Swamp Cliff Ledge Drop', 'Dam Area') # OWG
|
|
])
|
|
}
|
|
|
|
mirror_connections = {
|
|
'Skull Woods Forest': ['Lost Woods East Area'],
|
|
'Skull Woods Portal Entry': ['Lost Woods West Area'],
|
|
'Skull Woods Forest (West)': ['Lost Woods West Area'],
|
|
'Skull Woods Forgotten Path (Southwest)': ['Lost Woods West Area'],
|
|
'Skull Woods Forgotten Path (Northeast)': ['Lost Woods East Area', 'Lost Woods West Area'],
|
|
|
|
'Dark Lumberjack Area': ['Lumberjack Area'],
|
|
|
|
'West Dark Death Mountain (Top)': ['West Death Mountain (Top)'],
|
|
'West Dark Death Mountain (Bottom)': ['Spectacle Rock Ledge'],
|
|
|
|
'Dark Death Mountain Floating Island': ['Death Mountain Floating Island'],
|
|
'East Dark Death Mountain (Top)': ['East Death Mountain (Top West)', 'East Death Mountain (Top East)'],
|
|
'Dark Death Mountain Ledge': ['Spiral Cave Ledge', 'Mimic Cave Ledge'],
|
|
'Dark Death Mountain Isolated Ledge': ['Fairy Ascension Ledge'],
|
|
'East Dark Death Mountain (Bushes)': ['Fairy Ascension Plateau'],
|
|
'East Dark Death Mountain (Bottom Left)': ['East Death Mountain (Bottom Left)'],
|
|
|
|
'Turtle Rock Area': ['Death Mountain TR Pegs Area'],
|
|
|
|
'Bumper Cave Area': ['Mountain Pass Area'],
|
|
'Bumper Cave Entry': ['Mountain Pass Entry'],
|
|
'Bumper Cave Ledge': ['Mountain Pass Ledge'],
|
|
|
|
'Catfish Area': ['Zora Waterfall Area'],
|
|
|
|
'Skull Woods Pass West Area': ['Lost Woods Pass West Area'],
|
|
'Skull Woods Pass East Top Area': ['Lost Woods Pass East Top Area'],
|
|
'Skull Woods Pass Portal Area': ['Lost Woods Pass Portal Area'],
|
|
'Skull Woods Pass East Bottom Area': ['Lost Woods Pass East Bottom Area'],
|
|
|
|
'Dark Fortune Area': ['Kakariko Fortune Area'],
|
|
|
|
'Outcast Pond Area': ['Kakariko Pond Area'],
|
|
|
|
'Dark Chapel Area': ['Sanctuary Area', 'Bonk Rock Ledge'],
|
|
|
|
'Dark Graveyard Area': ['Graveyard Area'],
|
|
'Dark Graveyard North': ['Graveyard Ledge', 'Kings Grave Area'],
|
|
|
|
'Qirn Jump Area': ['River Bend Area'],
|
|
'Qirn Jump East Bank': ['River Bend East Bank'],
|
|
|
|
'Dark Witch Area': ['Potion Shop Area'],
|
|
'Dark Witch Northeast': ['Potion Shop Northeast'],
|
|
|
|
'Catfish Approach Area': ['Zora Approach Area'],
|
|
'Catfish Approach Ledge': ['Zora Approach Ledge'],
|
|
|
|
'Village of Outcasts': ['Kakariko Village'],
|
|
'Village of Outcasts Bush Yard': ['Kakariko Village'],
|
|
|
|
'Shield Shop Area': ['Forgotten Forest Area'],
|
|
'Shield Shop Fence': ['Forgotten Forest Area'],
|
|
|
|
'Pyramid Area': ['Hyrule Castle Ledge', 'Hyrule Castle Courtyard', 'Hyrule Castle Area', 'Hyrule Castle East Entry'],
|
|
'Pyramid Exit Ledge': ['Hyrule Castle Courtyard'],
|
|
'Pyramid Pass': ['Hyrule Castle Area'],
|
|
|
|
'Broken Bridge Area': ['Wooden Bridge Area'],
|
|
'Broken Bridge Northeast': ['Wooden Bridge Area'],
|
|
'Broken Bridge West': ['Wooden Bridge Area'],
|
|
|
|
'Palace of Darkness Area': ['Eastern Palace Area'],
|
|
|
|
'Hammer Pegs Area': ['Blacksmith Area', 'Blacksmith Ledge'],
|
|
'Hammer Pegs Entry': ['Blacksmith Area'],
|
|
|
|
'Dark Dunes Area': ['Sand Dunes Area'],
|
|
|
|
'Dig Game Area': ['Maze Race Ledge'],
|
|
'Dig Game Ledge': ['Maze Race Ledge'],
|
|
|
|
'Frog Area': ['Kakariko Suburb Area'],
|
|
'Archery Game Area': ['Kakariko Suburb Area'],
|
|
|
|
'Stumpy Area': ['Flute Boy Area'],
|
|
'Stumpy Pass': ['Flute Boy Pass'],
|
|
|
|
'Dark Bonk Rocks Area': ['Central Bonk Rocks Area'],
|
|
|
|
'Big Bomb Shop Area': ['Links House Area'],
|
|
|
|
'Hammer Bridge North Area': ['Stone Bridge North Area'],
|
|
'Hammer Bridge South Area': ['Stone Bridge South Area'],
|
|
'Hammer Bridge Water': ['Stone Bridge Water'],
|
|
|
|
'Dark Tree Line Area': ['Tree Line Area'],
|
|
|
|
'Darkness Nook Area': ['Eastern Nook Area'],
|
|
|
|
'Mire Area': ['Desert Area', 'Desert Ledge', 'Desert Checkerboard Ledge', 'Desert Stairs', 'Desert Ledge Keep'],
|
|
|
|
'Stumpy Approach Area': ['Cave 45 Ledge'],
|
|
'Stumpy Approach Bush Entry': ['Flute Boy Bush Entry'],
|
|
|
|
'Dark C Whirlpool Area': ['C Whirlpool Area'],
|
|
'Dark C Whirlpool Outer Area': ['C Whirlpool Outer Area'],
|
|
|
|
'Hype Cave Area': ['Statues Area'],
|
|
|
|
'Ice Lake Northwest Bank': ['Lake Hylia Northwest Bank'],
|
|
'Ice Lake Northeast Bank': ['Lake Hylia Northeast Bank'],
|
|
'Ice Lake Southwest Ledge': ['Lake Hylia South Shore'],
|
|
'Ice Lake Southeast Ledge': ['Lake Hylia South Shore'],
|
|
'Ice Lake Water': ['Lake Hylia Island'],
|
|
'Ice Palace Area': ['Lake Hylia Central Island'],
|
|
'Ice Lake Iceberg': ['Lake Hylia Water', 'Lake Hylia Water D'], #first one needs flippers
|
|
|
|
'Shopping Mall Area': ['Ice Cave Area'],
|
|
|
|
'Swamp Nook Area': ['Desert Pass Area', 'Desert Pass Ledge'],
|
|
|
|
'Swamp Area': ['Dam Area'],
|
|
|
|
'Dark South Pass Area': ['South Pass Area'],
|
|
|
|
'Bomber Corner Area': ['Octoballoon Area'],
|
|
|
|
|
|
'Lost Woods West Area': ['Skull Woods Forest (West)', 'Skull Woods Forgotten Path (Southwest)', 'Skull Woods Portal Entry'],
|
|
#'Lost Woods West Area': ['Skull Woods Forgotten Path (Northeast)'], # technically yes, but we dont need it
|
|
'Lost Woods East Area': ['Skull Woods Forgotten Path (Northeast)', 'Skull Woods Forest'],
|
|
|
|
'Lumberjack Area': ['Dark Lumberjack Area'],
|
|
|
|
'West Death Mountain (Top)': ['West Dark Death Mountain (Top)'],
|
|
'Spectacle Rock Ledge': ['West Dark Death Mountain (Bottom)'],
|
|
'West Death Mountain (Bottom)': ['West Dark Death Mountain (Bottom)'],
|
|
|
|
'East Death Mountain (Top West)': ['East Dark Death Mountain (Top)'],
|
|
'East Death Mountain (Top East)': ['East Dark Death Mountain (Top)'],
|
|
'Spiral Cave Ledge': ['Dark Death Mountain Ledge'],
|
|
'Mimic Cave Ledge': ['Dark Death Mountain Ledge'],
|
|
'Fairy Ascension Ledge': ['Dark Death Mountain Isolated Ledge'],
|
|
'Fairy Ascension Plateau': ['East Dark Death Mountain (Bottom)'],
|
|
'East Death Mountain (Bottom Left)': ['East Dark Death Mountain (Bottom Left)'],
|
|
'East Death Mountain (Bottom)': ['East Dark Death Mountain (Bottom)'],
|
|
'Death Mountain Floating Island': ['Dark Death Mountain Floating Island'],
|
|
|
|
'Death Mountain TR Pegs Area': ['Turtle Rock Area'],
|
|
'Death Mountain TR Pegs Ledge': ['Turtle Rock Ledge'],
|
|
|
|
'Mountain Pass Area': ['Bumper Cave Area'],
|
|
'Mountain Pass Entry': ['Bumper Cave Entry'],
|
|
'Mountain Pass Ledge': ['Bumper Cave Ledge'],
|
|
|
|
'Zora Waterfall Area': ['Catfish Area'],
|
|
|
|
'Lost Woods Pass West Area': ['Skull Woods Pass West Area'],
|
|
'Lost Woods Pass East Top Area': ['Skull Woods Pass East Top Area'],
|
|
'Lost Woods Pass Portal Area': ['Skull Woods Pass Portal Area'],
|
|
'Lost Woods Pass East Bottom Area': ['Skull Woods Pass East Bottom Area'],
|
|
|
|
'Kakariko Fortune Area': ['Dark Fortune Area'],
|
|
|
|
'Kakariko Pond Area': ['Outcast Pond Area'],
|
|
|
|
'Sanctuary Area': ['Dark Chapel Area'],
|
|
'Bonk Rock Ledge': ['Dark Chapel Area'],
|
|
|
|
'Graveyard Area': ['Dark Graveyard Area'],
|
|
'Graveyard Ledge': ['Dark Graveyard Area'],
|
|
'Kings Grave Area': ['Dark Graveyard Area'],
|
|
|
|
'River Bend Area': ['Qirn Jump Area'],
|
|
'River Bend East Bank': ['Qirn Jump East Bank'],
|
|
|
|
'Potion Shop Area': ['Dark Witch Area'],
|
|
'Potion Shop Northeast': ['Dark Witch Northeast'],
|
|
|
|
'Zora Approach Area': ['Catfish Approach Area'],
|
|
'Zora Approach Ledge': ['Catfish Approach Ledge'],
|
|
|
|
'Kakariko Village': ['Village of Outcasts'],
|
|
'Kakariko Southwest': ['Village of Outcasts'],
|
|
'Kakariko Bush Yard': ['Village of Outcasts Bush Yard'],
|
|
|
|
'Forgotten Forest Area': ['Shield Shop Area'],
|
|
|
|
'Hyrule Castle Area': ['Pyramid Area', 'Pyramid Pass'],
|
|
'Hyrule Castle Southwest': ['Pyramid Pass'],
|
|
'Hyrule Castle Courtyard': ['Pyramid Area'],
|
|
'Hyrule Castle Courtyard Northeast': ['Pyramid Area'],
|
|
'Hyrule Castle Ledge': ['Pyramid Area'],
|
|
'Hyrule Castle East Entry': ['Pyramid Area'],
|
|
|
|
'Wooden Bridge Area': ['Broken Bridge Area', 'Broken Bridge West'],
|
|
'Wooden Bridge Northeast': ['Broken Bridge Northeast'],
|
|
|
|
'Eastern Palace Area': ['Palace of Darkness Area'],
|
|
|
|
'Blacksmith Area': ['Hammer Pegs Area', 'Hammer Pegs Entry'],
|
|
|
|
'Sand Dunes Area': ['Dark Dunes Area'],
|
|
|
|
'Maze Race Area': ['Dig Game Area'],
|
|
'Maze Race Ledge': ['Dig Game Ledge'],
|
|
|
|
'Kakariko Suburb Area': ['Frog Area', 'Frog Prison', 'Archery Game Area'],
|
|
|
|
'Flute Boy Area': ['Stumpy Area'],
|
|
'Flute Boy Pass': ['Stumpy Pass'],
|
|
|
|
'Central Bonk Rocks Area': ['Dark Bonk Rocks Area'],
|
|
|
|
'Links House Area': ['Big Bomb Shop Area'],
|
|
|
|
'Stone Bridge North Area': ['Hammer Bridge North Area'],
|
|
'Stone Bridge South Area': ['Hammer Bridge South Area'],
|
|
'Stone Bridge Water': ['Hammer Bridge Water'],
|
|
|
|
'Tree Line Area': ['Dark Tree Line Area'],
|
|
|
|
'Eastern Nook Area': ['Darkness Nook Area'],
|
|
|
|
'Desert Area': ['Mire Area'],
|
|
'Desert Ledge': ['Mire Area'],
|
|
'Desert Ledge Keep': ['Mire Area'],
|
|
'Desert Checkerboard Ledge': ['Mire Area'],
|
|
'Desert Stairs': ['Mire Area'],
|
|
|
|
'Flute Boy Approach Area': ['Stumpy Approach Area'],
|
|
'Cave 45 Ledge': ['Stumpy Approach Area'],
|
|
'Flute Boy Bush Entry': ['Stumpy Approach Bush Entry'],
|
|
|
|
'C Whirlpool Area': ['Dark C Whirlpool Area'],
|
|
'C Whirlpool Outer Area': ['Dark C Whirlpool Outer Area'],
|
|
|
|
'Statues Area': ['Hype Cave Area'],
|
|
|
|
'Lake Hylia Northwest Bank': ['Ice Lake Northwest Bank'],
|
|
'Lake Hylia South Shore': ['Ice Lake Southwest Ledge', 'Ice Lake Southeast Ledge'],
|
|
'Lake Hylia Northeast Bank': ['Ice Lake Northeast Bank'],
|
|
'Lake Hylia Central Island': ['Ice Palace Area'],
|
|
'Lake Hylia Water D': ['Ice Lake Iceberg'],
|
|
|
|
'Ice Cave Area': ['Shopping Mall Area'],
|
|
|
|
'Desert Pass Area': ['Swamp Nook Area'],
|
|
'Desert Pass Southeast': ['Swamp Nook Area'],
|
|
'Desert Pass Ledge': ['Swamp Nook Area'],
|
|
|
|
'Dam Area': ['Swamp Area'],
|
|
|
|
'South Pass Area': ['Dark South Pass Area'],
|
|
|
|
'Octoballoon Area': ['Bomber Corner Area']
|
|
}
|
|
|
|
parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'),
|
|
('Dig Game EC', 'Frog WC'),
|
|
('Dig Game ES', 'Frog WS')
|
|
]
|
|
|
|
# non shuffled overworld
|
|
default_connections = [('Lost Woods NW', 'Master Sword Meadow SC'),
|
|
('Lost Woods SW', 'Lost Woods Pass NW'),
|
|
('Lost Woods SC', 'Lost Woods Pass NE'),
|
|
('Lost Woods SE', 'Kakariko Fortune NE'),
|
|
('Lost Woods EN', 'Lumberjack WN'),
|
|
('Lumberjack SW', 'Mountain Pass NW'),
|
|
('Mountain Pass SE', 'Kakariko Pond NE'),
|
|
('Zora Waterfall NE', 'Zoras Domain SW'),
|
|
('Lost Woods Pass SW', 'Kakariko NW'),
|
|
('Lost Woods Pass SE', 'Kakariko NC'),
|
|
('Kakariko Fortune SC', 'Kakariko NE'),
|
|
('Kakariko Fortune EN', 'Kakariko Pond WN'),
|
|
('Kakariko Fortune ES', 'Kakariko Pond WS'),
|
|
('Kakariko Pond SW', 'Forgotten Forest NW'),
|
|
('Kakariko Pond SE', 'Forgotten Forest NE'),
|
|
('Kakariko Pond EN', 'Sanctuary WN'),
|
|
('Kakariko Pond ES', 'Sanctuary WS'),
|
|
('Forgotten Forest ES', 'Hyrule Castle WN'),
|
|
('Sanctuary EC', 'Graveyard WC'),
|
|
('Graveyard EC', 'River Bend WC'),
|
|
('River Bend SW', 'Wooden Bridge NW'),
|
|
('River Bend SC', 'Wooden Bridge NC'),
|
|
('River Bend SE', 'Wooden Bridge NE'),
|
|
('River Bend EN', 'Potion Shop WN'),
|
|
('River Bend EC', 'Potion Shop WC'),
|
|
('River Bend ES', 'Potion Shop WS'),
|
|
('Potion Shop EN', 'Zora Approach WN'),
|
|
('Potion Shop EC', 'Zora Approach WC'),
|
|
('Zora Approach NE', 'Zora Waterfall SE'),
|
|
('Kakariko SE', 'Kakariko Suburb NE'),
|
|
('Kakariko ES', 'Blacksmith WS'),
|
|
('Hyrule Castle SW', 'Central Bonk Rocks NW'),
|
|
('Hyrule Castle SE', 'Links House NE'),
|
|
('Hyrule Castle ES', 'Sand Dunes WN'),
|
|
('Wooden Bridge SW', 'Sand Dunes NW'),
|
|
('Sand Dunes SC', 'Stone Bridge NC'),
|
|
('Eastern Palace SW', 'Tree Line NW'),
|
|
('Eastern Palace SE', 'Eastern Nook NE'),
|
|
('Maze Race ES', 'Kakariko Suburb WS'),
|
|
('Kakariko Suburb ES', 'Flute Boy WS'),
|
|
('Flute Boy SW', 'Flute Boy Approach NW'),
|
|
('Flute Boy SC', 'Flute Boy Approach NC'),
|
|
('Flute Boy Approach EC', 'C Whirlpool WC'),
|
|
('C Whirlpool NW', 'Central Bonk Rocks SW'),
|
|
('C Whirlpool SC', 'Dam NC'),
|
|
('C Whirlpool EN', 'Statues WN'),
|
|
('C Whirlpool EC', 'Statues WC'),
|
|
('C Whirlpool ES', 'Statues WS'),
|
|
('Central Bonk Rocks EN', 'Links House WN'),
|
|
('Central Bonk Rocks EC', 'Links House WC'),
|
|
('Central Bonk Rocks ES', 'Links House WS'),
|
|
('Links House SC', 'Statues NC'),
|
|
('Links House ES', 'Stone Bridge WS'),
|
|
('Stone Bridge SC', 'Lake Hylia NW'),
|
|
('Stone Bridge EN', 'Tree Line WN'),
|
|
('Stone Bridge EC', 'Tree Line WC'),
|
|
('Stone Bridge WC', 'Hobo EC'),
|
|
('Tree Line SC', 'Lake Hylia NC'),
|
|
('Tree Line SE', 'Lake Hylia NE'),
|
|
('Desert EC', 'Desert Pass WC'),
|
|
('Desert ES', 'Desert Pass WS'),
|
|
('Desert Pass EC', 'Dam WC'),
|
|
('Desert Pass ES', 'Dam WS'),
|
|
('Dam EC', 'South Pass WC'),
|
|
('Statues SC', 'South Pass NC'),
|
|
('South Pass ES', 'Lake Hylia WS'),
|
|
('Lake Hylia EC', 'Octoballoon WC'),
|
|
('Lake Hylia ES', 'Octoballoon WS'),
|
|
('Octoballoon NW', 'Ice Cave SW'),
|
|
('Octoballoon NE', 'Ice Cave SE'),
|
|
('West Death Mountain EN', 'East Death Mountain WN'),
|
|
('West Death Mountain ES', 'East Death Mountain WS'),
|
|
('East Death Mountain EN', 'Death Mountain TR Pegs WN'),
|
|
|
|
('Skull Woods SW', 'Skull Woods Pass NW'),
|
|
('Skull Woods SC', 'Skull Woods Pass NE'),
|
|
('Skull Woods SE', 'Dark Fortune NE'),
|
|
('Skull Woods EN', 'Dark Lumberjack WN'),
|
|
('Dark Lumberjack SW', 'Bumper Cave NW'),
|
|
('Bumper Cave SE', 'Outcast Pond NE'),
|
|
('Skull Woods Pass SW', 'Village of Outcasts NW'),
|
|
('Skull Woods Pass SE', 'Village of Outcasts NC'),
|
|
('Dark Fortune SC', 'Village of Outcasts NE'),
|
|
('Dark Fortune EN', 'Outcast Pond WN'),
|
|
('Dark Fortune ES', 'Outcast Pond WS'),
|
|
('Outcast Pond SW', 'Shield Shop NW'),
|
|
('Outcast Pond SE', 'Shield Shop NE'),
|
|
('Outcast Pond EN', 'Dark Chapel WN'),
|
|
('Outcast Pond ES', 'Dark Chapel WS'),
|
|
('Dark Chapel EC', 'Dark Graveyard WC'),
|
|
('Dark Graveyard EC', 'Qirn Jump WC'),
|
|
('Qirn Jump SW', 'Broken Bridge NW'),
|
|
('Qirn Jump SC', 'Broken Bridge NC'),
|
|
('Qirn Jump SE', 'Broken Bridge NE'),
|
|
('Qirn Jump EN', 'Dark Witch WN'),
|
|
('Qirn Jump EC', 'Dark Witch WC'),
|
|
('Qirn Jump ES', 'Dark Witch WS'),
|
|
('Dark Witch EN', 'Catfish Approach WN'),
|
|
('Dark Witch EC', 'Catfish Approach WC'),
|
|
('Catfish Approach NE', 'Catfish SE'),
|
|
('Village of Outcasts SE', 'Frog NE'),
|
|
('Village of Outcasts ES', 'Hammer Pegs WS'),
|
|
('Pyramid SW', 'Dark Bonk Rocks NW'),
|
|
('Pyramid SE', 'Big Bomb Shop NE'),
|
|
('Pyramid ES', 'Dark Dunes WN'),
|
|
('Broken Bridge SW', 'Dark Dunes NW'),
|
|
('Dark Dunes SC', 'Hammer Bridge NC'),
|
|
('Palace of Darkness SW', 'Dark Tree Line NW'),
|
|
('Palace of Darkness SE', 'Palace of Darkness Nook NE'),
|
|
('Dig Game EC', 'Frog WC'),
|
|
('Dig Game ES', 'Frog WS'),
|
|
('Frog ES', 'Stumpy WS'),
|
|
('Stumpy SW', 'Stumpy Approach NW'),
|
|
('Stumpy SC', 'Stumpy Approach NC'),
|
|
('Stumpy Approach EC', 'Dark C Whirlpool WC'),
|
|
('Dark C Whirlpool NW', 'Dark Bonk Rocks SW'),
|
|
('Dark C Whirlpool SC', 'Swamp NC'),
|
|
('Dark C Whirlpool EN', 'Hype Cave WN'),
|
|
('Dark C Whirlpool EC', 'Hype Cave WC'),
|
|
('Dark C Whirlpool ES', 'Hype Cave WS'),
|
|
('Dark Bonk Rocks EN', 'Big Bomb Shop WN'),
|
|
('Dark Bonk Rocks EC', 'Big Bomb Shop WC'),
|
|
('Dark Bonk Rocks ES', 'Big Bomb Shop WS'),
|
|
('Big Bomb Shop SC', 'Hype Cave NC'),
|
|
('Big Bomb Shop ES', 'Hammer Bridge WS'),
|
|
('Hammer Bridge SC', 'Ice Lake NW'),
|
|
('Hammer Bridge EN', 'Dark Tree Line WN'),
|
|
('Hammer Bridge EC', 'Dark Tree Line WC'),
|
|
('Dark Tree Line SC', 'Ice Lake NC'),
|
|
('Dark Tree Line SE', 'Ice Lake NE'),
|
|
('Swamp Nook EC', 'Swamp WC'),
|
|
('Swamp Nook ES', 'Swamp WS'),
|
|
('Swamp EC', 'Dark South Pass WC'),
|
|
('Hype Cave SC', 'Dark South Pass NC'),
|
|
('Dark South Pass ES', 'Ice Lake WS'),
|
|
('Ice Lake EC', 'Bomber Corner WC'),
|
|
('Ice Lake ES', 'Bomber Corner WS'),
|
|
('Bomber Corner NW', 'Shopping Mall SW'),
|
|
('Bomber Corner NE', 'Shopping Mall SE'),
|
|
('West Dark Death Mountain EN', 'East Dark Death Mountain WN'),
|
|
('West Dark Death Mountain ES', 'East Dark Death Mountain WS'),
|
|
('East Dark Death Mountain EN', 'Turtle Rock WN')
|
|
]
|
|
|
|
one_way_ledges = {
|
|
'West Death Mountain (Bottom)': {'West Death Mountain (Top)',
|
|
'Spectacle Rock Ledge'},
|
|
'East Death Mountain (Bottom)': {'East Death Mountain (Top East)',
|
|
'Spiral Cave Ledge'},
|
|
'Fairy Ascension Plateau': {'Fairy Ascension Ledge'},
|
|
'Mountain Pass Area': {'Mountain Pass Ledge'},
|
|
'Sanctuary Area': {'Bonk Rock Ledge'},
|
|
'Graveyard Area': {'Graveyard Ledge'},
|
|
'Potion Shop Water': {'Potion Shop Area',
|
|
'Potion Shop Northeast'},
|
|
'Zora Approach Water': {'Zora Approach Area'},
|
|
'Hyrule Castle Area': {'Hyrule Castle Ledge'},
|
|
'Hyrule Castle Courtyard': {'Hyrule Castle Ledge'},
|
|
'Wooden Bridge Water': {'Wooden Bridge Area',
|
|
'Wooden Bridge Northeast'},
|
|
'Maze Race Area': {'Maze Race Ledge',
|
|
'Maze Race Prize'},
|
|
'Flute Boy Approach Area': {'Cave 45 Ledge'},
|
|
'Desert Area': {'Desert Ledge',
|
|
'Desert Checkerboard Ledge',
|
|
'Desert Mouth',
|
|
'Bombos Tablet Ledge',
|
|
'Desert Teleporter Ledge'},
|
|
'Desert Pass Area': {'Desert Pass Ledge'},
|
|
'Lake Hylia Water': {'Lake Hylia South Shore',
|
|
'Lake Hylia Island'},
|
|
'West Dark Death Mountain (Bottom)': {'West Dark Death Mountain (Top)'},
|
|
'East Dark Death Mountain (Top)': {'Dark Death Mountain Floating Island'},
|
|
'East Dark Death Mountain (Bottom)': {'East Dark Death Mountain (Top)'},
|
|
'Turtle Rock Area': {'Turtle Rock Ledge'},
|
|
'Bumper Cave Area': {'Bumper Cave Ledge'},
|
|
'Qirn Jump Water': {'Qirn Jump Area'},
|
|
'Dark Witch Water': {'Dark Witch Area',
|
|
'Dark Witch Northeast'},
|
|
'Catfish Approach Water': {'Catfish Approach Area'},
|
|
'Pyramid Area': {'Pyramid Exit Ledge'},
|
|
'Broken Bridge Water': {'Broken Bridge West',
|
|
'Broken Bridge Area',
|
|
'Broken Bridge Northeast'},
|
|
'Mire Area': {'Mire Teleporter Ledge'},
|
|
'Ice Lake Water': {'Ice Lake Northwest Bank',
|
|
'Ice Lake Southwest Ledge',
|
|
'Ice Lake Southeast Ledge'}
|
|
}
|
|
|
|
isolated_regions = [
|
|
'Death Mountain Floating Island',
|
|
'Mimic Cave Ledge',
|
|
'Spiral Mimic Ledge Extend',
|
|
'Mountain Pass Ledge',
|
|
'Maze Race Prize',
|
|
'Maze Race Ledge',
|
|
'Desert Ledge',
|
|
'Desert Ledge Keep',
|
|
'Desert Mouth',
|
|
'Dark Death Mountain Floating Island',
|
|
'Dark Death Mountain Ledge',
|
|
'Dark Death Mountain Isolated Ledge',
|
|
'Bumper Cave Ledge',
|
|
'Pyramid Exit Ledge',
|
|
'Hyrule Castle Water',
|
|
'Pyramid Water'
|
|
]
|
|
|
|
flute_data = {
|
|
#OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
|
|
0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
|
|
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550),
|
|
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
|
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8),
|
|
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20),
|
|
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488),
|
|
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x02d0, 0x0f38),
|
|
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x0448, 0x0058),
|
|
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x0588, 0x0318),
|
|
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x0578, 0x04f8),
|
|
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x0508, 0x0758),
|
|
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x0580, 0x0928, 0x0580, 0x0948),
|
|
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x04f8, 0x0b60),
|
|
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x0598, 0x0ccf),
|
|
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x04e0, 0x0f68),
|
|
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x07c0, 0x0210, 0x07c8, 0x01f8),
|
|
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0548),
|
|
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07d8, 0x07f8),
|
|
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0720, 0x0a80),
|
|
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80),
|
|
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0908, 0x04d8),
|
|
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x08b8, 0x0b08),
|
|
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0b88, 0x00b8),
|
|
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0ae0, 0x02c0),
|
|
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b10, 0x04e8),
|
|
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b30, 0x0770),
|
|
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0b20, 0x08b8),
|
|
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0),
|
|
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58),
|
|
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30),
|
|
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070),
|
|
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0cd6, 0x0568),
|
|
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0c80, 0x0628),
|
|
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0d60, 0x08d8),
|
|
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88),
|
|
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0f90, 0x0da4),
|
|
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d48, 0x0ed0),
|
|
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0e70, 0x0540),
|
|
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0f30, 0x0770),
|
|
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0f38, 0x0898),
|
|
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0f80, 0x0ef0)
|
|
}
|
|
|
|
tile_swap_spoiler_table = \
|
|
""" 0 1 2 3 4 5 6 7
|
|
+---+-+---+---+-+
|
|
01234567 A(00)| |s| | |s|
|
|
+--------+ | s +-+ s | s +-+
|
|
A(00)|s ss s s| B(08)| |s| | |s|
|
|
B(08)| s s| +-+-+-+-+-+-+-+-+
|
|
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
|
|
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
|
|
E(20)| s s | D(18)| |s| |s| |
|
|
F(28)|ssssssss| | s +-+ s +-+ s |
|
|
G(30)|s ssss s| E(20)| |s| |s| |
|
|
H(38)| sss s| +-+-+-+-+-+-+-+-+
|
|
+--------+ F(28)|s|s|s|s|s|s|s|s|
|
|
+-+ +-+-+-+-+-+-+-+-+
|
|
Ped/Hobo: |s| G(30)| |s|s|s| |s|
|
|
+-+ | s +-+-+-+ s +-+
|
|
Zora: |s| H(38)| |s|s|s| |s|
|
|
+-+ +---+-+-+-+---+-+"""
|
|
|
|
flute_spoiler_table = \
|
|
""" 0 1 2 3 4 5 6 7
|
|
+---+-+---+---+-+
|
|
01234567 A(00)| |s| | |s|
|
|
+--------+ | s +-+ s | s +-+
|
|
A(00)|s ss s s| B(08)| |s| | |s|
|
|
B(08)| s s| +-+-+-+-+-+-+-+-+
|
|
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
|
|
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
|
|
E(20)| s s | D(18)| |s| |s| |
|
|
F(28)|ssssssss| | s +-+ s +-+ s |
|
|
G(30)|s ssss s| E(20)| |s| |s| |
|
|
H(38)| sss s| +-+-+-+-+-+-+-+-+
|
|
+--------+ F(28)|s|s|s|s|s|s|s|s|
|
|
+-+-+-+-+-+-+-+-+
|
|
G(30)| |s|s|s| |s|
|
|
| s +-+-+-+ s +-+
|
|
H(38)| |s|s|s| |s|
|
|
+---+-+-+-+---+-+"""
|