feat: swapped ER

This commit is contained in:
aerinon
2023-12-19 16:31:29 -07:00
parent 78713a633e
commit 4b888b3c48
15 changed files with 316 additions and 67 deletions

View File

@@ -112,7 +112,7 @@ class World(object):
set_player_attr('can_access_trock_big_chest', None) set_player_attr('can_access_trock_big_chest', None)
set_player_attr('can_access_trock_middle', None) set_player_attr('can_access_trock_middle', None)
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic']
or shuffle[player] in ['lean', 'crossed', 'insanity']) or shuffle[player] in ['lean', 'swapped', 'crossed', 'insanity'])
set_player_attr('mapshuffle', False) set_player_attr('mapshuffle', False)
set_player_attr('compassshuffle', False) set_player_attr('compassshuffle', False)
set_player_attr('keyshuffle', 'none') set_player_attr('keyshuffle', 'none')
@@ -2970,7 +2970,7 @@ class Pot(object):
# byte 0: DDDE EEEE (DR, ER) # byte 0: DDDE EEEE (DR, ER)
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4} dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4}
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8, er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6} 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, 'swapped': 10}
# byte 1: LLLW WSS? (logic, mode, sword) # byte 1: LLLW WSS? (logic, mode, sword)
logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4}

View File

@@ -1506,7 +1506,7 @@ def create_doors(world, player):
# static portal flags # static portal flags
world.get_door('Sanctuary S', player).dead_end(allowPassage=True) world.get_door('Sanctuary S', player).dead_end(allowPassage=True)
if world.mode[player] == 'open' and world.shuffle[player] not in ['crossed', 'insanity']: if world.mode[player] == 'open' and world.shuffle[player] not in ['lean', 'swapped', 'crossed', 'insanity']:
world.get_door('Sanctuary S', player).lw_restricted = True world.get_door('Sanctuary S', player).lw_restricted = True
world.get_door('Eastern Hint Tile Blocked Path SE', player).passage = False world.get_door('Eastern Hint Tile Blocked Path SE', player).passage = False
world.get_door('TR Big Chest Entrance SE', player).passage = False world.get_door('TR Big Chest Entrance SE', player).passage = False

View File

@@ -1360,7 +1360,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
calc_allowance_and_dead_ends(builder, connections_tuple, world, player) calc_allowance_and_dead_ends(builder, connections_tuple, world, player)
if world.mode[player] == 'open' and world.shuffle[player] not in ['crossed', 'insanity']: if world.mode[player] == 'open' and world.shuffle[player] not in ['lean', 'swapped', 'crossed', 'insanity']:
sanc = find_sector('Sanctuary', candidate_sectors) sanc = find_sector('Sanctuary', candidate_sectors)
if sanc: # only run if sanc if a candidate if sanc: # only run if sanc if a candidate
lw_builders = [] lw_builders = []

View File

@@ -809,7 +809,7 @@ def balance_prices(world, player):
def check_hints(world, player): def check_hints(world, player):
if world.shuffle[player] in ['simple', 'restricted', 'full', 'crossed', 'insanity']: if world.shuffle[player] in ['simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity']:
for shop, location_list in shop_to_location_table.items(): for shop, location_list in shop_to_location_table.items():
if shop in ['Capacity Upgrade', 'Paradox Shop', 'Potion Shop']: if shop in ['Capacity Upgrade', 'Paradox Shop', 'Potion Shop']:
continue # near the queen, near potions, and near 7 chests are fine continue # near the queen, near potions, and near 7 chests are fine

View File

@@ -241,7 +241,7 @@ def main(args, seed=None, fish=None):
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
create_dynamic_exits(world, player) create_dynamic_exits(world, player)
if world.experimental[player] or world.shuffle[player] in ['lite', 'lean'] or world.shuffletavern[player] or (world.customizer and world.customizer.get_entrances()): if world.experimental[player] or world.shuffle[player] in ['lite', 'lean', 'swapped'] or world.shuffletavern[player] or (world.customizer and world.customizer.get_entrances()):
link_entrances_new(world, player) link_entrances_new(world, player)
else: else:
link_entrances(world, player) link_entrances(world, player)

10
Rom.py
View File

@@ -1358,7 +1358,7 @@ def patch_rom(world, rom, player, team, is_mystery=False):
rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
# allow smith into multi-entrance caves in appropriate shuffles # allow smith into multi-entrance caves in appropriate shuffles
if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'):
rom.write_byte(0x18004C, 0x01) rom.write_byte(0x18004C, 0x01)
# set correct flag for hera basement item # set correct flag for hera basement item
@@ -1885,7 +1885,7 @@ def write_strings(rom, world, player, team):
break break
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
entrances_to_hint.update(InconvenientOtherEntrances) entrances_to_hint.update(InconvenientOtherEntrances)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'swapped']:
hint_count = 0 hint_count = 0
elif world.shuffle[player] in ['simple', 'restricted']: elif world.shuffle[player] in ['simple', 'restricted']:
hint_count = 2 hint_count = 2
@@ -1935,7 +1935,7 @@ def write_strings(rom, world, player, team):
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else: else:
entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0 hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 0
hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0 hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0
for entrance in all_entrances: for entrance in all_entrances:
if entrance.name in entrances_to_hint: if entrance.name in entrances_to_hint:
@@ -1954,7 +1954,7 @@ def write_strings(rom, world, player, team):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
locations_to_hint.extend(InconvenientVanillaLocations) locations_to_hint.extend(InconvenientVanillaLocations)
random.shuffle(locations_to_hint) random.shuffle(locations_to_hint)
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5 hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 5
hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
del locations_to_hint[hint_count:] del locations_to_hint[hint_count:]
for location in locations_to_hint: for location in locations_to_hint:
@@ -2017,7 +2017,7 @@ def write_strings(rom, world, player, team):
if world.bigkeyshuffle[player]: if world.bigkeyshuffle[player]:
items_to_hint.extend(BigKeys) items_to_hint.extend(BigKeys)
random.shuffle(items_to_hint) random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 8
hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
while hint_count > 0 and len(items_to_hint) > 0: while hint_count > 0 and len(items_to_hint) > 0:
this_item = items_to_hint.pop(0) this_item = items_to_hint.pop(0)

