Some generation improvements (bk checked better, backtrack added re-attempts)

No logic skips more key logic
Some prototyping new key rules
This commit is contained in:
aerinon
2020-05-12 15:03:39 -06:00
parent 300db22725
commit 337dbf311d
6 changed files with 172 additions and 42 deletions

View File

@@ -79,14 +79,24 @@ def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world,
dungeon_cache = {} dungeon_cache = {}
backtrack = False backtrack = False
itr = 0 itr = 0
attempt = 1
finished = False finished = False
# flag if standard and this is hyrule castle # flag if standard and this is hyrule castle
std_flag = world.mode[player] == 'standard' and bk_special std_flag = world.mode[player] == 'standard' and bk_special
while not finished: while not finished:
# what are my choices? # what are my choices?
itr += 1 itr += 1
if itr > 5000: if itr > 1000:
raise Exception('Generation taking too long. Ref %s' % name) if attempt > 9:
raise Exception('Generation taking too long. Ref %s' % name)
proposed_map = {}
choices_master = [[]]
depth = 0
dungeon_cache = {}
backtrack = False
itr = 0
attempt += 1
logger.debug(f'Starting new attempt {attempt}')
if depth not in dungeon_cache.keys(): if depth not in dungeon_cache.keys():
dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, proposed_map, dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, proposed_map,
doors_to_connect, bk_needed, bk_special, world, player) doors_to_connect, bk_needed, bk_special, world, player)
@@ -158,6 +168,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, va
dungeon = {} dungeon = {}
start = ExplorationState(dungeon=name) start = ExplorationState(dungeon=name)
start.big_key_special = bk_special start.big_key_special = bk_special
group_flags, door_map = find_bk_groups(name, available_sectors, proposed_map, bk_special)
def exception(d): def exception(d):
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS' return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
@@ -176,16 +187,17 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, va
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
if not door.stonewall and door not in proposed_map.keys(): if not door.stonewall and door not in proposed_map.keys():
hanger_set.add(door) hanger_set.add(door)
bk_flag = group_flags[door_map[door]]
parent = door.entrance.parent_region parent = door.entrance.parent_region
crystal_start = CrystalBarrier.Either if parent.crystal_switch else init_crystal crystal_start = CrystalBarrier.Either if parent.crystal_switch else init_crystal
init_state = ExplorationState(crystal_start, dungeon=name) init_state = ExplorationState(crystal_start, dungeon=name)
init_state.big_key_special = start.big_key_special init_state.big_key_special = start.big_key_special
o_state = extend_reachable_state_improved([parent], init_state, proposed_map, o_state = extend_reachable_state_improved([parent], init_state, proposed_map,
valid_doors, False, world, player, exception) valid_doors, bk_flag, world, player, exception)
o_state_cache[door.name] = o_state o_state_cache[door.name] = o_state
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception) piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception)
dungeon[door.name] = piece dungeon[door.name] = piece
check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_doors, world, player, exception) check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_doors, group_flags, door_map, world, player, exception)
# catalog hooks: Dict<Hook, List<Door, Crystal, Door>> # catalog hooks: Dict<Hook, List<Door, Crystal, Door>>
# and hangers: Dict<Hang, List<Door>> # and hangers: Dict<Hang, List<Door>>
@@ -205,7 +217,43 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, va
return dungeon, hangers, avail_hooks return dungeon, hangers, avail_hooks
def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_doors, world, player, exception): def find_bk_groups(name, available_sectors, proposed_map, bk_special):
groups = {}
door_ids = {}
gid = 1
for sector in available_sectors:
if bk_special:
my_gid = None
for door in sector.outstanding_doors:
if door in proposed_map and proposed_map[door] in door_ids:
if my_gid:
merge_gid = door_ids[proposed_map[door]]
for door in door_ids.keys():
if door_ids[door] == merge_gid:
door_ids[door] = my_gid
groups[my_gid] = groups[my_gid] or groups[merge_gid]
else:
my_gid = door_ids[proposed_map[door]]
if not my_gid:
my_gid = gid
gid += 1
for door in sector.outstanding_doors:
door_ids[door] = my_gid
if my_gid not in groups.keys():
groups[my_gid] = False
for region in sector.regions:
for loc in region.locations:
if loc.forced_item and loc.item.bigkey and name in loc.item.name:
groups[my_gid] = True
else:
for door in sector.outstanding_doors:
door_ids[door] = gid
groups[gid] = False
return groups, door_ids
def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_doors, group_flags, door_map,
world, player, exception):
not_blue = set() not_blue = set()
not_blue.update(hanger_set) not_blue.update(hanger_set)
doors_to_check = set() doors_to_check = set()
@@ -233,17 +281,18 @@ def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_do
hang_type = hanger_from_door(door) # am I hangable on a hook? hang_type = hanger_from_door(door) # am I hangable on a hook?
hook_type = hook_from_door(door) # am I hookable onto a hanger? hook_type = hook_from_door(door) # am I hookable onto a hanger?
if (hang_type in blue_hooks and not door.stonewall) or hook_type in blue_hangers: if (hang_type in blue_hooks and not door.stonewall) or hook_type in blue_hangers:
explore_blue_state(door, dungeon, o_state_cache[door.name], proposed_map, valid_doors, bk_flag = group_flags[door_map[door]]
explore_blue_state(door, dungeon, o_state_cache[door.name], proposed_map, valid_doors, bk_flag,
world, player, exception) world, player, exception)
doors_to_check.add(door) doors_to_check.add(door)
not_blue.difference_update(doors_to_check) not_blue.difference_update(doors_to_check)
def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player, exception): def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, bk_flag, world, player, exception):
parent = door.entrance.parent_region parent = door.entrance.parent_region
blue_start = ExplorationState(CrystalBarrier.Blue, o_state.dungeon) blue_start = ExplorationState(CrystalBarrier.Blue, o_state.dungeon)
blue_start.big_key_special = o_state.big_key_special blue_start.big_key_special = o_state.big_key_special
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, False, b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, bk_flag,
world, player, exception) world, player, exception)
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception) dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception)
@@ -915,7 +964,7 @@ def extend_reachable_state(search_regions, state, world, player):
return local_state return local_state
def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, isOrigin, world, player, exception): def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, bk_flag, world, player, exception):
local_state = state.copy() local_state = state.copy()
for region in search_regions: for region in search_regions:
local_state.visit_region(region) local_state.visit_region(region)
@@ -923,7 +972,7 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, valid_d
while len(local_state.avail_doors) > 0: while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door() explorable_door = local_state.next_avail_door()
if explorable_door.door.bigKey: if explorable_door.door.bigKey:
if isOrigin: if bk_flag:
big_not_found = not special_big_key_found(local_state, world, player) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0 big_not_found = not special_big_key_found(local_state, world, player) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0
if big_not_found: if big_not_found:
continue # we can't open this door continue # we can't open this door

