Skip to content

Commit d70b7f9

Browse files
committed
Setting to shuffle Ganon's tower into the boss entrance pool
1 parent 4366832 commit d70b7f9

13 files changed

Lines changed: 142 additions & 55 deletions

EntranceShuffle.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude
130130
('AdultBoss', ('Spirit Temple Before Boss -> Twinrova Boss Room', { 'index': 0x008D, 'savewarp_addresses': [ 0xB062F2, 0xBC6122 ] }),
131131
('Twinrova Boss Room -> Spirit Temple Before Boss', { 'index': 0x02F5 })),
132132

133+
('SpecialBoss', ('Ganons Castle Main -> Ganons Castle Tower', { 'index': 0x041B }),
134+
('Ganons Castle Tower -> Ganons Castle Main', { 'index': 0x0534 })),
135+
133136
('Interior', ('Kokiri Forest -> KF Midos House', { 'index': 0x0433 }),
134137
('KF Midos House -> Kokiri Forest', { 'index': 0x0443 })),
135138
('Interior', ('Kokiri Forest -> KF Sarias House', { 'index': 0x0437 }),
@@ -508,13 +511,17 @@ def shuffle_random_entrances(worlds: list[World]) -> None:
508511
if worlds[0].settings.shuffle_bosses == 'full':
509512
entrance_pools['Boss'] = world.get_shufflable_entrances(type='ChildBoss', only_primary=True)
510513
entrance_pools['Boss'] += world.get_shufflable_entrances(type='AdultBoss', only_primary=True)
514+
if worlds[0].settings.shuffle_ganon_tower:
515+
entrance_pools['Boss'] += world.get_shufflable_entrances(type='SpecialBoss', only_primary=True)
511516
if worlds[0].settings.open_forest == 'closed':
512517
# Deku is forced vanilla below, so Queen Gohma must be vanilla to ensure she is reachable.
513518
# This is already enforced by the fill algorithm in most cases, but this covers the odd settings combination where it isn't.
514519
entrance_pools['Boss'].remove(world.get_entrance('Deku Tree Before Boss -> Queen Gohma Boss Room'))
515520
elif worlds[0].settings.shuffle_bosses == 'limited':
516521
entrance_pools['ChildBoss'] = world.get_shufflable_entrances(type='ChildBoss', only_primary=True)
517522
entrance_pools['AdultBoss'] = world.get_shufflable_entrances(type='AdultBoss', only_primary=True)
523+
if worlds[0].settings.shuffle_ganon_tower:
524+
entrance_pools['AdultBoss'] += world.get_shufflable_entrances(type='SpecialBoss', only_primary=True)
518525
if worlds[0].settings.open_forest == 'closed':
519526
# Deku is forced vanilla below, so Queen Gohma must be vanilla to ensure she is reachable.
520527
# This is already enforced by the fill algorithm in most cases, but this covers the odd settings combination where it isn't.

HintList.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,28 @@ def tokens_required_by_settings(world: World) -> int:
227227
'LH Loach Fishing': lambda world: world.settings.shuffle_loach_reward == 'vanilla',
228228
}
229229