View File

@@ -49,6 +49,9 @@ def main(args=None):
test("Shopsanity", "--shuffle vanilla --shopsanity") test("Shopsanity", "--shuffle vanilla --shopsanity")
test("Simple ", "--shuffle simple") test("Simple ", "--shuffle simple")
test("Full ", "--shuffle full") test("Full ", "--shuffle full")
test("Lite ", "--shuffle lite")
test("Lean ", "--shuffle lean")
test("Swapped ", "--shuffle swapped")
test("Crossed ", "--shuffle crossed") test("Crossed ", "--shuffle crossed")
test("Insanity ", "--shuffle insanity") test("Insanity ", "--shuffle insanity")
test("OWG ", "--logic owglitches") test("OWG ", "--logic owglitches")

View File

@@ -72,6 +72,9 @@
simple: 2 simple: 2
restricted: 2 restricted: 2
full: 2 full: 2
lite: 2
lean: 2
swapped: 2
crossed: 3 crossed: 3
insanity: 1 insanity: 1
open_pyramid: open_pyramid:

View File

@@ -60,6 +60,9 @@ entrance_shuffle:
simple: 1 simple: 1
restricted: 1 restricted: 1
full: 1 full: 1
lite: 1
lean: 1
swapped: 1
crossed: 1 crossed: 1
insanity: 1 insanity: 1
shufflelinks: shufflelinks:

View File

@@ -167,6 +167,7 @@
"simple", "simple",
"restricted", "restricted",
"full", "full",
"swapped",
"crossed", "crossed",
"insanity", "insanity",
"dungeonsfull", "dungeonsfull",

View File

@@ -206,6 +206,7 @@
" connect remaining entrances.", " connect remaining entrances.",
"Crossed: Mix cave and dungeon entrances freely while allowing", "Crossed: Mix cave and dungeon entrances freely while allowing",
" caves to cross between worlds.", " caves to cross between worlds.",
"Swapped: Same as Crossed, but entrances switch places in pairs.",
"Insanity: Decouple entrances and exits from each other and", "Insanity: Decouple entrances and exits from each other and",
" shuffle them freely. Caves that used to be single", " shuffle them freely. Caves that used to be single",
" entrance will still exit to the same location from", " entrance will still exit to the same location from",

View File

@@ -156,6 +156,7 @@
"randomizer.entrance.entranceshuffle.simple": "Simple", "randomizer.entrance.entranceshuffle.simple": "Simple",
"randomizer.entrance.entranceshuffle.restricted": "Restricted", "randomizer.entrance.entranceshuffle.restricted": "Restricted",
"randomizer.entrance.entranceshuffle.full": "Full", "randomizer.entrance.entranceshuffle.full": "Full",
"randomizer.entrance.entranceshuffle.swapped": "Swapped",
"randomizer.entrance.entranceshuffle.crossed": "Crossed", "randomizer.entrance.entranceshuffle.crossed": "Crossed",
"randomizer.entrance.entranceshuffle.insanity": "Insanity", "randomizer.entrance.entranceshuffle.insanity": "Insanity",
"randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full", "randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full",

View File

@@ -9,6 +9,7 @@
"full", "full",
"lite", "lite",
"lean", "lean",
"swapped",
"crossed", "crossed",
"insanity", "insanity",
"dungeonsfull", "dungeonsfull",

View File

