From 5fc3924b8ab81c3aaf16bdc9ddb15605bca8cfe0 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 3 Sep 2025 02:50:20 -0500 Subject: [PATCH] Allow Zelda escape to use TT Maiden Cell as checkpoint --- BaseClasses.py | 2 ++ Doors.py | 8 ++++++-- DungeonGenerator.py | 12 ++++++------ ItemList.py | 15 +++++++++------ KeyDoorShuffle.py | 5 ++++- Rom.py | 14 +++++++++++++- Rules.py | 2 +- data/base2current.bps | Bin 136642 -> 136664 bytes source/dungeon/DungeonStitcher.py | 8 ++++---- source/enemizer/Enemizer.py | 15 +++++++++++++++ 10 files changed, 60 insertions(+), 21 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index a82f3093..1a230d6b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -76,6 +76,7 @@ class World(object): self.can_take_damage = True self.hints = hints.copy() self.prizes = {} + self.default_zelda_region = {} self.dynamic_regions = [] self.dynamic_locations = [] self.spoiler_mode = spoiler_mode @@ -185,6 +186,7 @@ class World(object): set_player_attr('standardize_palettes', 'standardize') set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}) set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []}) + set_player_attr('default_zelda_region', 'Hyrule Dungeon Cellblock') set_player_attr('exp_cache', defaultdict(dict)) set_player_attr('enabled_entrances', {}) diff --git a/Doors.py b/Doors.py index aa55cf0c..a8a53a26 100644 --- a/Doors.py +++ b/Doors.py @@ -1302,8 +1302,12 @@ def create_doors(world, player): world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain') if world.mode[player] == 'standard' and 'Zelda Herself' not in [i.name for i in world.precollected_items if i.player == player]: - world.get_door('Hyrule Castle Throne Room Tapestry', player).event('Zelda Pickup') - world.get_door('Hyrule Castle Tapestry Backwards', player).event('Zelda Pickup') + if not world.shuffle_followers[player]: + zelda_location = 'Zelda Pickup' + else: + zelda_location = 'Suspicious Maiden' + world.get_door('Hyrule Castle Throne Room Tapestry', player).event(zelda_location) + world.get_door('Hyrule Castle Tapestry Backwards', player).event(zelda_location) # crystal switches and barriers world.get_door('Hera Lobby Crystal Exit', player).c_switch() diff --git a/DungeonGenerator.py b/DungeonGenerator.py index d3e33bb8..3306b71d 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -586,13 +586,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name): paths.append(portal.door.entrance.parent_region.name) if world.mode[player] == 'standard': if name == 'Hyrule Castle': - paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) + paths.append(world.default_zelda_region[player]) + paths.append((world.default_zelda_region[player], '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')) + paths.append(world.default_zelda_region[player]) + paths.append((world.default_zelda_region[player], '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: @@ -1322,7 +1322,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge for r_name in dungeon_boss_sectors[key]: assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) if key == 'Hyrule Castle' and world.mode[player] == 'standard': - for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda + for r_name in [world.default_zelda_region[player], 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) if key == 'Thieves Town' and (world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind' @@ -3091,7 +3091,7 @@ def split_dungeon_builder(builder, split_list, builder_info): 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), + assign_sector(find_sector(world.default_zelda_region[player], 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 diff --git a/ItemList.py b/ItemList.py index 4b4b3ace..65d67f57 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1676,7 +1676,7 @@ def set_event_item(world, player, location_name, item_name=None): def shuffle_event_items(world, player): - if (world.shuffle_followers[player]): + if world.shuffle_followers[player]: available_quests = follower_quests.copy() available_pickups = [quests[0] for quests in available_quests.values()] @@ -1688,11 +1688,14 @@ def shuffle_event_items(world, player): available_pickups.remove(loc.item.name) - if world.mode[player] == 'standard': - if 'Zelda Herself' in available_pickups: - zelda_pickup = available_quests.pop('Zelda Pickup')[0] - available_pickups.remove(zelda_pickup) - set_event_item(world, player, 'Zelda Pickup', zelda_pickup) + if world.mode[player] == 'standard' and 'Zelda Herself' in available_pickups: + zelda_dropoff = 'Zelda Pickup' + if world.default_zelda_region[player] == 'Thieves Blind\'s Cell': + zelda_dropoff = 'Suspicious Maiden' + available_quests.pop(zelda_dropoff) + zelda_pickup = 'Zelda Herself' + available_pickups.remove(zelda_pickup) + set_event_item(world, player, zelda_dropoff, zelda_pickup) random.shuffle(available_pickups) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index d9c837b9..65ad0faf 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -1805,7 +1805,10 @@ def imp_locations_factory(world, player): return imp_locations imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] if world.mode[player] == 'standard': - imp_locations.append('Zelda Pickup') + if world.default_zelda_region[player] == 'Thieves Blinds\' Cell': + imp_locations.append('Suspicious Maiden') + else: + imp_locations.append('Zelda Pickup') imp_locations.append('Zelda Drop Off') return imp_locations diff --git a/Rom.py b/Rom.py index faf0b6c3..42cfe59d 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '93386b05ee4b5de6b9165941b9e14e97' +RANDOMIZERBASEHASH = '20e588b832011dcf15dbd31dff6955ce' class JsonRom(object): @@ -1581,6 +1581,18 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_bytes(snes_to_pc(0x09A045), [0xEA, 0xEA]) # allow super bomb to follow into UW holes rom.write_byte(snes_to_pc(0x09ACDF), 0x6B) # allow kiki/locksmith to follow after screen transition + if world.default_zelda_region[player] == 'Thieves Blind\'s Cell': + write_int16(rom, snes_to_pc(0x02D8D6), 0x45) # change zelda spawn point to maiden cell + rom.write_bytes(snes_to_pc(0x02D8F0), [0x08, 0x08, 0x08, 0x09, 0x0B, 0x0A, 0x0B, 0x0B]) + write_int16(rom, snes_to_pc(0x02D91C), 0x0B00) + write_int16(rom, snes_to_pc(0x02D92A), 0x0800) + write_int16(rom, snes_to_pc(0x02D938), 0x0860) + write_int16(rom, snes_to_pc(0x02D946), 0x0B90) + write_int16(rom, snes_to_pc(0x02D954), 0x0078) + write_int16(rom, snes_to_pc(0x02D962), 0x017F) + rom.write_byte(snes_to_pc(0x02D975), 0x00) + rom.write_byte(snes_to_pc(0x02D98A), 0x02) + if world.enemy_shuffle[player] != 'none': # informs zelda and maiden to draw over gfx slots that are guaranteed unused rom.write_bytes(0x1802C1, world.data_tables[player].room_headers[0x80].free_gfx[0:2]) diff --git a/Rules.py b/Rules.py index b062d917..d87eaa45 100644 --- a/Rules.py +++ b/Rules.py @@ -1669,7 +1669,7 @@ def standard_rules(world, player): def find_rules_for_zelda_delivery(world, player): # path rules for backtracking - start_region = world.get_region('Hyrule Dungeon Cellblock', player) + start_region = world.get_region(world.default_zelda_region[player], player) queue = deque([(start_region, [], [])]) visited = {start_region} blank_state = CollectionState(world) diff --git a/data/base2current.bps b/data/base2current.bps index 3ac6a7ce81674927ee8d2e3aaa19e8698204ca37..0fe2d932963a31af760b5381bf17b8627f8c443c 100644 GIT binary patch delta 1792 zcmW+#4Ny~87S0VxNHB>3L8Bmqhw`JM)YX;ZN>v1dbt=@3%d}AJ)I#LpR-JBLEW*9f z1cD7oxR6(Tggn3~Z>bAw?4}(iy9LKWB4}pW?kL+<{!P%*>@=O#u3PuTxik09J?A^; zeD^!|4EKre^@#^ILO>NmvT1>EHYzAf3J-w!3pbBvD!tZgMM3$K0&$;YUg|C zQzJ3xEL!(i2I`%=wl?es@y=T{6QZ~zKQqvEeV#E(h{JxydUE@B7UEre4d$jS_!+Z- zkOb^`Ia64gJ#`uB26~E1kQ!2lUVos_8i{r+N2}K51(+$M8%4S-*r%?(c@W}6El0dK zFu0=T2XmX=yn=We7or`jI1O9FK=0Lad+wu!o8s)Ldp6=fm_nK0E$1zCBTASw!w^Z% zx{teaKp9RRDd%=Hl2L>T>AmkgwCwQ+g1asi?4C7lcQ77PHJtxaJKzDnG#Ks4_a#pq zllT()zj|ScwXWScF+Mq?AijMHiFZEN4l8~h&LYJ#S@e-Ago7jd)7crfO73~tx6GX- z_r($!^UMGrO5KX(TtkJd)TUUjd8Bzn)Zp?_WfC#RaE0WWN9=i3?ksm`59ZE#1TQQX zsNL8)`l>8@-q7p0#3qt_u4Mh1nnO9Tafi^GMUT%g8{o&*qw z9iE!?9LQwd5o7F966r6hD7w`i7EXp)|Ll?Y#P;%PAKu|j%xIcpG`1{Swi%AIz-Er@ zgfiC5@ysR(&ZVRh%1$|xV%u%Q*b=ceoh^J=?#13@qnXH((+ib*dIQJbaSya=utGy^W{67WE61rE`cU zkI8gg$Bj7!KuHs#a zEOgsXZ5g%+_g(&C7n23siLCp&#Iw&(R!gCS0%!3EeT2&Dy*DZL?- z8lWyCfjy;lD8&us9uQ0jli;eq`jD=|1!Ji|k57yx)O)XLjbDG$@1+~NfKEgL-jnCg z>GC{38ohfu&)@ddbux7Gy8YveRggZT#WiM!IfCVpoE(}a{8}U3X|Fhsl=MNZF3)e@ zj?e^k#a8;{UwR&w$DaAhZM4)=y5}~o%z)H^Xn6Zuwx@31lz5?QXs+WazjOlI#@81`a84nX zHPENpT52l3!TILFT(*5Kz_le5pqtUS^Bi;?TJN`3d_aFIm-DzK=VmR)&6rI6;%n@N ztFP?ZWbesY_MnaU635(GpLgV~MHG)y8gUGwD&L=Dr9nX`#Z87%-K5oOAx&dH*4f`U zGiM?#29Gp|f!-nsvuxS94=%mG57(!|v-s?tM-nmviZ^&SwnjOBcej4CsGtO5Nxt*P z|CNftOH`2v#DguaY7zKL^cu3{qH!-A2(}zv4Gl0DY0Ym!WVg`{5?#Mhg27dS#93VG z`tKINX0EB0J!$zvY=cd=G&!8r{SRJWWGytXCtvGRQGYE18$g^ZPzFNDt4{yw*-&rO zWuk1F=@-DO8T_EM|1f>T6{-hZ6Ds#MQ?fHIej~U6gm2bDD#JBs0Ddue*>$-EL;$eK z)r-Ms1YatpPE#NQl({-6a0SHV*XnId@5ML!+%VFb_)!yeih83R{3K30YSSHyq*7eP zm%!hF@WX_A^a)pb2dIhwt0`;;zXk_fUOVs$MdEC@Y;>RNa|hs`6pwYE*H?)S^ZpNL C%|25A delta 1753 zcmW+!e^66b7S2sVeh>%{pahgOJdlc&$|4mCt6&j9MO$R1s}@AJ{)j%Q*4e6zNbgMz z5H%#>3a|Kx?+IebQkJZ-P1Q+PV5|fI(`q~F?o@<;30j(taRzr>-IxAz&%Nh<-+bqM z=iKd)4D?8Biv+@IRr5ppTY|&N-0M^9%*+b}H3(TUkbciJ+Y?21Bj`r3TmN*5T?~=# z?B7C>er9IIO?@gs?G$e%YVRWC_6PICLTUOm$2YYQAK>3!k4-<|Wlsl@^roYRmN%`S z%_L5Ny0b5hYoYncBM#~Rx@+DVz{S^yefUpmQmXWE*{}3OgS|_Q_=z1;RK^#EZN%^+ z{D#4pI!hP%v$V!tWw6~qm~mn9tHC6Ib=i(ZmM424%-_9-a;YuY7rIUf) z_XpG^XSRwM`jaDgZm2khW0liMC=ct9U>_FmkgJ?R;aQ*hChi6+1G+4B=|+H zOEx}~_2yoa7Eq5FfcsoE@f_FKnyoOH6@<#eIu53^Hf`u-sufQw?3}(F>F2{xC7DA8D~H0))IGkoV4hcyWUyMK)&169A0)WK6sgaIYsznU>~GjV72GN`ixN|6Im zE?s4QmT6^ml3*u?+=>CUwRc)4vhsL3Xd(oc#V@OTQXn~^7%qOkI$z|(g#okrNtlR;-r!2g{jye47}l^>h*zCA9WrH8NuoP zWG9rnfPa2am6OYRM6bmaBdMxd56iXjlaJ|ie5cDLn-?@75f!Cn6gs8}UBT=~%$EE@ zFB-bFvC!M{{Y5e`c#%1EaxbJ0YjL%y%OtS!xFri{PT+SN>9fqP&ykWY)#{ddnYR(T zOBEb$D4b_gy-Kxvz>olc@r0>L27LjytxI-N0Txy8|$FXymi6jz{gePGR${Gx>anr-E};S8FGlN+%DQN{Z`)-b;?knAJ_sZO%B z)l3?>b!D;+dyG|Py(`R*LA_ZTEU7tf6`b9_3g@T6kFja&vGmPFP?I)H;zjo3w?2DI zl(`AwPgmKcFDoR8MJWyU$lezMznEC}KTB!yZ10DHG~gC+H)@_kD+1p{CCE0PG`tIK z{^s&{sE2-WYtacrb{LrLiH?m*a5YX?H-T#%Q5nFRo=_S-*L)^>L;1USQt%a1-@q-?CC1AbdN;41uP~i9ugCPNbpNwjxKpI%?xJZEuK(V>D-okdD z+|uKO;#Sqs6I3O&w+%d#h($ diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 29a2c32f..d7ce3c4f 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -333,13 +333,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name): if portal.destination: paths.append(portal.door.entrance.parent_region.name) if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon': - paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) + paths.append(world.default_zelda_region[player]) + paths.append((world.default_zelda_region[player], 'Hyrule Castle Throne Room')) entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South') # todo: in non-er, we can use the other portals too - paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name)) + paths.append((world.default_zelda_region[player], entrance.door.entrance.parent_region.name)) paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name, - 'Hyrule Dungeon Cellblock'])) + world.default_zelda_region[player]])) if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': paths.append('Thieves Attic Window') elif 'Thieves Attic Window' in all_r_names: diff --git a/source/enemizer/Enemizer.py b/source/enemizer/Enemizer.py index f4e89207..cc32ba8c 100644 --- a/source/enemizer/Enemizer.py +++ b/source/enemizer/Enemizer.py @@ -521,6 +521,21 @@ def randomize_enemies(world, player): green_mail, blue_mail, red_mail = original_table[idx] del original_table[idx] world.data_tables[player].enemy_damage[i] = [green_mail, blue_mail, red_mail] + # determine default zelda follower location + if world.mode[player] == 'standard' and world.doorShuffle[player] == 'crossed' and world.shuffle_followers[player]: + def random_zelda(): + world.default_zelda_region[player] = random.choice(['Hyrule Dungeon Cellblock', 'Thieves Blind\'s Cell']) + if world.customizer: + placements = world.customizer.get_placements() + if placements and player in placements and 'Zelda Herself' in placements[player].values(): + location = [l for (l, item) in placements[player].items() if item == 'Zelda Herself'][0] + if location == 'Suspicious Maiden': + world.default_zelda_region[player] = 'Thieves Blind\'s Cell' + else: + random_zelda() + else: + random_zelda() + def write_enemy_shuffle_settings(world, player, rom):