Standard throne room changes

This commit is contained in:
aerinon
2022-06-14 12:29:57 -06:00
parent e9433e56c0
commit 5dce2daa0b
6 changed files with 164 additions and 47 deletions

View File

@@ -14,7 +14,7 @@ from Items import ItemFactory
from RoomData import DoorKind, PairedDoor, reset_rooms from RoomData import DoorKind, PairedDoor, reset_rooms
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException, connect_doors
from DungeonGenerator import valid_region_to_explore as valid_region_to_explore_lim from DungeonGenerator import valid_region_to_explore as valid_region_to_explore_lim
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
from Utils import ncr, kth_combination from Utils import ncr, kth_combination
@@ -685,6 +685,13 @@ def create_dungeon_entrances(world, player):
choice = random.choice(filtered_choices) choice = random.choice(filtered_choices)
r_name = portal.door.entrance.parent_region.name r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name) split_map[key][choice].append(r_name)
elif key == 'Hyrule Castle' and world.mode[player] == 'standard':
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
choice = 'Sewers' if portal_name == 'Sanctuary' else 'Dungeon'
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
entrance_map[key].append(r_name)
else: else:
for portal_name in portal_list: for portal_name in portal_list:
portal = world.get_portal(portal_name, player) portal = world.get_portal(portal_name, player)
@@ -774,7 +781,8 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon")) logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon"))
while len(sector_queue) > 0: while len(sector_queue) > 0:
builder = sector_queue.popleft() builder = sector_queue.popleft()
split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods') split_dungeon = (builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods')
or (builder.name.startswith('Hyrule Castle') and world.mode[player] == 'standard'))
name = builder.name name = builder.name
if split_dungeon: if split_dungeon:
name = ' '.join(builder.name.split(' ')[:-1]) name = ' '.join(builder.name.split(' ')[:-1])
@@ -782,6 +790,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
del dungeon_builders[builder.name] del dungeon_builders[builder.name]
continue continue
origin_list = list(builder.entrance_list) origin_list = list(builder.entrance_list)
find_standard_origins(builder, recombinant_builders, origin_list)
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player) split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player)
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player): if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
@@ -798,7 +807,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
builder.master_sector = ds builder.master_sector = ds
builder.layout_starts = origin_list if len(builder.entrance_list) <= 0 else builder.entrance_list builder.layout_starts = origin_list if len(builder.entrance_list) <= 0 else builder.entrance_list
last_key = None last_key = None
combine_layouts(recombinant_builders, dungeon_builders, entrances_map) combine_layouts(recombinant_builders, dungeon_builders, entrances_map, world, player)
world.dungeon_layouts[player] = {} world.dungeon_layouts[player] = {}
for builder in dungeon_builders.values(): for builder in dungeon_builders.values():
builder.entrance_list = builder.layout_starts = builder.path_entrances = find_accessible_entrances(world, player, builder) builder.entrance_list = builder.layout_starts = builder.path_entrances = find_accessible_entrances(world, player, builder)
@@ -872,6 +881,14 @@ def add_shuffled_entrances(sectors, region_list, entrance_list):
entrance_list.append(region.name) entrance_list.append(region.name)
def find_standard_origins(builder, recomb_builders, origin_list):
if builder.name == 'Hyrule Castle Sewers':
throne_door = recomb_builders['Hyrule Castle'].throne_door
sewer_entrance = throne_door.entrance.parent_region.name
if sewer_entrance not in origin_list:
origin_list.append(sewer_entrance)
def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key): def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key):
for sector in sectors: for sector in sectors:
for region in sector.regions: for region in sector.regions:
@@ -1378,7 +1395,7 @@ def merge_sectors(all_sectors, world, player):
# those with split region starts like Desert/Skull combine for key layouts # those with split region starts like Desert/Skull combine for key layouts
def combine_layouts(recombinant_builders, dungeon_builders, entrances_map): def combine_layouts(recombinant_builders, dungeon_builders, entrances_map, world, player):
for recombine in recombinant_builders.values(): for recombine in recombinant_builders.values():
queue = deque(dungeon_builders.values()) queue = deque(dungeon_builders.values())
while len(queue) > 0: while len(queue) > 0:
@@ -1390,6 +1407,10 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
recombine.master_sector.name = recombine.name recombine.master_sector.name = recombine.name
else: else:
recombine.master_sector.regions.extend(builder.master_sector.regions) recombine.master_sector.regions.extend(builder.master_sector.regions)
if recombine.name == 'Hyrule Castle':
recombine.master_sector.regions.extend(recombine.throne_sector.regions)
throne_n = world.get_door('Hyrule Castle Throne Room N', player)
connect_doors(throne_n, recombine.throne_door)
recombine.layout_starts = list(entrances_map[recombine.name]) recombine.layout_starts = list(entrances_map[recombine.name])
dungeon_builders[recombine.name] = recombine dungeon_builders[recombine.name] = recombine
@@ -1487,11 +1508,14 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num) proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num)
# eliminate start region if portal marked as destination # eliminate start region if portal marked as destination
std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle'
excluded = {} excluded = {}
for region in start_regions: for region in start_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None) portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination: if portal and portal.destination:
excluded[region] = None excluded[region] = None
if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'):
excluded[region] = None
start_regions = [x for x in start_regions if x not in excluded.keys()] start_regions = [x for x in start_regions if x not in excluded.keys()]
key_layout = build_key_layout(builder, start_regions, proposal, world, player) key_layout = build_key_layout(builder, start_regions, proposal, world, player)