@@ -11,6 +11,7 @@ class EntrancePool(object):
self.exits = set() self.exits = set()
self.inverted = False self.inverted = False
self.coupled = True self.coupled = True
self.swapped = False
self.default_map = {} self.default_map = {}
self.one_way_map = {} self.one_way_map = {}
self.skull_handled = False self.skull_handled = False
@@ -92,6 +93,7 @@ def link_entrances_new(world, player):
if mode not in modes: if mode not in modes:
raise RuntimeError(f'Shuffle mode {mode} is not yet supported') raise RuntimeError(f'Shuffle mode {mode} is not yet supported')
mode_cfg = copy.deepcopy(modes[mode]) mode_cfg = copy.deepcopy(modes[mode])
avail_pool.swapped = mode_cfg['undefined'] == 'swap'
if avail_pool.is_standard(): if avail_pool.is_standard():
do_standard_connections(avail_pool) do_standard_connections(avail_pool)
pool_list = mode_cfg['pools'] if 'pools' in mode_cfg else {} pool_list = mode_cfg['pools'] if 'pools' in mode_cfg else {}
@@ -99,7 +101,10 @@ def link_entrances_new(world, player):
special_shuffle = pool['special'] if 'special' in pool else None special_shuffle = pool['special'] if 'special' in pool else None
if special_shuffle == 'drops': if special_shuffle == 'drops':
holes, targets = find_entrances_and_targets_drops(avail_pool, pool['entrances']) holes, targets = find_entrances_and_targets_drops(avail_pool, pool['entrances'])
connect_random(holes, targets, avail_pool) if avail_pool.swapped:
connect_swapped(holes, targets, avail_pool)
else:
connect_random(holes, targets, avail_pool)
elif special_shuffle == 'fixed_shuffle': elif special_shuffle == 'fixed_shuffle':
do_fixed_shuffle(avail_pool, pool['entrances']) do_fixed_shuffle(avail_pool, pool['entrances'])
elif special_shuffle == 'same_world': elif special_shuffle == 'same_world':
@@ -123,7 +128,10 @@ def link_entrances_new(world, player):
do_vanilla_connect(pool, avail_pool) do_vanilla_connect(pool, avail_pool)
elif special_shuffle == 'skull': elif special_shuffle == 'skull':
entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances'])
connect_random(entrances, exits, avail_pool, True) if avail_pool.swapped:
connect_swapped(entrances, exits, avail_pool, True)
else:
connect_random(entrances, exits, avail_pool, True)
avail_pool.skull_handled = True avail_pool.skull_handled = True
else: else:
entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances'])
@@ -131,7 +139,7 @@ def link_entrances_new(world, player):
undefined_behavior = mode_cfg['undefined'] undefined_behavior = mode_cfg['undefined']
if undefined_behavior == 'vanilla': if undefined_behavior == 'vanilla':
do_vanilla_connections(avail_pool) do_vanilla_connections(avail_pool)
elif undefined_behavior == 'shuffle': elif undefined_behavior in {'shuffle', 'swap'}:
do_main_shuffle(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, mode_cfg) do_main_shuffle(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, mode_cfg)
# afterward # afterward
@@ -186,6 +194,10 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
if not avail.coupled: if not avail.coupled:
avail.decoupled_entrances.remove('Agahnims Tower') avail.decoupled_entrances.remove('Agahnims Tower')
avail.decoupled_exits.remove('Ganons Tower Exit') avail.decoupled_exits.remove('Ganons Tower Exit')
if avail.swapped:
connect_swap('Agahnims Tower', 'Ganons Tower Exit', avail)
entrances.remove('Ganons Tower')
exits.remove('Agahnims Tower Exit')
elif 'Ganons Tower' in entrances: elif 'Ganons Tower' in entrances:
connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail) connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail)
entrances.remove('Ganons Tower') entrances.remove('Ganons Tower')
@@ -207,7 +219,13 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
# inverted sanc # inverted sanc
if avail.inverted and 'Dark Sanctuary Hint' in exits: if avail.inverted and 'Dark Sanctuary Hint' in exits:
choices = [e for e in Inverted_Dark_Sanctuary_Doors if e in entrances] forbidden = set()
if avail.swapped:
forbidden.add('Dark Sanctuary Hint')
forbidden.update(Forbidden_Swap_Entrances)
if not avail.inverted:
forbidden.append('Links House')
choices = [e for e in Inverted_Dark_Sanctuary_Doors if e in entrances and e not in forbidden]
choice = random.choice(choices) choice = random.choice(choices)
entrances.remove(choice) entrances.remove(choice)
exits.remove('Dark Sanctuary Hint') exits.remove('Dark Sanctuary Hint')
@@ -216,6 +234,10 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
ext.connect(avail.world.get_entrance(choice, avail.player).parent_region) ext.connect(avail.world.get_entrance(choice, avail.player).parent_region)
if not avail.coupled: if not avail.coupled:
avail.decoupled_entrances.remove(choice) avail.decoupled_entrances.remove(choice)
if avail.swapped and choice != 'Dark Sanctuary Hint':
swap_ent, swap_ext = connect_swap(choice, 'Dark Sanctuary Hint', avail)
entrances.remove(swap_ent)
exits.remove(swap_ext)
# mandatory exits # mandatory exits
rem_entrances, rem_exits = set(), set() rem_entrances, rem_exits = set(), set()
@@ -241,12 +263,19 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
else: else:
# cross world mandantory # cross world mandantory
entrance_list = list(entrances) entrance_list = list(entrances)
if avail.swapped:
forbidden = [e for e in Forbidden_Swap_Entrances if e in entrance_list]
entrance_list = [e for e in entrance_list if e not in forbidden]
must_exit, multi_exit_caves = figure_out_must_exits_cross_world(entrances, exits, avail) must_exit, multi_exit_caves = figure_out_must_exits_cross_world(entrances, exits, avail)
do_mandatory_connections(avail, entrance_list, multi_exit_caves, must_exit) do_mandatory_connections(avail, entrance_list, multi_exit_caves, must_exit)
rem_entrances.update(entrance_list) rem_entrances.update(entrance_list)
if avail.swapped:
rem_entrances.update(forbidden)
rem_exits.update([x for item in multi_exit_caves for x in item]) rem_exits.update([x for item in multi_exit_caves for x in item])
rem_exits.update(exits) rem_exits.update(exits)
if avail.swapped:
rem_exits = [x for x in rem_exits if x in avail.exits]
# old man cave # old man cave
do_old_man_cave_exit(rem_entrances, rem_exits, avail, cross_world) do_old_man_cave_exit(rem_entrances, rem_exits, avail, cross_world)
@@ -254,9 +283,15 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
# blacksmith # blacksmith
if 'Blacksmiths Hut' in rem_exits: if 'Blacksmiths Hut' in rem_exits:
blacksmith_options = [x for x in Blacksmith_Options if x in rem_entrances] blacksmith_options = [x for x in Blacksmith_Options if x in rem_entrances]
if avail.swapped:
blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances]
blacksmith_choice = random.choice(blacksmith_options) blacksmith_choice = random.choice(blacksmith_options)
connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail) connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail)
rem_entrances.remove(blacksmith_choice) rem_entrances.remove(blacksmith_choice)
if avail.swapped and blacksmith_choice != 'Blacksmiths Hut':
swap_ent, swap_ext = connect_swap(blacksmith_choice, 'Blacksmiths Hut', avail)
rem_entrances.remove(swap_ent)
rem_exits.remove(swap_ext)
if not avail.coupled: if not avail.coupled:
avail.decoupled_exits.remove('Blacksmiths Hut') avail.decoupled_exits.remove('Blacksmiths Hut')
rem_exits.remove('Blacksmiths Hut') rem_exits.remove('Blacksmiths Hut')
@@ -266,9 +301,15 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
if bomb_shop in rem_exits: if bomb_shop in rem_exits:
bomb_shop_options = Inverted_Bomb_Shop_Options if avail.inverted else Bomb_Shop_Options bomb_shop_options = Inverted_Bomb_Shop_Options if avail.inverted else Bomb_Shop_Options
bomb_shop_options = [x for x in bomb_shop_options if x in rem_entrances] bomb_shop_options = [x for x in bomb_shop_options if x in rem_entrances]
if avail.swapped and len(bomb_shop_options) > 1:
bomb_shop_options = [x for x in bomb_shop_options if x != 'Big Bomb Shop']
bomb_shop_choice = random.choice(bomb_shop_options) bomb_shop_choice = random.choice(bomb_shop_options)
connect_entrance(bomb_shop_choice, bomb_shop, avail) connect_entrance(bomb_shop_choice, bomb_shop, avail)
rem_entrances.remove(bomb_shop_choice) rem_entrances.remove(bomb_shop_choice)
if avail.swapped and bomb_shop_choice != 'Big Bomb Shop':
swap_ent, swap_ext = connect_swap(bomb_shop_choice, bomb_shop, avail)
rem_exits.remove(swap_ext)
rem_entrances.remove(swap_ent)
if not avail.coupled: if not avail.coupled:
avail.decoupled_exits.remove(bomb_shop) avail.decoupled_exits.remove(bomb_shop)
rem_exits.remove(bomb_shop) rem_exits.remove(bomb_shop)
@@ -326,12 +367,17 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
rem_entrances = list(unused_entrances) rem_entrances = list(unused_entrances)
rem_entrances.sort() rem_entrances.sort()
rem_exits = list(rem_exits if avail.coupled else avail.decoupled_exits) rem_exits = list(rem_exits if avail.coupled else avail.decoupled_exits)
if avail.swapped:
rem_exits = [x for x in rem_exits if x in avail.exits]
rem_exits.sort() rem_exits.sort()
random.shuffle(rem_entrances) random.shuffle(rem_entrances)
random.shuffle(rem_exits) random.shuffle(rem_exits)
placing = min(len(rem_entrances), len(rem_exits)) placing = min(len(rem_entrances), len(rem_exits))
for door, target in zip(rem_entrances, rem_exits): if avail.swapped:
connect_entrance(door, target, avail) connect_swapped(rem_entrances, rem_exits, avail)
else:
for door, target in zip(rem_entrances, rem_exits):
connect_entrance(door, target, avail)
rem_entrances[:] = rem_entrances[placing:] rem_entrances[:] = rem_entrances[placing:]
rem_exits[:] = rem_exits[placing:] rem_exits[:] = rem_exits[placing:]
if rem_entrances or rem_exits: if rem_entrances or rem_exits:
@@ -344,6 +390,8 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world):
if avail.inverted and cross_world: if avail.inverted and cross_world:
om_cave_options = Inverted_Old_Man_Entrances + Old_Man_Entrances om_cave_options = Inverted_Old_Man_Entrances + Old_Man_Entrances
om_cave_options = [x for x in om_cave_options if x in entrances] om_cave_options = [x for x in om_cave_options if x in entrances]
if avail.swapped:
om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances]
om_cave_choice = random.choice(om_cave_options) om_cave_choice = random.choice(om_cave_options)
if not avail.coupled: if not avail.coupled:
connect_exit('Old Man Cave Exit (East)', om_cave_choice, avail) connect_exit('Old Man Cave Exit (East)', om_cave_choice, avail)
@@ -351,6 +399,10 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world):
else: else:
connect_two_way(om_cave_choice, 'Old Man Cave Exit (East)', avail) connect_two_way(om_cave_choice, 'Old Man Cave Exit (East)', avail)
entrances.remove(om_cave_choice) entrances.remove(om_cave_choice)
if avail.swapped and om_cave_choice != 'Old Man Cave (East)':
swap_ent, swap_ext = connect_swap(om_cave_choice, 'Old Man Cave Exit (East)', avail)
entrances.remove(swap_ent)
exits.remove(swap_ext)
exits.remove('Old Man Cave Exit (East)') exits.remove('Old Man Cave Exit (East)')
@@ -405,32 +457,74 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe
random.shuffle(hole_entrances) random.shuffle(hole_entrances)
if not cross_world and 'Sanctuary Grave' in holes_to_shuffle: if not cross_world and 'Sanctuary Grave' in holes_to_shuffle:
lw_entrance = next(entrance for entrance in hole_entrances if entrance[0] in LW_Entrances) hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player)
hole_entrances.remove(lw_entrance) is_hc_in_dw = avail.world.mode[avail.player] == 'inverted'
sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit') if hc.connected_region:
hole_targets.remove(sanc_interior) is_hc_in_dw = hc.connected_region.type == RegionType.DarkWorld
connect_two_way(lw_entrance[0], sanc_interior[0], avail) # two-way exit chosen_entrance = None
connect_entrance(lw_entrance[1], sanc_interior[1], avail) # hole if is_hc_in_dw:
remove_from_list(entrances, [lw_entrance[0], lw_entrance[1]]) if avail.swapped:
remove_from_list(exits, [sanc_interior[0], sanc_interior[1]]) chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances and e[0] != 'Sanctuary')
if not chosen_entrance:
chosen_entrance = next(e for e in hole_entrances if e[0] in DW_Entrances)
if not chosen_entrance:
if avail.swapped:
chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances and e[0] != 'Sanctuary')
if not chosen_entrance:
chosen_entrance = next(e for e in hole_entrances if e[0] in LW_Entrances)
if chosen_entrance:
hole_entrances.remove(chosen_entrance)
sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit')
hole_targets.remove(sanc_interior)
connect_two_way(chosen_entrance[0], sanc_interior[0], avail) # two-way exit
connect_entrance(chosen_entrance[1], sanc_interior[1], avail) # hole
remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]])
remove_from_list(exits, [sanc_interior[0], sanc_interior[1]])
if avail.swapped and drop_map[chosen_entrance[1]] != sanc_interior[1]:
swap_ent, swap_ext = connect_swap(chosen_entrance[0], sanc_interior[0], avail)
swap_drop, swap_tgt = connect_swap(chosen_entrance[1], sanc_interior[1], avail)
hole_entrances.remove((swap_ent, swap_drop))
hole_targets.remove((swap_ext, swap_tgt))
remove_from_list(entrances, [swap_ent, swap_drop])
remove_from_list(exits, [swap_ext, swap_tgt])
random.shuffle(hole_targets) random.shuffle(hole_targets)
for entrance, drop in hole_entrances: while len(hole_entrances):
ext, target = hole_targets.pop() entrance, drop = hole_entrances.pop()
if avail.swapped and len(hole_targets) > 1:
ext, target = next((x, t) for x, t in hole_targets if x != entrance_map[entrance])
hole_targets.remove((ext, target))
else:
ext, target = hole_targets.pop()
connect_two_way(entrance, ext, avail) connect_two_way(entrance, ext, avail)
connect_entrance(drop, target, avail) connect_entrance(drop, target, avail)
remove_from_list(entrances, [entrance, drop]) remove_from_list(entrances, [entrance, drop])
remove_from_list(exits, [ext, target]) remove_from_list(exits, [ext, target])
if avail.swapped and drop_map[drop] != target:
swap_ent, swap_ext = connect_swap(entrance, ext, avail)
swap_drop, swap_tgt = connect_swap(drop, target, avail)
hole_entrances.remove((swap_ent, swap_drop))
hole_targets.remove((swap_ext, swap_tgt))
remove_from_list(entrances, [swap_ent, swap_drop])
remove_from_list(exits, [swap_ext, swap_tgt])
def do_links_house(entrances, exits, avail, cross_world): def do_links_house(entrances, exits, avail, cross_world):
lh_exit = 'Links House Exit' lh_exit = 'Links House Exit'
if lh_exit in exits: if lh_exit in exits:
links_house_vanilla = 'Big Bomb Shop' if avail.inverted else 'Links House'
if not avail.world.shufflelinks[avail.player]: if not avail.world.shufflelinks[avail.player]:
links_house = 'Big Bomb Shop' if avail.inverted else 'Links House' links_house = links_house_vanilla
else: else:
forbidden = list((Isolated_LH_Doors_Inv + Inverted_Dark_Sanctuary_Doors) forbidden = list((Isolated_LH_Doors_Inv + Inverted_Dark_Sanctuary_Doors)
if avail.inverted else Isolated_LH_Doors_Open) if avail.inverted else Isolated_LH_Doors_Open)
if not avail.inverted:
if avail.world.doorShuffle[avail.player] != 'vanilla' and avail.world.intensity[avail.player] > 2:
forbidden.append('Hyrule Castle Entrance (South)')
if avail.swapped:
forbidden.append(links_house_vanilla)
forbidden.extend(Forbidden_Swap_Entrances)
shuffle_mode = avail.world.shuffle[avail.player] shuffle_mode = avail.world.shuffle[avail.player]
# simple shuffle - # simple shuffle -
if shuffle_mode == 'simple': if shuffle_mode == 'simple':
@@ -471,6 +565,11 @@ def do_links_house(entrances, exits, avail, cross_world):
avail.decoupled_entrances.remove(links_house) avail.decoupled_entrances.remove(links_house)
avail.decoupled_exits.remove('Links House Exit') avail.decoupled_exits.remove('Links House Exit')
avail.decoupled_exits.remove('Chris Houlihan Room Exit') avail.decoupled_exits.remove('Chris Houlihan Room Exit')
if avail.swapped and links_house != links_house_vanilla:
swap_ent, swap_ext = connect_swap(links_house, lh_exit, avail)
entrances.remove(swap_ent)
exits.remove(swap_ext)
# links on dm # links on dm
dm_spots = LH_DM_Connector_List.union(LH_DM_Exit_Forbidden) dm_spots = LH_DM_Connector_List.union(LH_DM_Exit_Forbidden)
if links_house in dm_spots: if links_house in dm_spots:
@@ -491,20 +590,20 @@ def do_links_house(entrances, exits, avail, cross_world):
possible_exits.sort() possible_exits.sort()
chosen_dm_escape = random.choice(possible_dm_exits) chosen_dm_escape = random.choice(possible_dm_exits)
chosen_landing = random.choice(possible_exits) chosen_landing = random.choice(possible_exits)
chosen_exit_start = chosen_cave.pop(0)
chosen_exit_end = chosen_cave.pop()
if avail.coupled: if avail.coupled:
connect_two_way(chosen_dm_escape, chosen_cave.pop(0), avail) connect_two_way(chosen_dm_escape, chosen_exit_start, avail)
connect_two_way(chosen_landing, chosen_cave.pop(), avail) connect_two_way(chosen_landing, chosen_exit_end, avail)
entrances.remove(chosen_dm_escape) entrances.remove(chosen_dm_escape)
entrances.remove(chosen_landing) entrances.remove(chosen_landing)
else: else:
chosen_cave_first = chosen_cave.pop(0) connect_entrance(chosen_dm_escape, chosen_exit_start, avail)
connect_entrance(chosen_dm_escape, chosen_cave_first, avail) connect_exit(chosen_exit_end, chosen_landing, avail)
connect_exit(chosen_cave.pop(), chosen_landing, avail)
entrances.remove(chosen_dm_escape) entrances.remove(chosen_dm_escape)
avail.decoupled_exits.remove(chosen_cave_first) avail.decoupled_exits.remove(chosen_exit_start)
avail.decoupled_entrances.remove(chosen_landing) avail.decoupled_entrances.remove(chosen_landing)
# chosen cave has already been removed from exits exits.add(chosen_exit_start) # this needs to be added back in
exits.add(chosen_cave_first) # this needs to be added back in
if len(chosen_cave): if len(chosen_cave):
exits.update([x for x in chosen_cave]) exits.update([x for x in chosen_cave])
exits.update([x for item in multi_exit_caves for x in item]) exits.update([x for item in multi_exit_caves for x in item])
@@ -626,21 +725,44 @@ def do_cross_world_connectors(entrances, caves, avail):
cave_candidate = (None, 0) cave_candidate = (None, 0)
for i, cave in enumerate(caves): for i, cave in enumerate(caves):
if isinstance(cave, str): if isinstance(cave, str):
cave = (cave,) cave = [cave]
if len(cave) > cave_candidate[1]: if len(cave) > cave_candidate[1]:
cave_candidate = (i, len(cave)) cave_candidate = (i, len(cave))
cave = caves.pop(cave_candidate[0]) cave = caves.pop(cave_candidate[0])
if isinstance(cave, str): if isinstance(cave, str):
cave = (cave,) cave = [cave]
for ext in cave: while len(cave):
ext = cave.pop()
if not avail.coupled: if not avail.coupled:
choice = random.choice(avail.decoupled_entrances) choice = random.choice(avail.decoupled_entrances)
connect_exit(ext, choice, avail) connect_exit(ext, choice, avail)
avail.decoupled_entrances.remove(choice) avail.decoupled_entrances.remove(choice)
else: else:
connect_two_way(entrances.pop(), ext, avail) if avail.swapped and len(entrances) > 1:
chosen_entrance = next(e for e in entrances if combine_map[e] != ext)
entrances.remove(chosen_entrance)
else:
chosen_entrance = entrances.pop()
connect_two_way(chosen_entrance, ext, avail)
if avail.swapped:
swap_ent, swap_ext = connect_swap(chosen_entrance, ext, avail)
if swap_ent:
entrances.remove(swap_ent)
if chosen_entrance not in single_entrance_map:
if swap_ext in cave:
cave.remove(swap_ext)
else:
for c in caves:
if swap_ext == c:
caves.remove(swap_ext)
break
if not isinstance(c, str) and swap_ext in c:
c.remove(swap_ext)
if len(c) == 0:
caves.remove(c)
break
def do_fixed_shuffle(avail, entrance_list): def do_fixed_shuffle(avail, entrance_list):
@@ -841,7 +963,12 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
invalid_connections[entrance] = set() invalid_connections[entrance] = set()
if entrance in must_exit: if entrance in must_exit:
must_exit.remove(entrance) must_exit.remove(entrance)
entrances.append(entrance) if entrance not in entrances:
entrances.append(entrance)
if avail.swapped:
swap_forbidden = [e for e in entrances if combine_map[e] in must_exit]
for e in swap_forbidden:
entrances.remove(e)
entrances.sort() # sort these for consistency entrances.sort() # sort these for consistency
random.shuffle(entrances) random.shuffle(entrances)
random.shuffle(cave_options) random.shuffle(cave_options)
@@ -854,6 +981,19 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
invalid_connections[ext] = invalid_connections[ext].union({'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'}) invalid_connections[ext] = invalid_connections[ext].union({'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'})
break break
def connect_cave_swap(entrance, exit, current_cave):
swap_entrance, swap_exit = connect_swap(entrance, exit, avail)
if swap_entrance and entrance not in single_entrance_map:
for option in cave_options:
if swap_exit in option and option == current_cave:
x=0
if swap_exit in option and option != current_cave:
option.remove(swap_exit)
if len(option) == 0:
cave_options.remove(option)
break
return swap_entrance, swap_exit
used_caves = [] used_caves = []
required_entrances = 0 # Number of entrances reserved for used_caves required_entrances = 0 # Number of entrances reserved for used_caves
while must_exit: while must_exit:
@@ -861,9 +1001,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
# find multi exit cave # find multi exit cave
candidates = [] candidates = []
for candidate in cave_options: for candidate in cave_options:
if not isinstance(candidate, str) and (candidate in used_caves if not isinstance(candidate, str) and len(candidate) > 1 and (candidate in used_caves
or len(candidate) < len(entrances) - required_entrances): or len(candidate) < len(entrances) - required_entrances):
candidates.append(candidate) if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped
candidates.append(candidate)
cave = random.choice(candidates) cave = random.choice(candidates)
if cave is None: if cave is None:
raise RuntimeError('No more caves left. Should not happen!') raise RuntimeError('No more caves left. Should not happen!')
@@ -871,13 +1012,23 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
# all caves are sorted so that the last exit is always reachable # all caves are sorted so that the last exit is always reachable
rnd_cave = list(cave) rnd_cave = list(cave)
shuffle_connector_exits(rnd_cave) # should be the same as unbiasing some entrances... shuffle_connector_exits(rnd_cave) # should be the same as unbiasing some entrances...
entrances.remove(exit) if avail.swapped and exit in swap_forbidden:
swap_forbidden.remove(exit)
else:
entrances.remove(exit)
connect_two_way(exit, rnd_cave[-1], avail) connect_two_way(exit, rnd_cave[-1], avail)
if avail.swapped:
swap_ent, _ = connect_cave_swap(exit, rnd_cave[-1], cave)
entrances.remove(swap_ent)
if len(cave) == 2: if len(cave) == 2:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]
and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit) and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit
and (not avail.swapped or rnd_cave[0] != combine_map[e]))
entrances.remove(entrance) entrances.remove(entrance)
connect_two_way(entrance, rnd_cave[0], avail) connect_two_way(entrance, rnd_cave[0], avail)
if avail.swapped and combine_map[entrance] != rnd_cave[0]:
swap_ent, _ = connect_cave_swap(entrance, rnd_cave[0], cave)
entrances.remove(swap_ent)
if cave in used_caves: if cave in used_caves:
required_entrances -= 2 required_entrances -= 2
used_caves.remove(cave) used_caves.remove(cave)
@@ -887,10 +1038,18 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit
cave_entrances = [] cave_entrances = []
for cave_exit in rnd_cave[:-1]: for cave_exit in rnd_cave[:-1]:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit) if avail.swapped and cave_exit not in avail.exits:
cave_entrances.append(entrance) entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name
entrances.remove(entrance) cave_entrances.append(entrance)
connect_two_way(entrance, cave_exit, avail) else:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit
and (not avail.swapped or cave_exit != combine_map[e]))
cave_entrances.append(entrance)
entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail)
if avail.swapped and combine_map[entrance] != cave_exit:
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
entrances.remove(swap_ent)
if entrance not in invalid_connections: if entrance not in invalid_connections:
invalid_connections[exit] = set() invalid_connections[exit] = set()
if all(entrance in invalid_connections for entrance in cave_entrances): if all(entrance in invalid_connections for entrance in cave_entrances):
@@ -911,11 +1070,20 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
for cave in used_caves: for cave in used_caves:
if cave in cave_options: # check if we placed multiple entrances from this 3 or 4 exit if cave in cave_options: # check if we placed multiple entrances from this 3 or 4 exit
for cave_exit in cave: for cave_exit in cave:
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]) if avail.swapped and cave_exit not in avail.exits:
invalid_cave_connections[tuple(cave)] = set() continue
entrances.remove(entrance) else:
connect_two_way(entrance, cave_exit, avail) entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]
and (not avail.swapped or cave_exit != combine_map[e]))
invalid_cave_connections[tuple(cave)] = set()
entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail)
if avail.swapped and combine_map[entrance] != cave_exit:
swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave)
entrances.remove(swap_ent)
cave_options.remove(cave) cave_options.remove(cave)
if avail.swapped:
entrances.extend(swap_forbidden)
def do_mandatory_connections_decoupled(avail, cave_options, must_exit): def do_mandatory_connections_decoupled(avail, cave_options, must_exit):
@@ -1033,6 +1201,47 @@ def inverted_substitution(avail_pool, collection, is_entrance, is_set=False):
pass pass
def connect_swapped(entrancelist, targetlist, avail, two_way=False):
random.shuffle(entrancelist)
sorted_targets = list()
for ent in entrancelist:
if ent in combine_map:
if combine_map[ent] not in targetlist:
logging.getLogger('').error(f'{combine_map[ent]} not in target list, cannot swap entrance')
raise Exception(f'{combine_map[ent]} not in target list, cannot swap entrance')
sorted_targets.append(combine_map[ent])
if len(sorted_targets):
targetlist = list(sorted_targets)
else:
targetlist = list(targetlist)
indexlist = list(range(len(targetlist)))
random.shuffle(indexlist)
while len(indexlist) > 1:
index1 = indexlist.pop()
index2 = indexlist.pop()
targetlist[index1], targetlist[index2] = targetlist[index2], targetlist[index1]
for exit, target in zip(entrancelist, targetlist):
if two_way:
connect_two_way(exit, target, avail)
else:
connect_entrance(exit, target, avail)
def connect_swap(entrance, exit, avail):
swap_exit = combine_map[entrance]
if swap_exit != exit:
swap_entrance = next(e for e, x in combine_map.items() if x == exit)
if swap_entrance in ['Pyramid Entrance', 'Pyramid Hole'] and avail.inverted:
swap_entrance = 'Inverted ' + swap_entrance
if entrance in entrance_map:
connect_two_way(swap_entrance, swap_exit, avail)
else:
connect_entrance(swap_entrance, swap_exit, avail)
return swap_entrance, swap_exit
return None, None
def connect_random(exitlist, targetlist, avail, two_way=False): def connect_random(exitlist, targetlist, avail, two_way=False):
targetlist = list(targetlist) targetlist = list(targetlist)
random.shuffle(targetlist) random.shuffle(targetlist)
@@ -1479,6 +1688,23 @@ modes = {
}, },
} }
}, },
'swapped': {
'undefined': 'swap',
'keep_drops_together': 'on',
'cross_world': 'on',
'pools': {
'skull_drops': {
'special': 'drops',
'entrances': ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)',
'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
},
'skull_doors': {
'special': 'skull',
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
'Skull Woods Second Section Door (West)']
},
}
},
'crossed': { 'crossed': {
'undefined': 'shuffle', 'undefined': 'shuffle',
'keep_drops_together': 'on', 'keep_drops_together': 'on',
@@ -1641,6 +1867,8 @@ single_entrance_map = {
'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing' 'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing'
} }
combine_map = {**entrance_map, **single_entrance_map, **drop_map}
default_dw = { default_dw = {
'Thieves Town Exit', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Thieves Town Exit', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)',
'Skull Woods Second Section Exit (West)', 'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Skull Woods Second Section Exit (West)', 'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit',
@@ -1882,6 +2110,8 @@ Inverted_Bomb_Shop_Options = [
'Agahnims Tower', 'Ganons Tower', 'Dark Sanctuary Hint', 'Big Bomb Shop', 'Links House'] + Blacksmith_Options 'Agahnims Tower', 'Ganons Tower', 'Dark Sanctuary Hint', 'Big Bomb Shop', 'Links House'] + Blacksmith_Options
Forbidden_Swap_Entrances = {'Old Man Cave (East)', 'Blacksmiths Hut', 'Big Bomb Shop'}
# these are connections that cannot be shuffled and always exist. # these are connections that cannot be shuffled and always exist.
# They link together separate parts of the world we need to divide into regions # They link together separate parts of the world we need to divide into regions
mandatory_connections = [('Links House S&Q', 'Links House'), mandatory_connections = [('Links House S&Q', 'Links House'),

View File

@@ -1,5 +1,5 @@
meta: meta:
seed: 398 seed: 394
settings: settings:
1: 1:
# mode: standard # mode: standard
@@ -7,10 +7,11 @@ settings:
# boss_shuffle: random # boss_shuffle: random
# dropshuffle: underworld # dropshuffle: underworld
# enemy_shuffle: shuffled # enemy_shuffle: shuffled
# door_shuffle: crossed door_shuffle: partitioned
# intensity: 3 intensity: 3
shuffle: insanity shuffle: lite
experimental: on experimental: on
shopsanity: on
# dungeon_counters: 'on' # dungeon_counters: 'on'
#entrances: #entrances:
@@ -19,23 +20,28 @@ settings:
# Hyrule Castle Secret Entrance Drop: Lumberjack Tree (top) # Hyrule Castle Secret Entrance Drop: Lumberjack Tree (top)
# two-way: # two-way:
# Hyrule Castle Entrance (South): Links House Exit # Hyrule Castle Entrance (South): Links House Exit
#doors: doors:
# 1: 1:
# lobbies: # lobbies:
# Hyrule Castle South: GT Lobby S # Hyrule Castle South: GT Lobby S
# doors: doors:
# GT Lobby Left Down Stairs: GT Quad Pot Up Stairs TR Eye Bridge SW: Ice Compass Room NE
# GT Lobby Right Down Stairs: GT Lobby Up Stairs TR Lazy Eyes SE: Mire Conveyor Barrier NW
# GT Beam Dash ES: Swamp Big Key Ledge WN Hera Basement Cage Up Stairs: Hera Lobby Down Stairs
Hera Lobby Key Stairs:
dest: Hera Boss Down Stairs
type: Key Door
Hera Tile Room Up Stairs: Hera 5F Down Stairs
#bosses: #bosses:
# 1: # 1:
# Ganons Tower (middle): Trinexx # Ganons Tower (middle): Trinexx
#placements: placements:
# 1: 1:
# Lumberjack Tree: Lamp#2 Tower of Hera - Basement Cage: Small Key (Tower of Hera)
# Link's House: Lamp # Link's House: Lamp