From 4f2fed48d7be409b9105104ad9886d35704c677b Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 26 Oct 2022 16:46:32 -0600 Subject: [PATCH] Big key door fix Some generation fixes (need to look at pre-validate some more) Bumped up escape assist values in non-basic shuffles --- DoorShuffle.py | 20 +++++++++++++------- DungeonGenerator.py | 27 +++++++++++++++++---------- Fill.py | 2 +- Rom.py | 8 +++++--- data/base2current.bps | Bin 93438 -> 93445 bytes source/dungeon/DungeonStitcher.py | 22 ++++++++++++---------- 6 files changed, 48 insertions(+), 31 deletions(-) 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 03d6471ed59fbc3a0aa2fcb13383c31d53c06f20..3fd78189a58e8bcb70b83f770932c6ebbe1745dc 100644 GIT binary patch delta 867 zcmW-cUu;u#6vyxBy|?YQ(tB_JtwpTAwQg<2kZn$!pk!z@q?9a!MB?8xBsyit!_*gI z#;?WOP{`8WUdq{aL#S*wp(V=}`cOccb(%SdFAK>MTwIhHqYs4iVFWaUdiA`V@A>4L z?>Fb^&%C_G&wm7*^Mm`-l_L;ip{Fv--C<#|Lap^SXjb;Rmfi+`#q$1F+k=x}a|+BB z!2MB%*1*F%UEtywyU@;nozspUU}!~4+xJ3OrM%p`8|t`jf{^v5LU|p?Beb#Lhx@pl zg$_Q7sU)oqu@JY|qKX_SZPGTwM60D%*ujAh@7kgFWQk!$>dl6QJjfSKlqI{=+s%e| zYDmccZD5{c6$AdH5Kpc@;Z6LH%y|Qy$S-{2_t0c}HZ^`oA#wF|L1*RSH|8HYZ`8O5 zU1`xd8OSTA=oX#3_;tEK3OTY>zPRsd<@M_q{xf(DgNkVZ9NyUV)tQ{mk}uK;QpxFr z)N+vlhFEZ30RPV5jU=xf{y6_fxz<>UG?~jfL(-~j(k;eesIt!IIiT8W9qI)WDXTK0 z+weK$MCjc1-gilBHZ{xZ9EmD_)h3tZHuciDEfEp4-T0OWL9-*+PGyS6MF_x6R7B{4 zN7ZX02+(s^rLH=k?V_1qRhqZnn~fRObUjgLm_|5@$snf2pQzM_rzfRzR3LxiV+r<) zJI;-4i0a^ZRoLUEJ=Bhe9B|kiP?CN449+{i*Ah=;H0QC5HimZ`Mr{v1bilBL6WL1` zK28^Bmd9${4TAW&6Y`EhRK=8TVOH4qAeN-?+%c3~K*(|-391Z-&)Bi4!NMyZH>67cHmS}&|Vg%l!lp+3x?zv>&a+b)PdJgitpw6P&a4?vFS=nM@PeMP&TVGXr4?zTyVNd!_~17a zN)}E2zWYtJ?`RT-t3eN>Tj3y<3A`b8Fd603T_eTfobRQa#`RU75HM!A`m_Tc$E@q4 LJ5ygCZ29G1-!Dm} delta 854 zcmW-bZ%i9?7{~AF^=Ly2cm3b)5Bt$>v=uYhUvQd9mxTz{45Ma7GrgHOjkx(jm#Go_ zUD-A5B5Cgq_7pA)h3(wRf=v6OVFtlJ2uplHO!i^~lWoz&#l-YQO+=@7@p@Lq(e`MjSGPN%{;9)u8UFwFdW!uYVTX!sh)59|j z;NTcwJmBX%-rV2WMn0gOIghcS@`&%X=S(dAw(VsC`bMoB^bU0t-#>0NYq- z<`S5a1}ZPGkhD1yiX1N0sKYc3RA%a7w-p+2y%F~I^)GH#ZR9t#XrV0W&~9y3gX7Qg z>V}Cq@`+-?6@_^6onlb@AF-;5&Z!Gxlb=f7Txz&aAw61V)L`Y}`MPg+;7J|=PpWyc zMV>hFMY&_{k~L0OgN9YsoRf03e&RrJlm^KYDN%cH{pj57$-Kdmche%dkv9mrnU8ld z#EKOjnhtKiDRKJo*VSL9=c`NcP3D@xkReUB7&h}T)wIE>R-pR9X6=%NT-9X8aAF7@ z0_@uM{FjnFmzv=WD@iE7&W|n04sB}KDaLvI044>9)Hx#oDi7dU0m3kc1p!*1R#^~$ zhdsA7>Rse=Ei}8XQPqBLrq`^dKZ%PBQ;p?NiePGZL!%+gj5Ur>p8SlPE_hmK9qGR> zXgwD-{;-ekp$>e>4aZ~K6lp*DPG_Y&o|5pRtkiiqHF;=xkamPanJK+n%<2tqXZ82- zj@$gC6(73c4cAB^H-)h?^y@3jgY)fGGJ@}UKy@ck6H