230+
def rainbow_bridge_hint_kind(world: World) -> str:
231+
if world.settings.bridge == 'open':
232+
return 'never'
233+
elif world.settings.bridge == 'vanilla':
234+
return 'always'
235+
elif world.settings.bridge == 'stones':
236+
return 'always' if world.settings.bridge_stones > 1 else 'sometimes'
237+
elif world.settings.bridge == 'medallions':
238+
return 'always' if world.settings.bridge_medallions > 1 else 'sometimes'
239+
elif world.settings.bridge == 'dungeons':
240+
return 'always' if world.settings.bridge_rewards > 2 else 'sometimes' if world.settings.bridge_rewards > 1 else 'never'
241+
elif world.settings.bridge == 'tokens':
242+
return 'always' if world.settings.bridge_tokens > 20 else 'sometimes' if world.settings.bridge_tokens > 10 else 'never'
243+
elif world.settings.bridge == 'hearts':
244+
return 'always' if world.settings.bridge_hearts > world.settings.starting_hearts + 1 else 'sometimes' if world.settings.bridge_hearts > world.settings.starting_hearts else 'never'
245+
else:
246+
raise NotImplementedError(f'Unimplemented bridge condition: {world.settings.bridge}')
247+
230248
# Entrance hints required under certain settings
231249
conditional_entrance_always: dict[str, Callable[[World], bool]] = {
232-
'Ganons Castle Grounds -> Ganons Castle Lobby': lambda world: (world.settings.bridge != 'open'
233-
and (world.settings.bridge != 'stones' or world.settings.bridge_stones > 1)
234-
and (world.settings.bridge != 'medallions' or world.settings.bridge_medallions > 1)
235-
and (world.settings.bridge != 'dungeons' or world.settings.bridge_rewards > 2)
236-
and (world.settings.bridge != 'tokens' or world.settings.bridge_tokens > 20)
237-
and (world.settings.bridge != 'hearts' or world.settings.bridge_hearts > world.settings.starting_hearts + 1)),
250+
'Ganons Castle Grounds -> Ganons Castle Lobby': lambda world: rainbow_bridge_hint_kind(world) == 'always',
251+
'Ganons Castle Main -> Ganons Castle Tower': lambda world: world.settings.trials > 3 or (rainbow_bridge_hint_kind(world) == 'always' and not world.shuffle_special_dungeon_entrances),
238252
}
239253

240254
# Dual hints required under certain settings
@@ -276,10 +290,8 @@ def tokens_required_by_settings(world: World) -> int:
276290
'Twinrova Rewards': lambda world: world.settings.shuffle_dungeon_rewards not in ('vanilla', 'reward'),
277291

278292
# Conditional entrance hints
279-
'Ganons Castle Grounds -> Ganons Castle Lobby': lambda world: (world.settings.bridge != 'open'
280-
and (world.settings.bridge != 'dungeons' or world.settings.bridge_rewards > 1)
281-
and (world.settings.bridge != 'tokens' or world.settings.bridge_tokens > 10)
282-
and (world.settings.bridge != 'hearts' or world.settings.bridge_hearts > world.settings.starting_hearts)),
293+
'Ganons Castle Grounds -> Ganons Castle Lobby': lambda world: rainbow_bridge_hint_kind(world) != 'never',
294+
'Ganons Castle Main -> Ganons Castle Tower': lambda world: world.settings.trials > 0 or (rainbow_bridge_hint_kind(world) != 'never' and not world.shuffle_special_dungeon_entrances),
283295
}
284296

285297
# Table of hints, format is (name, hint text, clear hint text, type of hint) there are special characters that are read for certain in game commands:
@@ -1389,6 +1401,7 @@ def tokens_required_by_settings(world: World) -> int:
13891401
'Kakariko Village -> Bottom of the Well': ("a #village well# leads to", None, 'entrance'),
13901402

13911403
'Ganons Castle Grounds -> Ganons Castle Lobby': ("the #rainbow bridge# leads to", None, 'entrance'),
1404+
'Ganons Castle Main -> Ganons Castle Tower': ("a #castle barrier# protects the way to", "#Ganon's trials# protect the way to", 'entrance'),
13921405

13931406
'KF Links House': ("Link's House", None, 'region'),
13941407
'Temple of Time': ("the #Temple of Time#", None, 'region'),
@@ -1476,6 +1489,7 @@ def tokens_required_by_settings(world: World) -> int:
14761489
'Morpha Boss Room': ("the #Giant Aquatic Amoeba#", "#Morpha#", 'region'),
14771490
'Bongo Bongo Boss Room': ("the #Phantom Shadow Beast#", "#Bongo Bongo#", 'region'),
14781491
'Twinrova Boss Room': ("the #Sorceress Sisters#", "#Twinrova#", 'region'),
1492+
'Ganons Castle Tower': ("#Ganon's Tower#", None, 'region'),
14791493

14801494
# Junk hints must satisfy all the following conditions:
14811495
# - They aren't inappropriate.

Hints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ def at(spot: Spot, use_alt_hint: bool = False) -> HintArea:
441441
else:
442442
parent_region = current_spot.parent_region
443443

444-
if parent_region.hint and (original_parent.name == 'Root' or parent_region.name != 'Root'):
444+
if (parent_region.hint or (use_alt_hint and parent_region.alt_hint)) and (original_parent.name == 'Root' or parent_region.name != 'Root'):
445445
if use_alt_hint and parent_region.alt_hint:
446446
return parent_region.alt_hint
447447
return parent_region.hint

