Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More consistent handling of boss keys from key rings #2149

Merged
merged 2 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions ItemPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,12 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
pending_junk_pool.append('Magic Bean Pack')
if (world.settings.gerudo_fortress != "open"
and world.settings.shuffle_hideoutkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional')):
if 'Thieves Hideout' in world.settings.key_rings and world.settings.gerudo_fortress != "fast":
if world.keyring('Thieves Hideout'):
pending_junk_pool.append('Small Key Ring (Thieves Hideout)')
else:
pending_junk_pool.append('Small Key (Thieves Hideout)')
if world.settings.shuffle_tcgkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
if 'Treasure Chest Game' in world.settings.key_rings:
if world.keyring('Treasure Chest Game'):
pending_junk_pool.append('Small Key Ring (Treasure Chest Game)')
else:
pending_junk_pool.append('Small Key (Treasure Chest Game)')
Expand All @@ -491,13 +491,13 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
if world.settings.shuffle_smallkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
for dungeon in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple',
'Bottom of the Well', 'Gerudo Training Ground', 'Ganons Castle'):
if dungeon in world.settings.key_rings:
if world.keyring(dungeon):
pending_junk_pool.append(f"Small Key Ring ({dungeon})")
else:
pending_junk_pool.append(f"Small Key ({dungeon})")
if world.settings.shuffle_bosskeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
for dungeon in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple'):
if not world.settings.keyring_give_bk or dungeon not in world.settings.key_rings or world.settings.shuffle_smallkeys == 'vanilla':
if not world.keyring_give_bk(dungeon):
pending_junk_pool.append(f"Boss Key ({dungeon})")
if world.settings.shuffle_ganon_bosskey in ('any_dungeon', 'overworld', 'keysanity', 'regional'):
pending_junk_pool.append('Boss Key (Ganons Castle)')
Expand Down Expand Up @@ -688,13 +688,13 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
or world.settings.gerudo_fortress == 'fast' and location.name != 'Hideout 1 Torch Jail Gerudo Key'):
item = IGNORE_LOCATION
shuffle_item = False
if shuffle_item and world.settings.gerudo_fortress == 'normal' and 'Thieves Hideout' in world.settings.key_rings:
if shuffle_item and world.keyring('Thieves Hideout'):
item = get_junk_item()[0] if vanilla_items_processed[location.vanilla_item] else 'Small Key Ring (Thieves Hideout)'

# Treasure Chest Game Key Shuffle
elif location.vanilla_item != 'Piece of Heart (Treasure Chest Game)' and location.scene == 0x10:
if world.settings.shuffle_tcgkeys in ('regional', 'overworld', 'any_dungeon', 'keysanity'):
if 'Treasure Chest Game' in world.settings.key_rings and location.vanilla_item == 'Small Key (Treasure Chest Game)':
if world.keyring('Treasure Chest Game') and location.vanilla_item == 'Small Key (Treasure Chest Game)':
item = get_junk_item()[0] if location.name != 'Market Treasure Chest Game Salesman' else 'Small Key Ring (Treasure Chest Game)'
shuffle_item = True
elif world.settings.shuffle_tcgkeys == 'remove':
Expand Down Expand Up @@ -766,9 +766,7 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:

# Boss Key
if location.vanilla_item == dungeon.item_name("Boss Key"):
if (world.settings.shuffle_smallkeys != 'vanilla'
and dungeon.name in world.settings.key_rings and world.settings.keyring_give_bk
and dungeon.name in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple')):
if world.keyring_give_bk(dungeon.name):
item = get_junk_item()[0]
shuffle_item = True
else:
Expand All @@ -788,9 +786,9 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
dungeon_collection = dungeon.small_keys
if shuffle_setting == 'vanilla':
shuffle_item = False
elif dungeon.name in world.settings.key_rings and not vanilla_items_processed[location.vanilla_item]:
elif world.keyring(dungeon.name) and not vanilla_items_processed[location.vanilla_item]:
item = dungeon.item_name("Small Key Ring")
elif dungeon.name in world.settings.key_rings:
elif world.keyring(dungeon.name):
item = get_junk_item()[0]
shuffle_item = True
# Silver Rupee
Expand Down
21 changes: 19 additions & 2 deletions Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -2969,8 +2969,25 @@ def get_door_to_unlock(rom: Rom, actor_id: int, actor: int, scene: int) -> list[
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]

# Return Boss Doors that should be unlocked
if (world.settings.shuffle_bosskeys == 'remove' and scene != 0x0A) or (world.settings.shuffle_ganon_bosskey == 'remove' and scene == 0x0A) or (world.settings.shuffle_pots and scene == 0x0A and switch_flag == 0x15):
if actor_id == 0x002E and door_type == 0x05:
if actor_id == 0x002E and door_type == 0x05:
dungeons = {
0x00: 'Deku Tree',
0x01: 'Dodongos Cavern',
0x02: 'Jabu Jabus Belly',
0x03: 'Forest Temple',
0x04: 'Fire Temple',
0x05: 'Water Temple',
0x06: 'Spirit Temple',
0x07: 'Shadow Temple',
0x0A: 'Ganons Castle',
}
if scene in dungeons and world.keyring_give_bk(dungeons[scene]):
setting = world.settings.shuffle_smallkeys
elif scene == 0x0A:
setting = world.settings.shuffle_ganon_bosskey
else:
setting = world.settings.shuffle_bosskeys
if setting == 'remove' or (world.settings.shuffle_pots and scene == 0x0A and switch_flag == 0x15):
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]

