diff --git a/DoorShuffle.py b/DoorShuffle.py index 368aa931..1dea2f99 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1018,7 +1018,8 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ find_standard_origins(builder, recombinant_builders, origin_list) 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) - if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player): + # todo: figure out pre-validate, ensure all needed origins are enabled? + if len(origin_list) <= 0: # or not pre_validate(builder, origin_list, split_dungeon, world, player): if last_key == builder.name or loops > 1000: origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin' raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}') @@ -1068,14 +1069,14 @@ def determine_entrance_list(world, player): connections = {} for key, portal_list in dungeon_portals.items(): entrance_map[key] = [] - r_names = {} + r_names = [] if key in dungeon_drops.keys(): for drop in dungeon_drops[key]: - r_names[drop] = None + r_names.append((drop, None)) for portal_name in portal_list: portal = world.get_portal(portal_name, player) - r_names[portal.door.entrance.parent_region.name] = portal - for region_name, portal in r_names.items(): + r_names.append((portal.door.entrance.parent_region.name, portal)) + for region_name, portal in r_names: if portal: region = world.get_region(portal.name + ' Portal', player) else: @@ -2160,11 +2161,16 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, # eliminate start region if portal marked as destination def filter_start_regions(builder, start_regions, world, player): std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' - excluded = {} + excluded = {} # todo: drop lobbies, might be better to white list instead (two entrances per region) 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: - excluded[region] = None + # make sure that a drop is not accessible for this "destination" + drop_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 not drop_region: + 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()] diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f982cb6f..d33db874 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1356,7 +1356,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge sanc_builder = random.choice(lw_builders) assign_sector(sanc, sanc_builder, candidate_sectors, global_pole) - bow_sectors, retro_std_flag = {}, world.bow_mode[player].startswith('retro') and world.mode[player] == 'standard' + retro_std_flag = world.bow_mode[player].startswith('retro') and world.mode[player] == 'standard' + non_hc_sectors = {} free_location_sectors = {} crystal_switches = {} crystal_barriers = {} @@ -1364,7 +1365,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge neutral_sectors = {} for sector in candidate_sectors: if retro_std_flag and 'Bow' in sector.item_logic: # these need to be distributed outside of HC - bow_sectors[sector] = None + non_hc_sectors[sector] = None + elif world.mode[player] == 'standard' and 'Open Floodgate' in sector.item_logic: + non_hc_sectors[sector] = None elif sector.chest_locations > 0: free_location_sectors[sector] = None elif sector.c_switch: @@ -1375,8 +1378,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge neutral_sectors[sector] = None else: polarized_sectors[sector] = None - if bow_sectors: - assign_bow_sectors(dungeon_map, bow_sectors, global_pole) + if non_hc_sectors: + assign_non_hc_sectors(dungeon_map, non_hc_sectors, global_pole) leftover = assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player) free_location_sectors = scatter_extra_location_sectors(dungeon_map, leftover, global_pole) for sector in free_location_sectors: @@ -1420,7 +1423,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge def standard_stair_check(dungeon_map, dungeon, candidate_sectors, global_pole): # this is because there must be at least one non-dead stairway in hc to get out # this check may not be necessary - filtered_sectors = [x for x in candidate_sectors if any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)] + filtered_sectors = [x for x in candidate_sectors if 'Open Floodgate' not in x.item_logic and + any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)] valid = False while not valid: chosen_sector = random.choice(filtered_sectors) @@ -1550,7 +1554,7 @@ def define_sector_features(sectors): sector.bk_required = True for ext in region.exits: door = ext.door - if door is not None: + if door is not None and not door.blocked: if door.crystal == CrystalBarrier.Either: sector.c_switch = True elif door.crystal == CrystalBarrier.Orange: @@ -1562,6 +1566,8 @@ def define_sector_features(sectors): if region.name in ['PoD Mimics 2', 'PoD Bow Statue Right', 'PoD Mimics 1', 'GT Mimics 1', 'GT Mimics 2', 'Eastern Single Eyegore', 'Eastern Duo Eyegores']: sector.item_logic.add('Bow') + if region.name in ['Swamp Lobby', 'Swamp Entrance']: + sector.item_logic.add('Open Floodgate') def assign_sector(sector, dungeon, candidate_sectors, global_pole): @@ -1613,8 +1619,8 @@ def find_sector(r_name, sectors): return None -def assign_bow_sectors(dungeon_map, bow_sectors, global_pole): - sector_list = list(bow_sectors) +def assign_non_hc_sectors(dungeon_map, non_hc_sectors, global_pole): + sector_list = list(non_hc_sectors) random.shuffle(sector_list) population = [] for name in dungeon_map: @@ -1623,7 +1629,7 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole): choices = random.choices(population, k=len(sector_list)) for i, choice in enumerate(choices): builder = dungeon_map[choice] - assign_sector(sector_list[i], builder, bow_sectors, global_pole) + assign_sector(sector_list[i], builder, non_hc_sectors, global_pole) def scatter_extra_location_sectors(dungeon_map, free_location_sectors, global_pole): @@ -3511,7 +3517,8 @@ def check_for_valid_layout(builder, sector_list, builder_info): for portal in world.dungeon_portals[player]: if not portal.destination and portal.name in dungeon_portals[builder.name]: possible_regions.add(portal.door.entrance.parent_region.name) - if builder.name in dungeon_drops.keys(): + if builder.name in dungeon_drops.keys() and (builder.name != 'Hyrule Castle' + or world.mode[player] != 'standard'): possible_regions.update(dungeon_drops[builder.name]) independents = find_independent_entrances(possible_regions, world, player) for name, split_build in builder.split_dungeon_map.items(): diff --git a/Fill.py b/Fill.py index f695bd9a..8487c750 100644 --- a/Fill.py +++ b/Fill.py @@ -289,7 +289,7 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] else: possible_swaps = [x for x in state.locations_checked - if x.item.type not in ['Event', 'Crystal'] and not x.forced_item] + if x.item.type not in ['Event', 'Crystal'] and not x.forced_item and not x.locked] swap_locations = sorted(possible_swaps, key=location_preference) return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, key_pool, single_player_placement) diff --git a/Rom.py b/Rom.py index 18677375..fff0456a 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '61c296effe6180274721d570d2471e1c' +RANDOMIZERBASEHASH = '5639de3bfd500ba238d3f27ea78c19e1' class JsonRom(object): @@ -1492,8 +1492,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.doorShuffle[player] not in ['vanilla', 'basic']: # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) - rom.write_bytes(0x180188, [0x20, 3, 10]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0x20, 3, 10]) # Mantle respawn refills (magic, bombs, arrows) + # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180188, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) + # Mantle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest rom.write_bytes(0x180185, [max(0x00, magic_max), max(3, bomb_max), max(0, bow_max)]) rom.write_bytes(0x180188, [magic_small, 3, bow_small]) # Zelda respawn refills (magic, bombs, arrows) diff --git a/data/base2current.bps b/data/base2current.bps index 03d6471e..3fd78189 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index b5e7515e..0a0d3ad4 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -58,15 +58,17 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon excluded[region] = None elif split_dungeon and builder.sewers_access and builder.sewers_access.entrance.parent_region == region: continue - 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]): + drop_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 drop_region: # for holes + if drop_region.name == 'Sewer Drop': + drop_region = next(x.parent_region for x in drop_region.entrances) + if (drop_region.name in world.inaccessible_regions[player] and + region.name not in world.enabled_entrances[player]): excluded[region] = None + elif region in excluded: + del excluded[region] entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] doors_to_connect, idx = {}, 0 all_regions = set() @@ -315,9 +317,9 @@ def determine_paths_for_dungeon(world, player, all_regions, name): 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': + if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon': paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) + paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': paths.append('Thieves Attic Window') elif 'Thieves Attic Window' in all_r_names: