Files
alttpr-python/source/dungeon/DungeonStitcher.py
aerinon 2e9d132985 Large refactor underway.
Wrote new main method
Implemented trap door shuffle to some degree
Still needs the other types
2022-06-28 13:05:38 -06:00

885 lines
38 KiB
Python

import RaceRandom as random
import collections
import logging
import time
from BaseClasses import CrystalBarrier, DoorType, Hook, RegionType, Sector
from BaseClasses import hook_from_door, flooded_keys
from Regions import dungeon_events, flooded_keys_reverse
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
pass
# todo: determine the part of check_valid that are necessary here
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
if builder.valid_proposal: # we made this earlier in gen, just use it
proposed_map = builder.valid_proposal
else:
proposed_map = generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon, world, player)
builder.valid_proposal = proposed_map
queue = collections.deque(proposed_map.items())
while len(queue) > 0:
a, b = queue.popleft()
if world.decoupledoors[player]:
connect_doors_one_way(a, b)
else:
connect_doors(a, b)
queue.remove((b, a))
if len(builder.sectors) == 0:
return Sector()
available_sectors = list(builder.sectors)
master_sector = available_sectors.pop()
for sub_sector in available_sectors:
master_sector.regions.extend(sub_sector.regions)
master_sector.outstanding_doors.clear()
master_sector.r_name_set = None
return master_sector
def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon, world, player):
logger = logging.getLogger('')
name = builder.name
logger.debug(f'Generating Dungeon: {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:
if portal.destination:
excluded[region] = None
elif len(entrance_regions) > 1:
p_region = portal.door.entrance.connected_region
access_region = next(x.parent_region for x in p_region.entrances
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld])
if (access_region.name in world.inaccessible_regions[player] and
region.name not in world.enabled_entrances[player]):
excluded[region] = None
elif len(region.entrances) == 1: # for holes
access_region = next(x.parent_region for x in region.entrances
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
or x.parent_region.name == 'Sewer Drop')
if access_region.name == 'Sewer Drop':
access_region = next(x.parent_region for x in access_region.entrances)
if (access_region.name in world.inaccessible_regions[player] and
region.name not in world.enabled_entrances[player]):
excluded[region] = None
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect, idx = {}, 0
all_regions = set()
for sector in builder.sectors:
for door in sector.outstanding_doors:
doors_to_connect[door.name] = door, idx
idx += 1
all_regions.update(sector.regions)
finished = False
# flag if standard and this is hyrule castle
paths = determine_paths_for_dungeon(world, player, all_regions, name)
proposed_map = create_random_proposal(doors_to_connect, world, player)
itr = 0
hash_code = proposal_hash(doors_to_connect, proposed_map)
hash_code_set = set()
start = time.time()
while not finished:
if itr > 1000:
elasped = time.time() - start
raise GenerationException(f'Generation taking too long. {elasped}. Ref {name}')
if hash_code in hash_code_set:
proposed_map = create_random_proposal(doors_to_connect, world, player)
hash_code = proposal_hash(doors_to_connect, proposed_map)
if hash_code not in hash_code_set:
hash_code_set.add(hash_code)
explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect,
world, player)
if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions,
paths, entrance_regions, world, player):
finished = True
else:
proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect,
hash_code_set, world, player)
itr += 1
return proposed_map
def create_random_proposal(doors_to_connect, world, player):
logger = logging.getLogger('')
hooks = [Hook.North, Hook.South, Hook.East, Hook.West, Hook.Stairs]
primary_bucket = collections.defaultdict(list)
secondary_bucket = collections.defaultdict(list)
for name, door in doors_to_connect.items():
door, idx = door
primary_bucket[hook_from_door(door)].append(door)
secondary_bucket[hook_from_door(door)].append(door)
proposal = {}
while True:
hooks_left, left = [], 0
for hook in hooks:
hook_len = len(primary_bucket[hook])
if hook_len > 0:
hooks_left.append(hook)
left += hook_len
if left == 0:
return proposal
next_hook = random.choice(hooks_left)
primary_door = random.choice(primary_bucket[next_hook])
opp_hook, secondary_door = type_map[next_hook], None
while (secondary_door is None or secondary_door == primary_door
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
primary_door, secondary_door, world, player)):
secondary_door = random.choice(secondary_bucket[opp_hook])
proposal[primary_door] = secondary_door
primary_bucket[next_hook].remove(primary_door)
secondary_bucket[opp_hook].remove(secondary_door)
if not world.decoupledoors[player]:
proposal[secondary_door] = primary_door
primary_bucket[opp_hook].remove(secondary_door)
secondary_bucket[next_hook].remove(primary_door)
logger.debug(f'Linking {primary_door.name} <-> {secondary_door.name}')
else:
logger.debug(f'Linking {primary_door.name} -> {secondary_door.name}')
def decouple_check(primary_list, secondary_list, primary_door, secondary_door, world, player):
if world.decoupledoors[player] and len(primary_list) == 2 and len(secondary_list) == 2:
primary_alone = next(d for d in primary_list if d != primary_door)
secondary_alone = next(d for d in secondary_list if d != secondary_door)
return primary_alone == secondary_alone
return False
def proposal_hash(doors_to_connect, proposed_map):
hash_code = ''
for name, door_pair in doors_to_connect.items():
door, idx = door_pair
hash_code += str(idx) + str(doors_to_connect[proposed_map[door].name][1])
return hash_code
def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_set, world, player):
logger = logging.getLogger('')
hash_code, itr = None, 0
while hash_code is None or hash_code in hash_code_set:
if itr > 10:
proposed_map = create_random_proposal(doors_to_connect, world, player)
hash_code = proposal_hash(doors_to_connect, proposed_map)
return proposed_map, hash_code
visited_bucket = collections.defaultdict(list)
unvisted_bucket = collections.defaultdict(list)
visited_choices = []
unvisted_count = 0
for door_one, door_two in proposed_map.items():
if door_one in explored_state.visited_doors:
visited_bucket[hook_from_door(door_one)].append(door_one)
visited_choices.append(door_one)
else:
unvisted_bucket[hook_from_door(door_one)].append(door_one)
unvisted_count += 1
if unvisted_count == 0:
# something is wrong beyond connectedness, crystal switch puzzle or bk layout - reshuffle
proposed_map = create_random_proposal(doors_to_connect, world, player)
hash_code = proposal_hash(doors_to_connect, proposed_map)
return proposed_map, hash_code
attempt, opp_hook = None, None
opp_hook_len = 0
while opp_hook_len == 0:
attempt = random.choice(visited_choices)
opp_hook = type_map[hook_from_door(attempt)]
opp_hook_len = len(unvisted_bucket[opp_hook])
unvisted_bucket[opp_hook].sort(key=lambda d: d.name)
new_door = random.choice(unvisted_bucket[opp_hook])
old_target = proposed_map[attempt]
proposed_map[attempt] = new_door
if not world.decoupledoors[player]:
old_attempt = proposed_map[new_door]
else:
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
proposed_map[old_attempt] = old_target
if not world.decoupledoors[player]:
proposed_map[old_target] = old_attempt
proposed_map[new_door] = attempt
hash_code = proposal_hash(doors_to_connect, proposed_map)
itr += 1
if not world.decoupledoors[player]:
logger.debug(f'Re-linking {attempt.name} <-> {new_door.name}')
logger.debug(f'Re-linking {old_attempt.name} <-> {old_target.name}')
else:
logger.debug(f'Re-Linking {attempt.name} -> {new_door.name}')
logger.debug(f'Re-Linking {old_attempt.name} -> {old_target.name}')
hash_code_set.add(hash_code)
return proposed_map, hash_code
def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player):
start = ExplorationState(dungeon=name)
original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map,
all_regions, valid_doors, world, player)
return original_state
def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions,
paths, entrance_regions, world, player):
all_visited = set()
all_visited.update(exploration_state.visited_blue)
all_visited.update(exploration_state.visited_orange)
if len(all_regions.difference(all_visited)) > 0:
return False
if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player):
return False
return True
def determine_if_bk_needed(sector, split_dungeon, bk_special, world, player):
if not split_dungeon or bk_special:
for region in sector.regions:
for ext in region.exits:
door = world.check_for_door(ext.name, player)
if door is not None and door.bigKey:
return True
return False
def check_for_special(regions):
for region in regions:
for loc in region.locations:
if loc.forced_big_key():
return True
return False
def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player):
for path in paths:
if type(path) is tuple:
target = path[1]
start_regions = []
for region in all_regions:
if path[0] == region.name:
start_regions.append(region)
break
else:
target = path
start_regions = entrance_regions
if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player):
return False
return True
def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player):
target_regions = set()
if type(target) is not list:
for region in all_regions:
if target == region.name:
target_regions.add(region)
break
else:
for region in all_regions:
if region.name in target:
target_regions.add(region)
start = ExplorationState(dungeon=name)
original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions,
valid_doors, world, player)
for exp_door in original_state.unattached_doors:
if not exp_door.door.blocked or exp_door.door.trapFlag != 0:
return True # outstanding connection possible
for target in target_regions:
if original_state.visited_at_all(target):
return True
return False # couldn't find an outstanding door or the target
boss_path_checks = ['Eastern Boss', 'Desert Boss', 'Hera Boss', 'Tower Agahnim 1', 'PoD Boss', 'Swamp Boss',
'Skull Boss', 'Ice Boss', 'Mire Boss', 'TR Boss', 'GT Agahnim 2']
# pinball is allowed to orphan you
drop_path_checks = ['Skull Pot Circle', 'Skull Left Drop', 'Skull Back Drop', 'Sewers Rat Path']
def determine_paths_for_dungeon(world, player, all_regions, name):
all_r_names = set(x.name for x in all_regions)
paths = []
non_hole_portals = []
for portal in world.dungeon_portals[player]:
if portal.door.entrance.parent_region in all_regions:
non_hole_portals.append(portal.door.entrance.parent_region.name)
if portal.destination:
paths.append(portal.door.entrance.parent_region.name)
if world.mode[player] == 'standard' and name == 'Hyrule Castle':
paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names:
paths.append('Thieves Attic Window')
for boss in boss_path_checks:
if boss in all_r_names:
paths.append(boss)
if 'Thieves Boss' in all_r_names:
paths.append('Thieves Boss')
if world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind':
paths.append(('Thieves Blind\'s Cell', 'Thieves Boss'))
for drop_check in drop_path_checks:
if drop_check in all_r_names:
paths.append((drop_check, non_hole_portals))
return paths
def convert_regions(region_names, world, player):
region_list = []
for name in region_names:
region_list.append(world.get_region(name, player))
return region_list
type_map = {
Hook.Stairs: Hook.Stairs,
Hook.North: Hook.South,
Hook.South: Hook.North,
Hook.West: Hook.East,
Hook.East: Hook.West
}
def connect_doors(a, b):
# Return on unsupported types.
if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Interior, DoorType.Logical]:
return
# Connect supported types
if a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
if a.blocked:
connect_one_way(b.entrance, a.entrance)
elif b.blocked:
connect_one_way(a.entrance, b.entrance)
else:
connect_two_way(a.entrance, b.entrance)
dep_doors, target = [], None
if len(a.dependents) > 0:
dep_doors, target = a.dependents, b
elif len(b.dependents) > 0:
dep_doors, target = b.dependents, a
if target is not None:
target_region = target.entrance.parent_region
for dep in dep_doors:
connect_simple_door(dep, target_region)
return
# If we failed to account for a type, panic
raise RuntimeError('Unknown door type ' + a.type.name)
def connect_doors_one_way(a, b):
# Return on unsupported types.
if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Interior, DoorType.Logical]:
return
# Connect supported types
if a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
if not a.blocked:
connect_one_way(a.entrance, b.entrance)
dep_doors, target = [], None
if len(a.dependents) > 0:
dep_doors, target = a.dependents, b
if target is not None:
target_region = target.entrance.parent_region
for dep in dep_doors:
connect_simple_door(dep, target_region)
return
# If we failed to account for a type, panic
raise RuntimeError('Unknown door type ' + a.type.name)
def connect_two_way(entrance, ext):
# if these were already connected somewhere, remove the backreference
if entrance.connected_region is not None:
entrance.connected_region.entrances.remove(entrance)
if ext.connected_region is not None:
ext.connected_region.entrances.remove(ext)
entrance.connect(ext.parent_region)
ext.connect(entrance.parent_region)
if entrance.parent_region.dungeon:
ext.parent_region.dungeon = entrance.parent_region.dungeon
x = entrance.door
y = ext.door
if x is not None:
x.dest = y
if y is not None:
y.dest = x
def connect_one_way(entrance, ext):
# if these were already connected somewhere, remove the backreference
if entrance.connected_region is not None:
entrance.connected_region.entrances.remove(entrance)
entrance.connect(ext.parent_region)
if entrance.parent_region.dungeon:
ext.parent_region.dungeon = entrance.parent_region.dungeon
x = entrance.door
if x is not None:
x.dest = ext.door
def connect_simple_door(exit_door, region):
exit_door.entrance.connect(region)
exit_door.dest = region
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
class ExplorationState(object):
def __init__(self, init_crystal=CrystalBarrier.Orange, dungeon=None):
self.unattached_doors = []
self.avail_doors = []
self.event_doors = []
self.visited_orange = []
self.visited_blue = []
self.visited_doors = set()
self.events = set()
self.crystal = init_crystal
# key region stuff
self.door_krs = {}
# key validation stuff
self.small_doors = []
self.big_doors = []
self.opened_doors = []
self.big_key_opened = False
self.big_key_special = False
self.found_locations = []
self.ttl_locations = 0
self.used_locations = 0
self.key_locations = 0
self.used_smalls = 0
self.bk_found = set()
self.non_door_entrances = []
self.dungeon = dungeon
self.pinball_used = False
self.prize_door_set = {}
self.prize_doors = []
self.prize_doors_opened = False
def copy(self):
ret = ExplorationState(dungeon=self.dungeon)
ret.unattached_doors = list(self.unattached_doors)
ret.avail_doors = list(self.avail_doors)
ret.event_doors = list(self.event_doors)
ret.visited_orange = list(self.visited_orange)
ret.visited_blue = list(self.visited_blue)
ret.events = set(self.events)
ret.crystal = self.crystal
ret.door_krs = self.door_krs.copy()
ret.small_doors = list(self.small_doors)
ret.big_doors = list(self.big_doors)
ret.opened_doors = list(self.opened_doors)
ret.big_key_opened = self.big_key_opened
ret.big_key_special = self.big_key_special
ret.ttl_locations = self.ttl_locations
ret.key_locations = self.key_locations
ret.used_locations = self.used_locations
ret.used_smalls = self.used_smalls
ret.found_locations = list(self.found_locations)
ret.bk_found = set(self.bk_found)
ret.non_door_entrances = list(self.non_door_entrances)
ret.dungeon = self.dungeon
ret.pinball_used = self.pinball_used
ret.prize_door_set = dict(self.prize_door_set)
ret.prize_doors = list(self.prize_doors)
ret.prize_doors_opened = self.prize_doors_opened
return ret
def next_avail_door(self):
self.avail_doors.sort(key=lambda x: 0 if x.flag else 1 if x.door.bigKey else 2)
exp_door = self.avail_doors.pop()
self.crystal = exp_door.crystal
return exp_door
def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False):
if region.type != RegionType.Dungeon:
self.crystal = CrystalBarrier.Orange
if self.crystal == CrystalBarrier.Either:
if region not in self.visited_blue:
self.visited_blue.append(region)
if region not in self.visited_orange:
self.visited_orange.append(region)
elif self.crystal == CrystalBarrier.Orange:
self.visited_orange.append(region)
elif self.crystal == CrystalBarrier.Blue:
self.visited_blue.append(region)
if region.type == RegionType.Dungeon:
for location in region.locations:
if key_checks and location not in self.found_locations:
if location.forced_item and 'Small Key' in location.item.name:
self.key_locations += 1
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
self.ttl_locations += 1
if location not in self.found_locations: # todo: special logic for TT Boss?
self.found_locations.append(location)
if not bk_flag:
self.bk_found.add(location)
if location.name in dungeon_events and location.name not in self.events:
if self.flooded_key_check(location):
self.perform_event(location.name, key_region)
if location.name in flooded_keys_reverse.keys() and self.location_found(
flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region)
if '- Prize' in location.name:
self.prize_received = True
def flooded_key_check(self, location):
if location.name not in flooded_keys.keys():
return True
return flooded_keys[location.name] in [x.name for x in self.found_locations]
def location_found(self, location_name):
for l in self.found_locations:
if l.name == location_name:
return True
return False
def perform_event(self, location_name, key_region):
self.events.add(location_name)
queue = collections.deque(self.event_doors)
while len(queue) > 0:
exp_door = queue.popleft()
if exp_door.door.req_event == location_name:
self.avail_doors.append(exp_door)
self.event_doors.remove(exp_door)
if key_region is not None:
d_name = exp_door.door.name
if d_name not in self.door_krs.keys():
self.door_krs[d_name] = key_region
def add_all_entrance_doors_check_unattached(self, region, world, player):
door_list = [x for x in get_doors(world, region, player) if x.type in [DoorType.Normal, DoorType.SpiralStairs]]
door_list.extend(get_entrance_doors(world, region, player))
for door in door_list:
if self.can_traverse(door):
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors)
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
for entrance in region.entrances:
door = world.check_for_door(entrance.name, player)
if door is None:
self.non_door_entrances.append(entrance)
def add_all_doors_check_unattached(self, region, world, player):
for door in get_doors(world, region, player):
if self.can_traverse(door):
if door.controller is not None:
door = door.controller
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors)
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, flag, world, player, exception):
for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door)
if door.blocked and exception(door):
self.pinball_used = True
if self.can_traverse(door, exception):
if door.controller is not None:
door = door.controller
if door.dest is None and door not in proposed_map.keys() and door.name in valid_doors:
if not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors, flag)
else:
other = self.find_door_in_list(door, self.unattached_doors)
if self.crystal != other.crystal:
other.crystal = CrystalBarrier.Either
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors, flag)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag)
def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, flag, world, player):
for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door)
if self.can_traverse_ignore_traps(door):
if door.controller is not None:
door = door.controller
if door.dest is None and door not in proposed_map.keys() and door.name in valid_doors:
if not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors, flag)
else:
other = self.find_door_in_list(door, self.unattached_doors)
if self.crystal != other.crystal:
other.crystal = CrystalBarrier.Either
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors, flag)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag)
def add_all_doors_check_proposed_traps(self, region, proposed_traps, world, player):
for door in get_doors(world, region, player):
if self.can_traverse_ignore_traps(door) and door not in proposed_traps:
if door.controller is not None:
door = door.controller
if door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors, False)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, False)
def add_all_doors_check_key_region(self, region, key_region, world, player):
for door in get_doors(world, region, player):
if self.can_traverse(door):
if door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
if door.name not in self.door_krs.keys():
self.door_krs[door.name] = key_region
else:
if door.name not in self.door_krs.keys():
self.door_krs[door.name] = key_region
def add_all_doors_check_keys(self, region, key_door_proposal, world, player):
for door in get_doors(world, region, player):
if self.can_traverse(door):
if door.controller:
door = door.controller
if door in key_door_proposal and door not in self.opened_doors:
if not self.in_door_list(door, self.small_doors):
self.append_door_to_list(door, self.small_doors)
elif (door.bigKey or door.name in special_big_key_doors) and not self.big_key_opened:
if not self.in_door_list(door, self.big_doors):
self.append_door_to_list(door, self.big_doors)
elif door.req_event is not None and door.req_event not in self.events:
if not self.in_door_list(door, self.event_doors):
self.append_door_to_list(door, self.event_doors)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
def visited(self, region):
if self.crystal == CrystalBarrier.Either:
return region in self.visited_blue and region in self.visited_orange
elif self.crystal == CrystalBarrier.Orange:
return region in self.visited_orange
elif self.crystal == CrystalBarrier.Blue:
return region in self.visited_blue
return False
def visited_at_all(self, region):
return region in self.visited_blue or region in self.visited_orange
def found_forced_bk(self):
for location in self.found_locations:
if location.forced_big_key():
return True
return False
def can_traverse(self, door, exception=None):
if door.blocked:
return exception(door) if exception else False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def can_traverse_ignore_traps(self, door):
if door.blocked and door.trapFlag == 0:
return False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def count_locations_exclude_specials(self, world, player):
return count_locations_exclude_big_chest(self.found_locations, world, player)
def validate(self, door, region, world, player):
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon,
world, player)
def in_door_list(self, door, door_list):
for d in door_list:
if d.door == door and d.crystal == self.crystal:
return True
return False
@staticmethod
def in_door_list_ic(door, door_list):
for d in door_list:
if d.door == door:
return True
return False
@staticmethod
def find_door_in_list(door, door_list):
for d in door_list:
if d.door == door:
return d
return None
def append_door_to_list(self, door, door_list, flag=False):
if door.crystal == CrystalBarrier.Null:
door_list.append(ExplorableDoor(door, self.crystal, flag))
else:
door_list.append(ExplorableDoor(door, door.crystal, flag))
def key_door_sort(self, d):
if d.door.smallKey:
if d.door in self.opened_doors:
return 1
else:
return 0
return 2
def count_locations_exclude_big_chest(locations, world, player):
cnt = 0
for loc in locations:
if ('- Big Chest' not in loc.name and not loc.forced_item and not reserved_location(loc, world, player)
and not prize_or_event(loc) and not blind_boss_unavail(loc, locations, world, player)):
cnt += 1
return cnt
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
def reserved_location(loc, world, player):
return hasattr(world, 'item_pool_config') and loc.name in world.item_pool_config.reserved_locations[player]
def blind_boss_unavail(loc, locations, world, player):
if loc.name == "Thieves' Town - Boss":
return (loc.parent_region.dungeon.boss.name == 'Blind' and
(not any(x for x in locations if x.name == 'Suspicious Maiden') or
(world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
not any(x for x in locations if x.name == 'Attic Cracked Floor'))))
return False
class ExplorableDoor(object):
def __init__(self, door, crystal, flag):
self.door = door
self.crystal = crystal
self.flag = flag
def __str__(self):
return str(self.__unicode__())
def __unicode__(self):
return '%s (%s)' % (self.door.name, self.crystal.name)
def extend_reachable_state_improved(search_regions, state, proposed_map, all_regions, valid_doors, bk_flag, world, player, exception):
local_state = state.copy()
for region in search_regions:
local_state.visit_region(region)
local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, False, world, player, exception)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
if explorable_door.door.bigKey:
if bk_flag:
big_not_found = (not special_big_key_found(local_state) if local_state.big_key_special
else local_state.count_locations_exclude_specials(world, player) == 0)
if big_not_found:
continue # we can't open this door
if explorable_door.door in proposed_map:
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
else:
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None:
if valid_region_to_explore_in_regions(connect_region, all_regions, world, player) and not local_state.visited(
connect_region):
flag = explorable_door.flag or explorable_door.door.bigKey
local_state.visit_region(connect_region, bk_flag=flag)
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, flag, world, player, exception)
return local_state
def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player):
local_state = state.copy()
for region in search_regions:
local_state.visit_region(region)
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
if explorable_door.door in proposed_map:
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
else:
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None:
if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player)
and not local_state.visited(connect_region)):
local_state.visit_region(connect_region)
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player)
return local_state
def special_big_key_found(state):
for location in state.found_locations:
if location.forced_item and location.forced_item.bigkey:
return True
return False
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]
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard'))
def valid_region_to_explore(region, name, world, player):
if region is None:
return False
return ((region.type == RegionType.Dungeon and region.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):
res = []
for ext in region.exits:
door = world.check_for_door(ext.name, player)
if door is not None:
res.append(door)
return res
def get_entrance_doors(world, region, player):
res = []
for ext in region.entrances:
door = world.check_for_door(ext.name, player)
if door is not None:
res.append(door)
return res
class GenerationException(Exception):
pass