View File

@@ -43,7 +43,10 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
for sector in builder.sectors: for sector in builder.sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
doors_to_connect[door.name] = door doors_to_connect[door.name] = door
all_regions.update(sector.regions) if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle Dungeon':
all_regions.update([x for x in sector.regions if x.name != 'Hyrule Castle Behind Tapestry'])
else:
all_regions.update(sector.regions)
bk_special |= check_for_special(sector.regions) bk_special |= check_for_special(sector.regions)
bk_needed = False bk_needed = False
for sector in builder.sectors: for sector in builder.sectors:
@@ -91,18 +94,27 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
p_region = portal.door.entrance.connected_region p_region = portal.door.entrance.connected_region
access_region = next(x.parent_region for x in p_region.entrances access_region = next(x.parent_region for x in p_region.entrances
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]) 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])
or (world.mode[player] == 'standard' and access_region.name != 'Hyrule Castle Courtyard'
and 'Hyrule Castle' in builder.name)):
excluded[region] = None
else: # for non-portals, holes and sewers in std
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'), None)
if access_region is None:
if builder.sewers_access is None:
excluded[region] = None
else:
if access_region.name == 'Sewer Drop':
if world.mode[player] == 'standard' and (builder.sewers_access is None
or builder.sewers_access.entrance.parent_region != region):
excluded[region] = None
access_region = next(x.parent_region for x in access_region.entrances)
if (access_region.name in world.inaccessible_regions[player] and if (access_region.name in world.inaccessible_regions[player] and
region.name not in world.enabled_entrances[player]): region.name not in world.enabled_entrances[player]):
excluded[region] = None 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()] entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect = {} doors_to_connect = {}
all_regions = set() all_regions = set()
@@ -143,7 +155,11 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, all_regions, proposed_map, dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, all_regions, proposed_map,
doors_to_connect, bk_needed, bk_special, world, player) doors_to_connect, bk_needed, bk_special, world, player)
dungeon_cache[depth] = dungeon, hangers, hooks dungeon_cache[depth] = dungeon, hangers, hooks
valid = check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, if len(proposed_map) != len(doors_to_connect) and builder.name == 'Hyrule Castle Dungeon':
check_regions = all_regions.difference({world.get_region('Hyrule Castle Behind Tapestry', player)})
else:
check_regions = all_regions
valid = check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, check_regions,
bk_needed, bk_special, paths, entrance_regions, world, player) bk_needed, bk_special, paths, entrance_regions, world, player)
else: else:
dungeon, hangers, hooks = dungeon_cache[depth] dungeon, hangers, hooks = dungeon_cache[depth]
@@ -565,9 +581,15 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
non_hole_portals.append(portal.door.entrance.parent_region.name) non_hole_portals.append(portal.door.entrance.parent_region.name)
if portal.destination: if portal.destination:
paths.append(portal.door.entrance.parent_region.name) paths.append(portal.door.entrance.parent_region.name)
if world.mode[player] == 'standard' and name == 'Hyrule Castle': if world.mode[player] == 'standard':
paths.append('Hyrule Dungeon Cellblock') if name == 'Hyrule Castle':
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if name == 'Hyrule Castle Sewers':
paths.append('Sanctuary')
if name == 'Hyrule Castle Dungeon':
paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room'))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window') paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names: elif 'Thieves Attic Window' in all_r_names:
@@ -1206,6 +1228,11 @@ class DungeonBuilder(object):
self.split_dungeon_map = None self.split_dungeon_map = None
self.exception_list = [] self.exception_list = []
self.throne_door = None
self.throne_sector = None
self.chosen_lobby = None
self.sewers_access = None
def polarity_complement(self): def polarity_complement(self):
pol = Polarity() pol = Polarity()
for sector in self.sectors: for sector in self.sectors:
@@ -1258,7 +1285,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
for r_name in dungeon_boss_sectors[key]: for r_name in dungeon_boss_sectors[key]:
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole)
if key == 'Hyrule Castle' and world.mode[player] == 'standard': if key == 'Hyrule Castle' and world.mode[player] == 'standard':
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
candidate_sectors, global_pole) candidate_sectors, global_pole)
if key == 'Thieves Town' and world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind': if key == 'Thieves Town' and world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind':
@@ -2077,16 +2104,18 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builde
while len(problem_builders) > 0: while len(problem_builders) > 0:
for name, builder in problem_builders.items(): for name, builder in problem_builders.items():
candidates = find_branching_candidates(builder, neutral_choices, builder_info) candidates = find_branching_candidates(builder, neutral_choices, builder_info)
valid, choice = False, None valid, choice, package = False, None, None
while not valid: while not valid:
if len(candidates) <= 0: if len(candidates) <= 0:
raise GenerationException('Cross Dungeon Builder: Complex branch problems: %s' % name) raise GenerationException('Cross Dungeon Builder: Complex branch problems: %s' % name)
choice = random.choice(candidates) choice, package = random.choice(candidates)
candidates.remove(choice) candidates.remove((choice, package))
valid = global_pole.is_valid_choice(dungeon_map, builder, choice) and valid_polarized_assignment(builder, choice) valid = global_pole.is_valid_choice(dungeon_map, builder, choice) and valid_polarized_assignment(builder, choice)
neutral_choices.remove(choice) neutral_choices.remove(choice)
for sector in choice: for sector in choice:
assign_sector(sector, builder, polarized_sectors, global_pole) assign_sector(sector, builder, polarized_sectors, global_pole)
if package:
builder.throne_door, builder.throne_sector, builder.chosen_lobby = package
builder.unfulfilled.clear() builder.unfulfilled.clear()
problem_builders = identify_branching_issues(problem_builders, builder_info) problem_builders = identify_branching_issues(problem_builders, builder_info)
@@ -2107,16 +2136,21 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builde
chosen_sectors = defaultdict(list) chosen_sectors = defaultdict(list)
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
chosen_sectors[choice].extend(neutral_choices[i]) chosen_sectors[choice].extend(neutral_choices[i])
all_valid = True all_valid, package_map = True, {}
for name, sector_list in chosen_sectors.items(): for name, sector_list in chosen_sectors.items():
if not valid_assignment(dungeon_map[name], sector_list, builder_info): flag, package = valid_assignment(dungeon_map[name], sector_list, builder_info)
if not flag:
all_valid = False all_valid = False
break break
if package:
package_map[dungeon_map[name]] = package
if all_valid: if all_valid:
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[choice] builder = dungeon_map[choice]
for sector in neutral_choices[i]: for sector in neutral_choices[i]:
assign_sector(sector, builder, polarized_sectors, global_pole) assign_sector(sector, builder, polarized_sectors, global_pole)
if builder in package_map:
builder.throne_door, builder.throne_sector, builder.chosen_lobby = package_map[builder]
tries += 1 tries += 1
@@ -2629,9 +2663,9 @@ def weed_candidates(builder, candidates, best_charge):
def find_branching_candidates(builder, neutral_choices, builder_info): def find_branching_candidates(builder, neutral_choices, builder_info):
candidates = [] candidates = []
for choice in neutral_choices: for choice in neutral_choices:
resolved, problem_list = check_for_valid_layout(builder, choice, builder_info) resolved, problem_list, package = check_for_valid_layout(builder, choice, builder_info)
if resolved: if resolved:
candidates.append(choice) candidates.append((choice, package))
return candidates return candidates
@@ -2786,13 +2820,13 @@ def categorize_groupings(sectors):
def valid_assignment(builder, sector_list, builder_info): def valid_assignment(builder, sector_list, builder_info):
if not valid_entrance(builder, sector_list, builder_info): if not valid_entrance(builder, sector_list, builder_info):
return False return False, None
if not valid_c_switch(builder, sector_list): if not valid_c_switch(builder, sector_list):
return False return False, None
if not valid_polarized_assignment(builder, sector_list): if not valid_polarized_assignment(builder, sector_list):
return False return False, None
resolved, problems = check_for_valid_layout(builder, sector_list, builder_info) resolved, problems, package = check_for_valid_layout(builder, sector_list, builder_info)
return resolved return resolved, package
def valid_entrance(builder, sector_list, builder_info): def valid_entrance(builder, sector_list, builder_info):
@@ -2898,31 +2932,56 @@ def assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info):
chosen_sectors = defaultdict(list) chosen_sectors = defaultdict(list)
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
chosen_sectors[choice].append(neutral_sector_list[i]) chosen_sectors[choice].append(neutral_sector_list[i])
all_valid = True all_valid, package_map = True, {}
for name, sector_list in chosen_sectors.items(): for name, sector_list in chosen_sectors.items():
if not valid_assignment(dungeon_map[name], sector_list, builder_info): flag, package = valid_assignment(dungeon_map[name], sector_list, builder_info)
if not flag:
all_valid = False all_valid = False
break break
if package:
package_map[dungeon_map[name]] = package
if all_valid: if all_valid:
for name, sector_list in chosen_sectors.items(): for name, sector_list in chosen_sectors.items():
builder = dungeon_map[name] builder = dungeon_map[name]
for sector in sector_list: for sector in sector_list:
assign_sector(sector, builder, neutral_sectors, global_pole) assign_sector(sector, builder, neutral_sectors, global_pole)
if builder in package_map:
builder.throne_door, builder.throne_sector, builder.chosen_lobby = package_map[builder]
tries += 1 tries += 1
def split_dungeon_builder(builder, split_list, builder_info): def split_dungeon_builder(builder, split_list, builder_info):
ents, splits, c_tuple, world, player = builder_info
if builder.split_dungeon_map and len(builder.exception_list) == 0: if builder.split_dungeon_map and len(builder.exception_list) == 0:
for name, proposal in builder.valid_proposal.items(): for name, proposal in builder.valid_proposal.items():
builder.split_dungeon_map[name].valid_proposal = proposal builder.split_dungeon_map[name].valid_proposal = proposal
if builder.name == 'Hyrule Castle':
builder.chosen_lobby.outstanding_doors.remove(builder.throne_door)
builder.throne_sector.outstanding_doors.remove(world.get_door('Hyrule Castle Throne Room N', player))
return builder.split_dungeon_map # we made this earlier in gen, just use it return builder.split_dungeon_map # we made this earlier in gen, just use it
attempts, comb_w_replace, merge_attempt, merge_limit = 0, None, 0, len(split_list) - 1 attempts, comb_w_replace, merge_attempt, merge_limit = 0, None, 0, len(split_list) - 1
while attempts < 5: # does not solve coin flips 3% of the time while attempts < 5: # does not solve coin flips 3% of the time
try: try:
candidate_sectors = dict.fromkeys(builder.sectors) candidate_sectors = dict.fromkeys(builder.sectors)
global_pole = GlobalPolarity(candidate_sectors) if builder.name == 'Hyrule Castle':
throne_sector = find_sector('Hyrule Castle Throne Room', candidate_sectors)
chosen_lobbies = {r_name for x in split_list.values() for r_name in x}
choices = {}
for sector in candidate_sectors:
if sector.adj_outflow() > 1 and sector != throne_sector:
for door in sector.outstanding_doors:
if door.direction == Direction.South and door.entrance.parent_region not in chosen_lobbies:
choices[door] = sector
chosen_door = random.choice(list(choices.keys()))
split_list['Sewers'].append(chosen_door.entrance.parent_region.name)
choices[chosen_door].outstanding_doors.remove(chosen_door)
builder.throne_door = chosen_door
builder.throne_sector = throne_sector
builder.chosen_lobby = choices[chosen_door]
throne_sector.outstanding_doors.remove(world.get_door('Hyrule Castle Throne Room N', player))
global_pole = GlobalPolarity(candidate_sectors)
dungeon_map, sub_builder, merge_keys = {}, None, [] dungeon_map, sub_builder, merge_keys = {}, None, []
if merge_attempt > 0: if merge_attempt > 0:
candidates = [] candidates = []
@@ -2932,7 +2991,6 @@ def split_dungeon_builder(builder, split_list, builder_info):
continue continue
elif len(split_entrances) <= 0: elif len(split_entrances) <= 0:
continue continue
ents, splits, c_tuple, world, player = builder_info
r_name = split_entrances[0] r_name = split_entrances[0]
p = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name), None) p = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name), None)
if p and not p.deadEnd: if p and not p.deadEnd:
@@ -2953,6 +3011,13 @@ def split_dungeon_builder(builder, split_list, builder_info):
sub_builder.all_entrances = list(split_entrances) sub_builder.all_entrances = list(split_entrances)
for r_name in split_entrances: for r_name in split_entrances:
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole) assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
if builder.name == 'Hyrule Castle':
assign_sector(find_sector('Hyrule Castle Throne Room', candidate_sectors),
dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole)
assign_sector(find_sector('Hyrule Dungeon Cellblock', candidate_sectors),
dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole)
dungeon_map['Hyrule Castle Dungeon'].throne_door = world.get_door('Hyrule Castle Throne Room N', player)
dungeon_map['Hyrule Castle Sewers'].sewers_access = builder.throne_door
comb_w_replace = len(dungeon_map) ** len(candidate_sectors) comb_w_replace = len(dungeon_map) ** len(candidate_sectors)
return balance_split(candidate_sectors, dungeon_map, global_pole, builder_info) return balance_split(candidate_sectors, dungeon_map, global_pole, builder_info)
except (GenerationException, NeutralizingException): except (GenerationException, NeutralizingException):
@@ -2960,7 +3025,13 @@ def split_dungeon_builder(builder, split_list, builder_info):
attempts += 5 # all the combinations were tried already, no use repeating attempts += 5 # all the combinations were tried already, no use repeating
else: else:
attempts += 1 attempts += 1
if attempts >= 5 and merge_attempt < merge_limit: if builder.throne_door:
previous = find_sector(builder.throne_door.entrance.parent_region.name, builder.sectors)
previous.outstanding_doors.append(builder.throne_door)
builder.throne_sector.outstanding_doors.append(world.get_door('Hyrule Castle Throne Room N', player))
split_list['Sewers'].remove(builder.throne_door.entrance.parent_region.name)
builder.throne_door = None
if attempts >= 5 and merge_attempt < merge_limit and builder.name != 'Hyrule Castle':
merge_attempt, attempts = merge_attempt + 1, 0 merge_attempt, attempts = merge_attempt + 1, 0
raise GenerationException('Unable to resolve in 5 attempts') raise GenerationException('Unable to resolve in 5 attempts')
@@ -2981,16 +3052,21 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
chosen_sectors = defaultdict(list) chosen_sectors = defaultdict(list)
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
chosen_sectors[choice].append(main_sector_list[i]) chosen_sectors[choice].append(main_sector_list[i])
all_valid = True all_valid, package_map = True, {}
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
if not valid_assignment(builder, chosen_sectors[name], builder_info): flag, package = valid_assignment(builder, chosen_sectors[name], builder_info)
if not flag:
all_valid = False all_valid = False
break break
if package:
package_map[builder] = package
if all_valid: if all_valid:
for name, sector_list in chosen_sectors.items(): for name, sector_list in chosen_sectors.items():
builder = dungeon_map[name] builder = dungeon_map[name]
for sector in sector_list: for sector in sector_list:
assign_sector(sector, builder, candidate_sectors, global_pole) assign_sector(sector, builder, candidate_sectors, global_pole)
if builder in package_map:
builder.throne_door, builder.throne_sector, builder.chosen_lobby = package_map[builder]
return dungeon_map return dungeon_map
tries += 1 tries += 1
raise GenerationException('Split Dungeon Builder: Impossible dungeon. Ref %s' % next(iter(dungeon_map.keys()))) raise GenerationException('Split Dungeon Builder: Impossible dungeon. Ref %s' % next(iter(dungeon_map.keys())))
@@ -3380,7 +3456,7 @@ class DungeonAccess:
def identify_branching_issues(dungeon_map, builder_info): def identify_branching_issues(dungeon_map, builder_info):
unconnected_builders = {} unconnected_builders = {}
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
resolved, unreached_doors = check_for_valid_layout(builder, [], builder_info) resolved, unreached_doors, package = check_for_valid_layout(builder, [], builder_info)
if not resolved: if not resolved:
unconnected_builders[name] = builder unconnected_builders[name] = builder
for hook, door_list in unreached_doors.items(): for hook, door_list in unreached_doors.items():
@@ -3421,16 +3497,27 @@ def check_for_valid_layout(builder, sector_list, builder_info):
proposal = generate_dungeon_find_proposal(split_build, entrance_regions, split, world, player) proposal = generate_dungeon_find_proposal(split_build, entrance_regions, split, world, player)
# record split proposals # record split proposals
builder.valid_proposal[name] = proposal builder.valid_proposal[name] = proposal
package = None
if temp_builder.name == 'Hyrule Castle':
temp_builder.chosen_lobby.outstanding_doors.append(temp_builder.throne_door)
temp_builder.throne_sector.outstanding_doors.append(world.get_door('Hyrule Castle Throne Room N', player))
package = temp_builder.throne_door, temp_builder.throne_sector, temp_builder.chosen_lobby
split_list['Sewers'].remove(temp_builder.throne_door.entrance.parent_region.name)
builder.exception_list = list(sector_list) builder.exception_list = list(sector_list)
return True, {} return True, {}, package
except (GenerationException, NeutralizingException): except (GenerationException, NeutralizingException):
builder.split_dungeon_map = None builder.split_dungeon_map = None
builder.valid_proposal = None builder.valid_proposal = None
if temp_builder.name == 'Hyrule Castle' and temp_builder.throne_door:
temp_builder.chosen_lobby.outstanding_doors.append(temp_builder.throne_door)
temp_builder.throne_sector.outstanding_doors.append(world.get_door('Hyrule Castle Throne Room N', player))
old_entrance = temp_builder.throne_door.entrance.parent_region.name
split_dungeon_entrances[builder.name]['Sewers'].remove(old_entrance)
unreached_doors = resolve_equations(builder, sector_list) unreached_doors = resolve_equations(builder, sector_list)
return False, unreached_doors return False, unreached_doors, None
else: else:
unreached_doors = resolve_equations(builder, sector_list) unreached_doors = resolve_equations(builder, sector_list)
return len(unreached_doors) == 0, unreached_doors return len(unreached_doors) == 0, unreached_doors, None
def find_independent_entrances(entrance_regions, world, player): def find_independent_entrances(entrance_regions, world, player):
@@ -3831,9 +3918,7 @@ def find_free_equation(equations):
def copy_door_equations(builder, sector_list): def copy_door_equations(builder, sector_list):
equations = {} equations = {}
for sector in builder.sectors + sector_list: for sector in builder.sectors + sector_list:
if sector.equations is None: sector.equations = calc_sector_equations(sector)
# todo: sort equations?
sector.equations = calc_sector_equations(sector)
curr_list = equations[sector] = [] curr_list = equations[sector] = []
for equation in sector.equations: for equation in sector.equations:
curr_list.append(equation.copy()) curr_list.append(equation.copy())