View File

@@ -226,7 +226,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool =
def valid_key_placement(item, location, itempool, world): def valid_key_placement(item, location, itempool, world):
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player]: if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player] or world.logic[item.player] == 'nologic':
return True return True
dungeon = location.parent_region.dungeon dungeon = location.parent_region.dungeon
if dungeon: if dungeon:

View File

@@ -41,7 +41,8 @@ class KeyLogic(object):
def __init__(self, dungeon_name): def __init__(self, dungeon_name):
self.door_rules = {} self.door_rules = {}
self.bk_restricted = set() self.bk_restricted = set() # subset of free locations
self.bk_locked = set() # includes potentially other locations and key only locations
self.sm_restricted = set() self.sm_restricted = set()
self.small_key_name = dungeon_keys[dungeon_name] self.small_key_name = dungeon_keys[dungeon_name]
self.bk_name = dungeon_bigs[dungeon_name] self.bk_name = dungeon_bigs[dungeon_name]
@@ -50,7 +51,9 @@ class KeyLogic(object):
self.logic_min = {} self.logic_min = {}
self.logic_max = {} self.logic_max = {}
self.placement_rules = [] self.placement_rules = []
self.location_rules = {}
self.outside_keys = 0 self.outside_keys = 0
self.dungeon = dungeon_name
def check_placement(self, unplaced_keys, big_key_loc=None): def check_placement(self, unplaced_keys, big_key_loc=None):
for rule in self.placement_rules: for rule in self.placement_rules:
@@ -77,6 +80,18 @@ class DoorRules(object):
self.opposite = None self.opposite = None
class LocationRule(object):
def __init__(self):
self.small_key_num = 0
self.conditional_sets = []
class ConditionalLocationRule(object):
def __init__(self, conditional_set):
self.conditional_set = conditional_set
self.small_key_num = 0
class PlacementRule(object): class PlacementRule(object):
def __init__(self): def __init__(self):
@@ -88,6 +103,7 @@ class PlacementRule(object):
self.check_locations_w_bk = None self.check_locations_w_bk = None
self.check_locations_wo_bk = None self.check_locations_wo_bk = None
self.bk_relevant = True self.bk_relevant = True
self.key_reduced = False
def contradicts(self, rule, unplaced_keys, big_key_loc): def contradicts(self, rule, unplaced_keys, big_key_loc):
bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False
@@ -208,6 +224,7 @@ def analyze_dungeon(key_layout, world, player):
find_bk_locked_sections(key_layout, world, player) find_bk_locked_sections(key_layout, world, player)
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations))
if world.retro[player] and world.mode[player] != 'standard': if world.retro[player] and world.mode[player] != 'standard':
return return
@@ -297,7 +314,9 @@ def create_exhaustive_placement_rules(key_layout, world, player):
rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc)) rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc))
if valid_rule: if valid_rule:
key_logic.placement_rules.append(rule) key_logic.placement_rules.append(rule)
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
refine_placement_rules(key_layout, max_ctr) refine_placement_rules(key_layout, max_ctr)
refine_location_rules(key_layout)
def placement_self_lock_adjustment(rule, max_ctr, blocked_loc, ctr, world, player): def placement_self_lock_adjustment(rule, max_ctr, blocked_loc, ctr, world, player):
@@ -319,6 +338,37 @@ def check_sm_restriction_needed(key_layout, max_ctr, rule, blocked):
return False return False
def adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr):
if rule.bk_conditional_set:
test_set = (rule.bk_conditional_set - key_logic.bk_locked) - set(max_ctr.key_only_locations.keys())
needed = rule.needed_keys_wo_bk if test_set else 0
else:
test_set = None
needed = rule.needed_keys_w_bk
if needed > 0:
accessible_loc.update(key_counter.other_locations)
blocked_loc = key_layout.all_locations-accessible_loc
for location in blocked_loc:
if location not in key_logic.location_rules.keys():
loc_rule = LocationRule()
key_logic.location_rules[location] = loc_rule
else:
loc_rule = key_logic.location_rules[location]
if test_set:
if location not in key_logic.bk_locked:
cond_rule = None
for other in loc_rule.conditional_sets:
if other.conditional_set == test_set:
cond_rule = other
break
if not cond_rule:
cond_rule = ConditionalLocationRule(test_set)
loc_rule.conditional_sets.append(cond_rule)
cond_rule.small_key_num = max(needed, cond_rule.small_key_num)
else:
loc_rule.small_key_num = max(needed, loc_rule.small_key_num)
def refine_placement_rules(key_layout, max_ctr): def refine_placement_rules(key_layout, max_ctr):
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
changed = True changed = True
@@ -407,6 +457,20 @@ def refine_placement_rules(key_layout, max_ctr):
removed_rules[r2] = r1 removed_rules[r2] = r1
def refine_location_rules(key_layout):
locs_to_remove = []
for loc, rule in key_layout.key_logic.location_rules.items():
conditions_to_remove = []
for cond_rule in rule.conditional_sets:
if cond_rule.small_key_num <= rule.small_key_num:
conditions_to_remove.append(cond_rule)
rule.conditional_sets = [x for x in rule.conditional_sets if x not in conditions_to_remove]
if rule.small_key_num == 0 and len(rule.conditional_sets) == 0:
locs_to_remove.append(loc)
for loc in locs_to_remove:
del key_layout.key_logic.location_rules[loc]
def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player): def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player):
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
rule = PlacementRule() rule = PlacementRule()
@@ -421,6 +485,7 @@ def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, a
rule.check_locations_w_bk = accessible_loc rule.check_locations_w_bk = accessible_loc
check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc) check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc)
key_logic.placement_rules.append(rule) key_logic.placement_rules.append(rule)
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
def queue_sorter(queue_item): def queue_sorter(queue_item):
@@ -438,12 +503,10 @@ def queue_sorter_2(queue_item):
def find_bk_locked_sections(key_layout, world, player): def find_bk_locked_sections(key_layout, world, player):
if key_layout.big_key_special:
return
key_counters = key_layout.key_counters key_counters = key_layout.key_counters
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
bk_key_not_required = set() bk_not_required = set()
big_chest_allowed_big_key = world.accessibility[player] != 'locations' big_chest_allowed_big_key = world.accessibility[player] != 'locations'
for counter in key_counters.values(): for counter in key_counters.values():
key_layout.all_chest_locations.update(counter.free_locations) key_layout.all_chest_locations.update(counter.free_locations)
@@ -452,10 +515,19 @@ def find_bk_locked_sections(key_layout, world, player):
if counter.big_key_opened and counter.important_location: if counter.big_key_opened and counter.important_location:
big_chest_allowed_big_key = False big_chest_allowed_big_key = False
if not counter.big_key_opened: if not counter.big_key_opened:
bk_key_not_required.update(counter.free_locations) bk_not_required.update(counter.free_locations)
key_logic.bk_restricted.update(dict.fromkeys(set(key_layout.all_chest_locations).difference(bk_key_not_required))) bk_not_required.update(counter.key_only_locations)
bk_not_required.update(counter.other_locations)
# todo?: handle bk special differently in cross dungeon
# notably: things behind bk doors - relying on the bk door logic atm
if not key_layout.big_key_special:
key_logic.bk_restricted.update(dict.fromkeys(set(key_layout.all_chest_locations).difference(bk_not_required)))
key_logic.bk_locked.update(dict.fromkeys(set(key_layout.all_locations) - bk_not_required))
if not big_chest_allowed_big_key: if not big_chest_allowed_big_key:
key_logic.bk_restricted.update(find_big_chest_locations(key_layout.all_chest_locations)) bk_required_locations = find_big_chest_locations(key_layout.all_chest_locations)
bk_required_locations += find_big_key_locked_locations(key_layout.all_chest_locations)
key_logic.bk_restricted.update(bk_required_locations)
key_logic.bk_locked.update(bk_required_locations)
def empty_counter(counter): def empty_counter(counter):
@@ -936,6 +1008,14 @@ def find_big_chest_locations(locations):
return ret return ret
def find_big_key_locked_locations(locations):
ret = []
for loc in locations:
if loc.name in ["Thieves' Town - Blind's Cell", "Hyrule Castle - Zelda's Chest"]:
ret.append(loc)
return ret
def expand_key_state(state, flat_proposal, world, player): def expand_key_state(state, flat_proposal, world, player):
while len(state.avail_doors) > 0: while len(state.avail_doors) > 0:
exp_door = state.next_avail_door() exp_door = state.next_avail_door()
@@ -1149,7 +1229,7 @@ def set_paired_rules(key_logic, world, player):
# Soft lock stuff # Soft lock stuff
def validate_key_layout(key_layout, world, player): def validate_key_layout(key_layout, world, player):
# retro is all good - except for hyrule castle in standard mode # retro is all good - except for hyrule castle in standard mode
if world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle'): if (world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) or world.logic[player] == 'nologic':
return True return True
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState(dungeon=key_layout.sector.name) state = ExplorationState(dungeon=key_layout.sector.name)
@@ -1294,8 +1374,10 @@ def create_key_counter(state, key_layout, world, player):
if important_location(loc, world, player): if important_location(loc, world, player):
key_counter.important_location = True key_counter.important_location = True
key_counter.other_locations[loc] = None key_counter.other_locations[loc] = None
elif loc.event and 'Small Key' in loc.item.name: elif loc.forced_item and loc.item.name == key_layout.key_logic.small_key_name:
key_counter.key_only_locations[loc] = None key_counter.key_only_locations[loc] = None
elif loc.forced_item and loc.item.name == key_layout.key_logic.bk_name:
key_counter.other_locations[loc] = None
elif loc.name not in dungeon_events: elif loc.name not in dungeon_events:
key_counter.free_locations[loc] = None key_counter.free_locations[loc] = None
else: else:
@@ -1306,11 +1388,6 @@ def create_key_counter(state, key_layout, world, player):
key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player)) key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player))
else: else:
key_counter.big_key_opened = state.big_key_opened key_counter.big_key_opened = state.big_key_opened
# if soft_lock_check:
# avail_chests = available_chest_small_keys(key_counter, key_counter.big_key_opened, world)
# avail_keys = avail_chests + len(key_counter.key_only_locations)
# if avail_keys <= key_counter.used_keys and avail_keys < key_layout.max_chests + key_layout.max_drops:
# raise SoftLockException()
return key_counter return key_counter
@@ -1322,13 +1399,14 @@ def imp_locations_factory(world, player):
if imp_locations: if imp_locations:
return imp_locations return imp_locations
imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed': if world.mode[player] == 'standard':
imp_locations.append('Hyrule Dungeon Cellblock') imp_locations.append('Zelda Pickup')
imp_locations.append('Zelda Dropoff')
return imp_locations return imp_locations
def important_location(loc, world, player): def important_location(loc, world, player):
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_item is not None and loc.item.bigkey)
def create_odd_key_counter(door, parent_counter, key_layout, world, player): def create_odd_key_counter(door, parent_counter, key_layout, world, player):

