Generation improvements

Hera Lobby fix
Added gauntlet 3 to cut carpet
This commit is contained in:
aerinon
2020-10-07 16:33:43 -06:00
parent 0ff0a1cf76
commit 38be9e6ec0
9 changed files with 345 additions and 52 deletions

View File

@@ -1419,10 +1419,9 @@ class Sector(object):
self.branch_factor -= cnt_dead - 1
for region in self.regions:
for ent in region.entrances:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
# same sector as another entrance
if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']:
if (ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and ent.parent_region.name != 'Menu') or ent.parent_region.name == 'Sewer Drop':
self.branch_factor += 1
break # you only ever get one allowance for an entrance region, multiple entrances don't help
return self.branch_factor
def branches(self):

View File

@@ -51,10 +51,7 @@ def link_doors(world, player):
for portal in world.dungeon_portals[player]:
connect_portal(portal, world, player)
world.get_portal('Desert East', player).destination = True
world.get_portal('Desert Back', player).deadEnd = True
world.get_portal('Skull 1', player).deadEnd = True
world.get_portal('Skull 2 West', player).destination = True
world.get_portal('Skull 3', player).deadEnd = True
world.get_portal('Turtle Rock Lazy Eyes', player).destination = True
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
@@ -343,10 +340,12 @@ def choose_portals(world, player):
for portal in portal_list:
placeholder = world.get_region(portal + ' Placeholder', player)
portal_region = placeholder.exits[0].connected_region
name = portal_region.name
if portal_region.type == RegionType.LightWorld:
world.get_portal(portal, player).light_world = True
if portal_region.name in world.inaccessible_regions[player]:
region_map[portal_region.name].append(portal)
if name in world.inaccessible_regions[player]:
name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name
region_map[name_key].append(portal)
inaccessible_portals.append(portal)
else:
reachable_portals.append(portal)
@@ -403,6 +402,9 @@ def choose_portals(world, player):
if not sanctuary_door.entranceFlag:
world.get_room(0x12, player).delete(3)
world.get_room(0x12, player).change(2, DoorKind.NormalLow)
hera_door = world.get_door('Hera Lobby S', player)
if not hera_door.entranceFlag:
world.get_room(0x77, player).change(0, DoorKind.NormalLow2)
if not world.swamp_patch_required[player]:
swamp_region = world.get_entrance('Swamp Palace', player).connected_region
@@ -425,10 +427,12 @@ def connect_portal(portal, world, player):
else:
edit_entrance = world.get_entrance(ent, player)
entrance_region = portal_entrance.parent_region
old_region = edit_entrance.connected_region
edit_entrance.connected_region = entrance_region
entrance_region.exits.remove(portal_entrance)
entrance_region.exits.append(target_exit)
entrance_region.entrances.append(edit_entrance)
old_region.entrances.remove(edit_entrance)
world.regions.remove(placeholder)
@@ -485,7 +489,7 @@ def assign_portal(candidates, possible_portals, world, player):
old_door = portal.door
if old_door:
old_door.entranceFlag = False
if old_door.name not in ['Hyrule Castle Lobby S', 'Sanctuary S']:
if old_door.name not in ['Hyrule Castle Lobby S', 'Sanctuary S', 'Hera Lobby S']:
old_door_kind = DoorKind.NormalLow if old_door.layer or old_door.pseudo_bg else DoorKind.Normal
world.get_room(old_door.roomIndex, player).change(old_door.doorListPos, old_door_kind)
portal.change_door(candidate)
@@ -510,6 +514,7 @@ def clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_l
def create_dungeon_entrances(world, player):
entrance_map = defaultdict(list)
split_map: DefaultDict[str, DefaultDict[str, List]] = defaultdict(lambda: defaultdict(list))
originating: DefaultDict[str, DefaultDict[str, Dict]] = defaultdict(lambda: defaultdict(dict))
for key, portal_list in dungeon_portals.items():
if world.mode[player] == 'standard' and key in standard_starts.keys():
portal = world.get_portal('Hyrule Castle South', player)
@@ -523,25 +528,36 @@ def create_dungeon_entrances(world, player):
the_rest = []
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
r_name = portal.door.entrance.parent_region.name
entrance_map[key].append(r_name)
entrance_map[key].append(portal.door.entrance.parent_region.name)
if portal.deadEnd:
dead_ends.append(r_name)
dead_ends.append(portal)
elif portal.destination:
destinations.append(r_name)
destinations.append(portal)
else:
the_rest.append(r_name)
the_rest.append(portal)
choices = list(split_portals[key])
for r_name in dead_ends:
for portal in dead_ends:
choice = random.choice(choices)
choices.remove(choice)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
for r_name in the_rest:
for portal in the_rest:
if len(choices) == 0:
choices.append('Extra')
choice = random.choice(choices)
p_entrance = portal.door.entrance
r_name = p_entrance.parent_region.name
split_map[key][choice].append(r_name)
originating[key][choice][p_entrance.connected_region.name] = None
dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
for r_name in destinations:
choice = random.choice(dest_choices)
for portal in destinations:
restricted = portal.door.entrance.connected_region.name in world.inaccessible_regions[player]
if restricted:
filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
else:
filtered_choices = dest_choices
choice = random.choice(filtered_choices)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
else:
for portal_name in portal_list:
@@ -605,7 +621,10 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map,
split_builders = split_dungeon_builder(builder, split_list, builder_info)
dungeon_builders.update(split_builders)
for sub_name, split_entrances in split_list.items():
sub_builder = dungeon_builders[name+' '+sub_name]
key = name+' '+sub_name
if key not in dungeon_builders:
continue
sub_builder = dungeon_builders[key]
sub_builder.split_flag = True
entrance_list = list(split_entrances)
for ent in entrances_map[name]:
@@ -700,8 +719,9 @@ def determine_entrance_list_2(world, player):
if parent.name not in world.inaccessible_regions[player]:
entrance_map[key].append(region_name)
else:
if ent.parent_region not in potential_entrances.keys():
if parent not in potential_entrances.keys():
potential_entrances[parent] = []
if region_name not in potential_entrances[parent]:
potential_entrances[parent].append(region_name)
connections[region_name] = parent
return entrance_map, potential_entrances, connections
@@ -1047,7 +1067,9 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
def valid_region_to_explore(region, world, player):
return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player])
return region and (region.type == RegionType.Dungeon
or region.name in world.inaccessible_regions[player]
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard'))
def shuffle_key_doors(builder, world, player):
@@ -1503,6 +1525,9 @@ def valid_inaccessible_region(r):
def add_inaccessible_doors(world, player):
if world.mode[player] == 'standard':
create_door(world, player, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Ledge')
create_door(world, player, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Ledge')
# todo: ignore standard mode hyrule castle ledge?
for inaccessible_region in world.inaccessible_regions[player]:
region = world.get_region(inaccessible_region, player)

View File

@@ -30,6 +30,12 @@ class GraphPiece:
# Dungeons shouldn't be generated until all entrances are appropriately accessible
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
entrance_regions = convert_regions(entrance_region_names, world, player)
excluded = {}
for region in entrance_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination:
excluded[region] = None
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
proposed_map = {}
doors_to_connect = {}
all_regions = set()
@@ -90,6 +96,12 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
logger = logging.getLogger('')
name = builder.name
entrance_regions = convert_regions(entrance_region_names, world, player)
excluded = {}
for region in entrance_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination:
excluded[region] = None
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect = {}
all_regions = set()
bk_needed = False
@@ -1076,14 +1088,18 @@ def special_big_key_found(state, world, player):
def valid_region_to_explore_in_regions(region, all_regions, world, player):
if region is None:
return False
return (region.type == RegionType.Dungeon and region in all_regions) or region.name in world.inaccessible_regions[player]
return (region.type == RegionType.Dungeon and region in all_regions)\
or region.name in world.inaccessible_regions[player]\
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard')
# cross-utility methods
def valid_region_to_explore(region, name, world, player):
if region is None:
return False
return (region.type == RegionType.Dungeon and region.dungeon.name in name) or region.name in world.inaccessible_regions[player]
return (region.type == RegionType.Dungeon and region.dungeon.name in name)\
or region.name in world.inaccessible_regions[player]\
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard')
def get_doors(world, region, player):
@@ -1159,9 +1175,7 @@ class DungeonBuilder(object):
self.key_door_proposal = None
self.allowance = None
if name in dungeon_dead_end_allowance.keys():
self.allowance = dungeon_dead_end_allowance[name]
elif 'Stonewall' in name:
if 'Stonewall' in name:
self.allowance = 1
elif 'Prewall' in name:
orig_name = name[:-8]
@@ -2597,6 +2611,8 @@ def categorize_groupings(sectors):
def valid_assignment(builder, sector_list, builder_info):
if not valid_entrance(builder, sector_list, builder_info):
return False
if not valid_c_switch(builder, sector_list):
return False
if not valid_polarized_assignment(builder, sector_list):
@@ -2605,6 +2621,34 @@ def valid_assignment(builder, sector_list, builder_info):
return resolved
def valid_entrance(builder, sector_list, builder_info):
is_dead_end = False
if len(builder.sectors) == 0:
is_dead_end = True
else:
entrances, splits, world, player = builder_info
if builder.name not in entrances.keys():
name_parts = builder.name.rsplit(' ', 1)
entrance_list = splits[name_parts[0]][name_parts[1]]
entrances = []
for sector in builder.sectors:
if sector.is_entrance_sector():
sector.region_set()
entrances.append(sector)
all_dead = True
for sector in entrances:
for region in entrance_list:
if region in sector.region_set():
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == region))
if not portal.deadEnd:
all_dead = False
break
if not all_dead:
break
is_dead_end = all_dead
return len(sector_list) == 0 if is_dead_end else True
def valid_c_switch(builder, sector_list):
if builder.c_switch_present:
return True
@@ -2699,17 +2743,38 @@ def split_dungeon_builder(builder, split_list, builder_info):
builder.split_dungeon_map[name].valid_proposal = proposal
return builder.split_dungeon_map # we made this earlier in gen, just use it
attempts, comb_w_replace = 0, None
attempts, comb_w_replace, merge_attempt = 0, None, False
while attempts < 5: # does not solve coin flips 3% of the time
try:
candidate_sectors = dict.fromkeys(builder.sectors)
global_pole = GlobalPolarity(candidate_sectors)
dungeon_map = {}
dungeon_map, sub_builder, merge_keys = {}, None, []
if merge_attempt:
candidates = []
for name, split_entrances in split_list.items():
if len(split_entrances) > 1:
candidates.append(name)
continue
elif len(split_entrances) <= 0:
continue
x, y, world, player = builder_info
r_name = split_entrances[0]
p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name)
if not p.deadEnd:
candidates.append(name)
merge_keys = random.sample(candidates, 2)
for name, split_entrances in split_list.items():
key = builder.name + ' ' + name
if merge_keys and name in merge_keys:
other_key = builder.name + ' ' + [x for x in merge_keys if x != name][0]
if other_key in dungeon_map:
key = other_key
sub_builder = dungeon_map[other_key]
sub_builder.all_entrances.extend(split_entrances)
if key not in dungeon_map:
dungeon_map[key] = sub_builder = DungeonBuilder(key)
sub_builder.all_entrances = split_entrances
sub_builder.all_entrances = list(split_entrances)
for r_name in split_entrances:
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
comb_w_replace = len(dungeon_map) ** len(candidate_sectors)
@@ -2719,6 +2784,9 @@ def split_dungeon_builder(builder, split_list, builder_info):
attempts += 5 # all the combinations were tried already, no use repeating
else:
attempts += 1
if attempts >= 5 and not merge_attempt:
merge_attempt, attempts = True, 0
raise GenerationException('Unable to resolve in 5 attempts')
@@ -2735,8 +2803,8 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
for i, choice in enumerate(choices):
chosen_sectors[choice].append(main_sector_list[i])
all_valid = True
for name, sector_list in chosen_sectors.items():
if not valid_assignment(dungeon_map[name], sector_list, builder_info):
for name, builder in dungeon_map.items():
if not valid_assignment(builder, chosen_sectors[name], builder_info):
all_valid = False
break
if all_valid:
@@ -3287,7 +3355,7 @@ def find_priority_equation(equations, access_id, current_access):
if len(filtered_candidates) == 1:
return filtered_candidates[0]
neutral_candidates = [x for x in filtered_candidates if x[0].neutral_profit() or x[0].neutral()]
neutral_candidates = [x for x in filtered_candidates if (x[0].neutral_profit() or x[0].neutral()) and x[0].profit(current_access) == local_profit_map[x[2]]]
if len(neutral_candidates) == 0:
neutral_candidates = filtered_candidates
if len(neutral_candidates) == 1:

2
Rom.py
View File

@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '98e8426901b441858631ae444c12d4fc'
RANDOMIZERBASEHASH = '7d4881c390855dbe2f9c308bed2e61ef'
class JsonRom(object):

View File

@@ -904,7 +904,9 @@ def find_rules_for_zelda_delivery(world, player):
region, path_rules, path = queue.popleft()
for ext in region.exits:
connect = ext.connected_region
if connect and connect.type == RegionType.Dungeon and connect not in visited:
valid_region = connect and connect not in visited and\
(connect.type == RegionType.Dungeon or connect.name == 'Hyrule Castle Ledge')
if valid_region:
rule = ext.access_rule
rule_list = list(path_rules)
next_path = list(path)
@@ -1562,6 +1564,7 @@ def add_key_logic_rules(world, player):
for d_name, d_logic in key_logic.items():
for door_name, keys in d_logic.door_rules.items():
spot = world.get_entrance(door_name, player)
if not world.retro[player] or world.mode[player] != 'standard' or not retro_in_hc(spot):
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
if keys.opposite:
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
@@ -1576,6 +1579,10 @@ def add_key_logic_rules(world, player):
add_rule(world.get_location(chest.name, player), create_rule(d_logic.bk_name, player))
def retro_in_hc(spot):
return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False
def create_rule(item_name, player):
return lambda state: state.has(item_name, player)