ItemPool.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,20 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
794794
dungeon = Dungeon.from_vanilla_reward(ItemFactory(location.vanilla_item, world))
795795
dungeon.reward.append(ItemFactory(item, world))
796796

797+
# Ganon boss key
798+
elif location.vanilla_item == 'Boss Key (Ganons Castle)':
799+
if world.settings.shuffle_ganon_bosskey == 'vanilla':
800+
shuffle_item = False
801+
elif world.settings.shuffle_ganon_bosskey == 'remove':
802+
world.state.collect(ItemFactory(item, world))
803+
item = get_junk_item()[0]
804+
shuffle_item = True
805+
elif world.settings.shuffle_ganon_bosskey in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
806+
shuffle_item = True
807+
else:
808+
dungeon = [dungeon for dungeon in world.dungeons if dungeon.name == 'Ganons Castle'][0]
809+
dungeon.boss_key.append(ItemFactory(item, world))
810+
797811
# Dungeon Items
798812
elif location.dungeon is not None:
799813
dungeon = location.dungeon
@@ -806,7 +820,7 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
806820
item = get_junk_item()[0]
807821
shuffle_item = True
808822
else:
809-
shuffle_setting = world.settings.shuffle_bosskeys if dungeon.name != 'Ganons Castle' else world.settings.shuffle_ganon_bosskey
823+
shuffle_setting = world.settings.shuffle_bosskeys
810824
dungeon_collection = dungeon.boss_key
811825
if shuffle_setting == 'vanilla':
812826
shuffle_item = False

SettingsList.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ class SettingInfos:
646646
'glitched': {'settings': ['allowed_tricks', 'shuffle_interior_entrances', 'shuffle_hideout_entrances', 'shuffle_grotto_entrances',
647647
'shuffle_dungeon_entrances', 'shuffle_overworld_entrances', 'shuffle_gerudo_valley_river_exit', 'owl_drops',
648648
'warp_songs', 'spawn_positions', 'mq_dungeons_mode', 'mq_dungeons_specific',
649-
'mq_dungeons_count', 'shuffle_bosses', 'dungeon_shortcuts', 'deadly_bonks',
649+
'mq_dungeons_count', 'shuffle_bosses', 'shuffle_ganon_tower', 'dungeon_shortcuts', 'deadly_bonks',
650650
'shuffle_freestanding_items', 'shuffle_pots', 'shuffle_crates', 'shuffle_beehives', 'shuffle_silver_rupees', 'shuffle_wonderitems']},
651651
'none': {'settings': ['allowed_tricks', 'logic_no_night_tokens_without_suns_song', 'reachable_locations']},
652652
},
@@ -2269,6 +2269,28 @@ class SettingInfos:
22692269
'limited': 'Age-Restricted',
22702270
'full': 'Full',
22712271
},
2272+
disable = {
2273+
'off' : {'settings': ['shuffle_ganon_tower']},
2274+
},
2275+
shared = True,
2276+
gui_params = {
2277+
'randomize_key': 'randomize_settings',
2278+
},
2279+
)
2280+
2281+
shuffle_ganon_tower = Checkbutton(
2282+
gui_text = "Shuffle Ganon's Tower Entrance",
2283+
gui_tooltip = '''\
2284+
Shuffle the entrance from Ganon's Castle to
2285+
Ganon's Tower, just behind the trials barrier,
2286+
into the adult boss entrance pool if "Shuffle
2287+
Boss Entrances" is set to "Age-Restricted", or
2288+
the boss entrance pool if it's set to "Full".
2289+
2290+
The entrance from Ganon's Tower to Ganondorf's
2291+
boss room is never shuffled.
2292+
''',
2293+
default = False,
22722294
shared = True,
22732295
gui_params = {
22742296
'randomize_key': 'randomize_settings',

Spoiler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def parse_data(self) -> None:
116116
"Dungeon": 5,
117117
"ChildBoss": 6,
118118
"AdultBoss": 6,
119+
"SpecialBoss": 6,
119120
"Hideout": 7,
120121
"SpecialInterior": 7,
121122
"Interior": 7,

0 commit comments

Comments
 (0)