View File

@@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
from source.tools.BPS import create_bps_from_data from source.tools.BPS import create_bps_from_data
__version__ = '1.0.2.4-v' __version__ = '1.0.2.4-x'
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish

View File

@@ -155,8 +155,16 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
## Notes and Bug Fixes ## Notes and Bug Fixes
#### StandardThrone
* Changed standard dungeon generation to always have Throne Room in hyrule castle and always have sanctuary behind it
* S&Q/death in standard after moving the tapestry but before delivering Zelda will result in spawning at the tapestry
* Mirror scroll will return you to Zelda's cell instead of last entrance. This reverts to normal behavior once the tapestry open trigger is reach
#### Volatile #### Volatile
* 1.0.2.5
* Some textual changes for hints
* 1.0.2.4 * 1.0.2.4
* Updated tourney winners (included Doors Async League winners) * Updated tourney winners (included Doors Async League winners)
* Fixed a couple issues with dungeon counters and the DungeonCompletion field for autotracking * Fixed a couple issues with dungeon counters and the DungeonCompletion field for autotracking

2
Rom.py
View File

@@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '01166fb16b38b49ef79acc9993dc4f02' RANDOMIZERBASEHASH = '4941faf4496875a373a51119f2bedf4c'
class JsonRom(object): class JsonRom(object):

Binary file not shown.