View File

@@ -31,11 +31,11 @@ def main(args=None):
['Std ', ' --mode standard'],
['Inv ', ' --mode inverted']]:
basecommand = f"py -{py_version} DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
basecommand = f"py DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
def gen_seed():
taskcommand = basecommand + " " + command + mode[1]
return subprocess.run(taskcommand, capture_output=True, shell=False, text=True)
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
for x in range(1, max_attempts + 1):
task = pool.submit(gen_seed)
@@ -80,7 +80,7 @@ def main(args=None):
success = f"{testname}{mode} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
successes.append(success)
print(success)
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}% "
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
return result.strip()
results = []

196
Utils.py
View File

@@ -457,7 +457,201 @@ def print_graph(world):
ET.dump(root)
def extract_data_from_us_rom(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad]
for room in rooms:
b2idx = room*2
b3idx = room*3
headerptr = 0x110000 + b2idx # zscream specific
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x108000 # zscream specific
header, objectdata, spritedata, secretdata = [], [], [], []
for i in range(0, 14):
header.append(rom_data[headerloc+i])
objectptr = 0xF8000 + b3idx
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
objectloc -= 0x100000
stop, idx = False, 0
ffcnt = 0
mode = 0
while ffcnt < 2:
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
b3 = rom_data[objectloc+idx+2]
objectdata.append(b1)
objectdata.append(b2)
if b1 == 0xff and b2 == 0xff:
ffcnt += 1
mode = 0
if b1 == 0xf0 and b2 == 0xff:
mode = 1
if not mode and ffcnt < 2:
objectdata.append(b3)
idx += 3
else:
idx += 2
spriteptr = 0x4d62e + b2idx
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
done, idx = False, 0
while not done:
b1 = rom_data[spriteloc+idx]
spritedata.append(b1)
if b1 == 0xff:
done = True
idx += 1
secretptr = 0xdb69 + b2idx
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
done, idx = False, 0
while not done:
b1 = rom_data[secretloc+idx]
b2 = rom_data[secretloc+idx+1]
b3 = rom_data[secretloc+idx+2]
secretdata.append(b1)
secretdata.append(b2)
if b1 == 0xff and b2 == 0xff:
done = True
else:
secretdata.append(b3)
idx += 3
print(f'Room {room:02x}')
print(f'db {",".join([f"${x:02x}" for x in header])}')
print(f'Obj Length: {len(objectdata)}')
print_data_block(objectdata)
print('Sprites')
print_data_block(spritedata)
print('Secrets')
print_data_block(secretdata)
blockdata, torchdata = [], []
blockloc = 0x271de
for i in range(0, 128):
idx = i*4
b1 = rom_data[blockloc+idx]
b2 = rom_data[blockloc+idx+1]
room_idx = b1 + b2*0x100
if room_idx in rooms:
blockdata.append(b1)
blockdata.append(b2)
blockdata.append(rom_data[blockloc+idx+2])
blockdata.append(rom_data[blockloc+idx+3])
torchloc = 0x2736A
nomatch = False
append = False
for i in range(0, 192):
idx = i*2
b1 = rom_data[torchloc+idx]
b2 = rom_data[torchloc+idx+1]
if nomatch:
if b1 == 0xff and b2 == 0xff:
nomatch = False
elif not append:
room_idx = b1 + b2*0x100
if room_idx in rooms:
append = True
else:
nomatch = True
if append:
torchdata.append(b1)
torchdata.append(b2)
if b1 == 0xff and b2 == 0xff:
append = False
print('Blocks')
print_data_block(blockdata)
print('Torches')
print_data_block(torchdata)
print()
def print_data_block(block):
for i in range(0, len(block)//16 + 1):
slice = block[i*16:i*16+16]
print(f'db {",".join([f"${x:02x}" for x in slice])}')
def extract_data_from_jp_rom(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
for room in rooms:
b2idx = room*2
b3idx = room*3
headerptr = 0x271e2 + b2idx
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x18000
header, objectdata, spritedata, secretdata = [], [], [], []
for i in range(0, 14):
header.append(rom_data[headerloc+i])
objectptr = 0xF8000 + b3idx
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
objectloc -= 0x100000
stop, idx = False, 0
ffcnt = 0
mode = 0
while ffcnt < 2:
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
b3 = rom_data[objectloc+idx+2]
objectdata.append(b1)
objectdata.append(b2)
if b1 == 0xff and b2 == 0xff:
ffcnt += 1
mode = 0
if b1 == 0xf0 and b2 == 0xff:
mode = 1
if not mode and ffcnt < 2:
objectdata.append(b3)
idx += 3
else:
idx += 2
spriteptr = 0x4d62e + b2idx
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
secretptr = 0xdb67 + b2idx
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
done, idx = False, 0
while not done:
b1 = rom_data[spriteloc+idx]
spritedata.append(b1)
if b1 == 0xff:
done = True
idx += 1
done, idx = False, 0
while not done:
b1 = rom_data[secretloc+idx]
b2 = rom_data[secretloc+idx+1]
b3 = rom_data[secretloc+idx+2]
secretdata.append(b1)
secretdata.append(b2)
if b1 == 0xff and b2 == 0xff:
done = True
else:
secretdata.append(b3)
idx += 3
print(f'Room {room:02x}')
print(f'HeaderPtr {headerptr:06x}')
print(f'HeaderLoc {headerloc:06x}')
print(f'db {",".join([f"${x:02x}" for x in header])}')
print(f'Obj Length: {len(objectdata)}')
print(f'ObjectPtr {objectptr:06x}')
print(f'ObjectLoc {objectloc:06x}')
for i in range(0, len(objectdata)//16 + 1):
slice = objectdata[i*16:i*16+16]
print(f'db {",".join([f"${x:02x}" for x in slice])}')
print(f'SpritePtr {spriteptr:06x}')
print(f'SpriteLoc {spriteloc:06x}')
print_data_block(spritedata)
print(f'SecretPtr {secretptr:06x}')
print(f'SecretLoc {secretloc:06x}')
print_data_block(secretdata)
print()
if __name__ == '__main__':
# make_new_base2current()
# read_entrance_data(old_rom=sys.argv[1])
room_palette_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1])
extract_data_from_us_rom(sys.argv[1])
# extract_data_from_jp_rom(sys.argv[1])

View File

@@ -88,10 +88,10 @@ SuctionOverworldFix:
+ rtl
; TT Alcove, Mire bridges, pod falling, SW torch room, TR Pipe room, Bob's Room, Ice Many Pots, Mire Hub
; swamp waterfall
; swamp waterfall, Gauntlet 3
CutoffRooms:
db $bc, $a2, $1a, $49, $14, $8c, $9f, $c2
db $66
db $66, $5d
CutoffEntranceRug:
pha : phx

File diff suppressed because one or more lines are too long