return get_actor_list(rom, get_door_to_unlock)
Expand Down
7 changes: 0 additions & 7 deletions Plandomizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,13 +1102,6 @@ def configure_effective_starting_items(self, worlds: list[World], world: World)
self.skipped_locations.append(loc)
if loc.item is not None and world.id == loc.item.world.id:
add_starting_item_with_ammo(items, loc.item.name)
# With small keysy, key rings, and key rings give boss key, but boss keysy
# is not on, boss keys are still required in the game to open boss doors.
for dungeon in world.dungeons:
if (dungeon.name in world.settings.key_rings and dungeon.name != 'Ganons Castle'
and dungeon.shuffle_smallkeys == 'remove' and dungeon.shuffle_bosskeys != 'remove'
and world.settings.keyring_give_bk and len(dungeon.boss_key) > 0):
items[dungeon.boss_key[0].name] = StarterRecord(1)

effective_adult_trade_item_index = -1
effective_child_trade_item_index = -1
Expand Down
2 changes: 1 addition & 1 deletion SaveContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ def give_item(self, world: World, item: str, count: int = 1) -> None:
'total_keys.tcg': 6,
},
}[dungeon]
if world.settings.keyring_give_bk:
if world.keyring_give_bk(dungeon):
bk_names = {
"Forest Temple": 'dungeon_items.forest.boss_key',
"Fire Temple": 'dungeon_items.fire.boss_key',
Expand Down
8 changes: 4 additions & 4 deletions State.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ def guarantee_hint(self) -> bool:
def collect(self, item: Item) -> None:
if item.solver_id is None:
raise Exception(f"Item '{item.name}' lacks a `solver_id` and can not be used in `State.collect()`.")
if 'Small Key Ring' in item.name and self.world.settings.keyring_give_bk:
if 'Small Key Ring' in item.name:
dungeon_name = item.name[:-1].split(' (', 1)[1]
if dungeon_name in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple'):
if self.world.keyring_give_bk(dungeon_name):
bk = f'Boss Key ({dungeon_name})'
self.solv_items[ItemInfo.solver_ids[escape_name(bk)]] = 1
if item.alias and item.alias_id is not None:
Expand All @@ -165,9 +165,9 @@ def collect(self, item: Item) -> None:
def remove(self, item: Item) -> None:
if item.solver_id is None:
raise Exception(f"Item '{item.name}' lacks a `solver_id` and can not be used in `State.remove()`.")
if 'Small Key Ring' in item.name and self.world.settings.keyring_give_bk:
if 'Small Key Ring' in item.name:
dungeon_name = item.name[:-1].split(' (', 1)[1]
if dungeon_name in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple'):
if self.world.keyring_give_bk(dungeon_name):
bk = f'Boss Key ({dungeon_name})'
self.solv_items[ItemInfo.solver_ids[escape_name(bk)]] = 0
if item.alias and item.alias_id is not None and self.solv_items[item.alias_id] > 0:
Expand Down
21 changes: 20 additions & 1 deletion World.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ def resolve_random_settings(self) -> None:
elif self.settings.dungeon_shortcuts_choice == 'all':
self.settings.dungeon_shortcuts = dungeons

# Determine area with keyring
# Determine areas with keyrings
areas = ['Thieves Hideout', 'Treasure Chest Game', 'Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple', 'Bottom of the Well', 'Gerudo Training Ground', 'Ganons Castle']
if self.settings.key_rings_choice == 'random':
self.settings.key_rings = random.sample(areas, random.randint(0, len(areas)))
Expand Down Expand Up @@ -1159,6 +1159,25 @@ def region_has_shortcuts(self, region_name: str) -> bool:
dungeon_name = HintArea.at(region).dungeon_name
return dungeon_name in self.settings.dungeon_shortcuts

# Returns whether the given dungeon has a keyring.
def keyring(self, dungeon_name: str) -> bool:
if dungeon_name in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple', 'Bottom of the Well', 'Gerudo Training Ground', 'Ganons Castle'):
return dungeon_name in self.settings.key_rings and self.settings.shuffle_smallkeys != 'vanilla'
elif dungeon_name == 'Thieves Hideout':
return dungeon_name in self.settings.key_rings and self.settings.gerudo_fortress != 'fast' and self.settings.shuffle_hideoutkeys != 'vanilla'
elif dungeon_name == 'Treasure Chest Game':
return dungeon_name in self.settings.key_rings and self.settings.shuffle_tcgkeys != 'vanilla'
else:
raise ValueError(f'Attempted to check keyring for unknown dungeon {dungeon_name!r}')

# Returns whether the given dungeon has a keyring that includes the boss key.
def keyring_give_bk(self, dungeon_name: str) -> bool:
return (
dungeon_name in ('Forest Temple', 'Fire Temple', 'Water Temple', 'Shadow Temple', 'Spirit Temple')
and self.settings.keyring_give_bk
and self.keyring(dungeon_name)
)

# Function to run exactly once after placing items in drop locations for each world
# Sets all Drop locations to a unique name in order to avoid name issues and to identify locations in the spoiler
def set_drop_location_names(self):
Expand Down