25
Main.py
View File

@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.1.0.1-u' __version__ = '0.1.0.2-u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
@@ -145,17 +145,18 @@ def main(args, seed=None, fish=None):
fill_dungeons(world) fill_dungeons(world)
for player in range(1, world.players+1): for player in range(1, world.players+1):
for key_layout in world.key_layout[player].values(): if world.logic[player] != 'nologic':
if not validate_key_placement(key_layout, world, player): for key_layout in world.key_layout[player].values():
raise RuntimeError( if not validate_key_placement(key_layout, world, player):
"%s: %s (%s %d)" % raise RuntimeError(
( "%s: %s (%s %d)" %
world.fish.translate("cli","cli","keylock.detected"), (
key_layout.sector.name, world.fish.translate("cli", "cli", "keylock.detected"),
world.fish.translate("cli","cli","player"), key_layout.sector.name,
player world.fish.translate("cli", "cli", "player"),
) player
) )
)
logger.info(world.fish.translate("cli","cli","fill.world")) logger.info(world.fish.translate("cli","cli","fill.world"))

View File

@@ -10,7 +10,9 @@
* Crossed Dungeon generation improvements * Crossed Dungeon generation improvements
* Fix for Animated Tiles in crossed dungeon * Fix for Animated Tiles in crossed dungeon
* Stonewall hardlock no longer reachable from certain drops (Sewer Drop, some Skull Woods drops) that were previously possible * Stonewall hardlock no longer reachable from certain drops (Sewer Drop, some Skull Woods drops) that were previously possible
* No logic uses less key door logic
##### In Progress ##### In Progress
* ~~TT Attic Hint tile should have a crystal switch accessible now~~ * TT Attic Hint tile should have a crystal switch accessible now
* Different key logic rules in development

View File

@@ -177,7 +177,7 @@ def create_regions(world, player):
create_cave_region(player, 'Dark Desert Hint', 'a storyteller'), create_cave_region(player, 'Dark Desert Hint', 'a storyteller'),
create_dw_region(player, 'Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']), create_dw_region(player, 'Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']),
create_dw_region(player, 'Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Superbunny Cave (Top)', create_dw_region(player, 'Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Superbunny Cave (Top)',
'Hookshot Cave', 'East Death Mountain (Top) Mirror Spot', 'Turtle Rock']), 'Hookshot Cave', 'East Death Mountain (Top) Mirror Spot', 'Turtle Rock']),
create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']), create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']),
create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']), create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']),
create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Fairy Ascension Mirror Spot']), create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Fairy Ascension Mirror Spot']),