From 249fae39a09bc9e2afbdc6590d82b52ebbe0b751 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 1 Dec 2022 08:30:27 -0700 Subject: [PATCH] Fix for certain Standard key layouts Valid key door logic fix --- DoorShuffle.py | 35 ++++++++++++++++++++++++++++------- DungeonGenerator.py | 10 +++++++++- KeyDoorShuffle.py | 9 +++++++-- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index ad22b1c0..bab0bf56 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -230,7 +230,7 @@ def vanilla_key_logic(world, player): origin_list = entrances_map[builder.name] start_regions = convert_regions(origin_list, world, player) doors = convert_key_doors(default_small_key_doors[builder.name], world, player) - key_layout = build_key_layout(builder, start_regions, doors, world, player) + key_layout = build_key_layout(builder, start_regions, doors, {}, world, player) valid = validate_key_layout(key_layout, world, player) if not valid: logging.getLogger('').info('Vanilla key layout not valid %s', builder.name) @@ -1758,7 +1758,7 @@ class BuilderDoorCandidates: def shuffle_door_types(door_type_pools, paths, world, player): start_regions_map = {} for name, builder in world.dungeon_layouts[player].items(): - start_regions = convert_regions(builder.path_entrances, world, player) + start_regions = convert_regions(find_possible_entrances(world, player, builder), world, player) start_regions_map[name] = start_regions builder.candidates = BuilderDoorCandidates() @@ -2149,7 +2149,7 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed) proposal.extend(custom_trap_doors) - start_regions = filter_start_regions(builder, start_regions, world, player) + start_regions, event_starts = filter_start_regions(builder, start_regions, world, player) while not validate_trap_layout(proposal, builder, start_regions, paths, world, player): itr += 1 if itr >= len(sample_list): @@ -2171,6 +2171,7 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, def filter_start_regions(builder, start_regions, world, player): std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' excluded = {} # todo: drop lobbies, might be better to white list instead (two entrances per region) + event_doors = {} for region in start_regions: portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None) if portal and portal.destination: @@ -2180,9 +2181,21 @@ def filter_start_regions(builder, start_regions, world, player): or x.parent_region.name == 'Sewer Drop'), None) if not drop_region: excluded[region] = None + if portal and not portal.destination: + portal_entrance_region = portal.door.entrance.parent_region.name + if portal_entrance_region not in builder.path_entrances: + excluded[region] = None if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'): excluded[region] = None - return [x for x in start_regions if x not in excluded.keys()] + if portal is None: + entrance = next((x for x in region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] + or x.parent_region.name == 'Sewer Drop'), None) + event_doors[entrance] = None + else: + event_doors[portal.find_portal_entrance()] = None + + return [x for x in start_regions if x not in excluded.keys()], event_doors def validate_trap_layout(proposal, builder, start_regions, paths, world, player): @@ -2412,7 +2425,7 @@ def find_valid_bk_combination(builder, suggested, start_regions, world, player, proposal = kth_combination(sample_list[itr], bk_door_pool, bk_doors_needed) proposal.extend(custom_bk_doors) - start_regions = filter_start_regions(builder, start_regions, world, player) + start_regions, event_starts = filter_start_regions(builder, start_regions, world, player) while not validate_bk_layout(proposal, builder, start_regions, world, player): itr += 1 if itr >= len(sample_list): @@ -2556,9 +2569,9 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k sample_list = build_sample_list(combinations) proposal = kth_combination(sample_list[itr], key_door_pool, key_doors_needed) proposal.extend(custom_key_doors) - start_regions = filter_start_regions(builder, start_regions, world, player) + start_regions, event_starts = filter_start_regions(builder, start_regions, world, player) - key_layout = build_key_layout(builder, start_regions, proposal, world, player) + key_layout = build_key_layout(builder, start_regions, proposal, event_starts, world, player) determine_prize_lock(key_layout, world, player) while not validate_key_layout(key_layout, world, player): itr += 1 @@ -3251,6 +3264,14 @@ def find_accessible_entrances(world, player, builder): return visited_entrances +def find_possible_entrances(world, player, builder): + entrances = [region.name for region in + (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) + if region.dungeon.name == builder.name] + entrances.extend(drop_entrances[builder.name]) + return entrances + + def valid_inaccessible_region(r): return r.type is not RegionType.Cave or (len(r.exits) > 0 and r.name not in ['Links House', 'Chris Houlihan Room']) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index d33db874..b1ce5456 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -10,7 +10,7 @@ import time from typing import List from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector -from BaseClasses import Hook, hook_from_door +from BaseClasses import Hook, hook_from_door, Door from Regions import dungeon_events, flooded_keys_reverse from Dungeons import dungeon_regions, split_region_starts from RoomData import DoorKind @@ -846,6 +846,14 @@ class ExplorationState(object): ret.prize_received = self.prize_received return ret + def init_zelda_event_doors(self, event_starts, player): + for entrance in event_starts: + event_door = Door(player, entrance.name, DoorType.Logical) + event_door.req_event = 'Zelda Drop Off' + event_door.entrance = entrance + event_door.crystal = CrystalBarrier.Orange # always start in orange + self.append_door_to_list(event_door, self.event_doors) + 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() diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 0a2ee5ac..17859357 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -14,6 +14,7 @@ class KeyLayout(object): def __init__(self, sector, starts, proposal): self.sector = sector self.start_regions = starts + self.event_starts = [] self.proposal = proposal self.key_logic = KeyLogic(sector.name) @@ -223,13 +224,14 @@ class KeyCounter(object): return max(self.used_keys + reserve - len(self.key_only_locations), 0) -def build_key_layout(builder, start_regions, proposal, world, player): +def build_key_layout(builder, start_regions, proposal, event_starts, world, player): key_layout = KeyLayout(builder.master_sector, start_regions, proposal) key_layout.flat_prop = flatten_pair_list(key_layout.proposal) key_layout.max_drops = count_key_drops(key_layout.sector) key_layout.max_chests = calc_max_chests(builder, key_layout, world, player) key_layout.big_key_special = check_bk_special(key_layout.sector.region_set(), world, player) key_layout.all_locations = find_all_locations(key_layout.sector) + key_layout.event_starts = list(event_starts.keys()) return key_layout @@ -1455,6 +1457,7 @@ def validate_key_layout(key_layout, world, player): return True flat_proposal = key_layout.flat_prop state = ExplorationState(dungeon=key_layout.sector.name) + state.init_zelda_event_doors(key_layout.event_starts, player) state.key_locations = key_layout.max_chests state.big_key_special = check_bk_special(key_layout.sector.regions, world, player) for region in key_layout.start_regions: @@ -1489,7 +1492,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa # todo: allow more key shuffles - refine placement rules # if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0): found_forced_bk = state.found_forced_bk() - smalls_done = not smalls_avail # or not enough_small_locations(state, available_small_locations) + smalls_done = not smalls_avail or available_small_locations == 0 + # or not enough_small_locations(state, available_small_locations) bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk) # prize door should not be opened if the boss is reachable - but not reached yet allow_for_prize_lock = (key_layout.prize_can_lock and @@ -1646,6 +1650,7 @@ def create_key_counters(key_layout, world, player): key_layout.found_doors.clear() flat_proposal = key_layout.flat_prop state = ExplorationState(dungeon=key_layout.sector.name) + state.init_zelda_event_doors(key_layout.event_starts, player) if world.doorShuffle[player] == 'vanilla': builder = world.dungeon_layouts[player][key_layout.sector.name] state.key_locations = len(builder.key_door_proposal) - builder.key_drop_cnt