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

Handle Gerudo Fortress heart piece in logic #2179

Merged
merged 7 commits into from
Dec 12, 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
1 change: 1 addition & 0 deletions ItemList.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ class GetItemId(IntEnum):
'Gerudo Membership Card': ('Item', True, GetItemId.GI_GERUDOS_CARD, None),
'Heart Container': ('Item', True, GetItemId.GI_HEART_CONTAINER, {'alias': ('Piece of Heart', 4), 'progressive': float('Inf')}),
'Piece of Heart': ('Item', True, GetItemId.GI_HEART_PIECE, {'progressive': float('Inf')}),
'Piece of Heart (Out of Logic)': ('Item', None, GetItemId.GI_HEART_PIECE, None),
'Boss Key': ('BossKey', True, GetItemId.GI_BOSS_KEY, None),
'Compass': ('Compass', None, GetItemId.GI_COMPASS, None),
'Map': ('Map', None, GetItemId.GI_DUNGEON_MAP, None),
Expand Down
39 changes: 21 additions & 18 deletions ItemPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from World import World


plentiful_items: list[str] = ([
plentiful_items: list[str] = [
'Biggoron Sword',
'Boomerang',
'Lens of Truth',
Expand All @@ -36,14 +36,8 @@
'Bow',
'Slingshot',
'Bomb Bag',
'Double Defense'] +
['Heart Container'] * 8
)

# Ludicrous replaces all health upgrades with heart containers
# as done in plentiful. The item list is used separately to
# dynamically replace all junk with even levels of each item.
ludicrous_health: list[str] = ['Heart Container'] * 8
'Double Defense',
]

# List of items that can be multiplied in ludicrous mode.
# Used to filter the pre-plando pool for candidates instead
Expand Down Expand Up @@ -219,12 +213,8 @@
)

item_difficulty_max: dict[str, dict[str, int]] = {
'ludicrous': {
'Piece of Heart': 3,
},
'plentiful': {
'Piece of Heart': 3,
},
'ludicrous': {},
'plentiful': {},
'balanced': {},
'scarce': {
'Bombchus (5)': 1,
Expand Down Expand Up @@ -544,9 +534,6 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
if world.settings.shuffle_individual_ocarina_notes:
pending_junk_pool.extend(['Ocarina A Button', 'Ocarina C up Button', 'Ocarina C left Button', 'Ocarina C down Button', 'Ocarina C right Button'])

if world.settings.item_pool_value == 'ludicrous':
pending_junk_pool.extend(ludicrous_health)

if world.settings.triforce_hunt:
pending_junk_pool.extend(['Triforce Piece'] * world.settings.triforce_count_per_world)
if world.settings.shuffle_individual_ocarina_notes:
Expand Down Expand Up @@ -713,6 +700,15 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:
else:
shuffle_item = False

# Gerudo Fortress Freestanding Heart Piece
elif location.vanilla_item == 'Piece of Heart (Out of Logic)':
shuffle_item = world.settings.shuffle_gerudo_fortress_heart_piece == 'shuffle'
if world.settings.shuffle_hideout_entrances or world.settings.logic_rules == 'glitched':
if world.settings.shuffle_hideout_entrances and world.settings.shuffle_gerudo_fortress_heart_piece == 'remove':
item = IGNORE_LOCATION
else:
item = 'Piece of Heart'

# Thieves' Hideout
elif location.vanilla_item == 'Small Key (Thieves Hideout)':
shuffle_item = world.settings.shuffle_hideoutkeys != 'vanilla'
Expand Down Expand Up @@ -961,6 +957,13 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]:

for item, maximum in item_difficulty_max[world.settings.item_pool_value].items():
replace_max_item(pool, item, maximum)
# Dynamically condense regular heart pieces into heart containers depending on how many are in the pool
# (which varies based on the Shuffle Gerudo Fortress Heart Piece setting)
if world.settings.item_pool_value in ('plentiful', 'ludicrous'):
indices = [items_idx for items_idx, val in enumerate(pool) if val == 'Piece of Heart']
num_full_hearts = (len(indices) // 4) * 4
for hearts_idx, items_idx in enumerate(indices[:num_full_hearts]):
pool[items_idx] = 'Heart Container' if hearts_idx % 4 == 0 else get_junk_item()[0]
fenhl marked this conversation as resolved.
Show resolved Hide resolved

world.distribution.alter_pool(world, pool)

Expand Down
1 change: 1 addition & 0 deletions LocationList.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@ def shop_address(shop_id: int, shelf_id: int) -> int:

# Gerudo's Fortress
("GF Chest", ("Chest", 0x5D, 0x00, None, 'Piece of Heart', ("Gerudo's Fortress", "Chests",))),
("GF Freestanding PoH", ("Collectable", 0x5D, 0x01, None, 'Piece of Heart (Out of Logic)', ("Gerudo's Fortress", "Freestandings",))),
("GF HBA 1000 Points", ("NPC", 0x5D, 0x3E, None, 'Piece of Heart', ("Gerudo's Fortress", "Minigames",))),
("GF HBA 1500 Points", ("NPC", 0x5D, 0x30, None, 'Bow', ("Gerudo's Fortress", "Minigames",))),
("GF GS Top Floor", ("GS Token", 0x14, 0x02, None, 'Gold Skulltula Token', ("Gerudo's Fortress", "Gold Skulltulas",))),
Expand Down
6 changes: 4 additions & 2 deletions Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ def copy(addr, size):
# Add the extended texture data to the DMA table.
rom.update_dmadata_record_by_key(None, extended_textures_start, end_address)

save_context = SaveContext()

# Create an option so that recovery hearts no longer drop by changing the code which checks Link's health when an item is spawned.
if world.settings.no_collectible_hearts:
symbol = rom.sym('NO_COLLECTIBLE_HEARTS')
Expand Down Expand Up @@ -792,6 +794,8 @@ def set_entrance_updates(entrances: Iterable[Entrance]) -> None:

if world.settings.shuffle_hideout_entrances:
rom.write_byte(rom.sym('HIDEOUT_SHUFFLED'), 1)
if world.settings.shuffle_gerudo_fortress_heart_piece == 'remove':
save_context.write_permanent_flag(Scenes.GERUDO_FORTRESS, FlagType.COLLECT, 0x3, 0x02)

if world.shuffle_dungeon_entrances:
rom.write_byte(rom.sym('DUNGEONS_SHUFFLED'), 1)
Expand Down Expand Up @@ -839,8 +843,6 @@ def set_entrance_updates(entrances: Iterable[Entrance]) -> None:
rom.write_bytes(rom.sym('CFG_FILE_SELECT_HASH'), spoiler.file_hash)
rom.write_bytes(rom.sym('PASSWORD'), spoiler.password)

save_context = SaveContext()

# Initial Save Data
if not world.settings.useful_cutscenes and 'Forest Temple' not in world.settings.dungeon_shortcuts:
save_context.write_bits(0x00D4 + 0x03 * 0x1C + 0x04 + 0x0, 0x08) # Forest Temple switch flag (Poe Sisters cutscene)
Expand Down
3 changes: 2 additions & 1 deletion SaveContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ class Scenes(IntEnum):
WATER_TEMPLE = 0x05
SPIRIT_TEMPLE = 0x06
SHADOW_TEMPLE = 0x07
# Bean patch scenes
# Various overworld scenes
GRAVEYARD = 0x53
ZORAS_RIVER = 0x54
KOKIRI_FOREST = 0x55
LAKE_HYLIA = 0x57
GERUDO_VALLEY = 0x5A
LOST_WOODS = 0x5B
DESERT_COLOSSUS = 0x5C
GERUDO_FORTRESS = 0x5D
DEATH_MOUNTAIN_TRAIL = 0x60
DEATH_MOUNTAIN_CRATER = 0x61
GORON_CITY = 0x62
Expand Down
48 changes: 41 additions & 7 deletions SettingsList.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ class SettingInfos:
'settings': [
'open_forest', 'open_kakariko', 'open_door_of_time', 'zora_fountain', 'gerudo_fortress', 'dungeon_shortcuts_choice',
'dungeon_shortcuts', 'trials_random', 'trials',
'starting_age', 'shuffle_interior_entrances', 'shuffle_hideout_entrances',
'starting_age', 'shuffle_interior_entrances', 'shuffle_hideout_entrances', 'shuffle_gerudo_fortress_heart_piece',
'shuffle_grotto_entrances', 'shuffle_dungeon_entrances',
'shuffle_bosses', 'shuffle_overworld_entrances', 'shuffle_gerudo_valley_river_exit', 'owl_drops', 'warp_songs', 'spawn_positions',
'triforce_hunt', 'triforce_count_per_world', 'triforce_goal_per_world', 'free_bombchu_drops', 'one_item_per_dungeon',
Expand Down Expand Up @@ -664,7 +664,7 @@ class SettingInfos:
''',
disable = {
'glitchless': {'settings': ['tricks_list_msg']},
'glitched': {'settings': ['allowed_tricks', 'shuffle_interior_entrances', 'shuffle_hideout_entrances', 'shuffle_grotto_entrances',
'glitched': {'settings': ['allowed_tricks', 'shuffle_interior_entrances', 'shuffle_hideout_entrances', 'shuffle_gerudo_fortress_heart_piece', 'shuffle_grotto_entrances',
'shuffle_dungeon_entrances', 'shuffle_overworld_entrances', 'shuffle_gerudo_valley_river_exit', 'owl_drops',
'warp_songs', 'spawn_positions', 'mq_dungeons_mode', 'mq_dungeons_specific',
'mq_dungeons_count', 'shuffle_bosses', 'shuffle_ganon_tower', 'dungeon_shortcuts', 'deadly_bonks',
Expand Down Expand Up @@ -1667,7 +1667,7 @@ class SettingInfos:
''',
shared = True,
disable = {
'off': {'settings': ['shuffle_hideout_entrances']},
'off': {'settings': ['shuffle_hideout_entrances', 'shuffle_gerudo_fortress_heart_piece']},
},
gui_params = {
'randomize_key': 'randomize_settings',
Expand All @@ -1687,18 +1687,52 @@ class SettingInfos:

Note that savewarping in any room of Thieves' Hideout
always takes you to the first room (with 1 torch).

There is an extra heart piece on the balcony above the jail in
Gerudo's Fortress if accessed as child. This is not shuffled
and not considered in logic.
''',
default = False,
shared = True,
disable = {
False: {'settings': ['shuffle_gerudo_fortress_heart_piece']},
},
gui_params = {
'randomize_key': 'randomize_settings',
},
)

shuffle_gerudo_fortress_heart_piece = Combobox(
gui_text = 'Shuffle Gerudo Fortress Heart Piece',
default = 'remove',
choices = {
'remove': 'Remove',
'vanilla': 'Vanilla',
'shuffle': 'Shuffle',
},
gui_tooltip = '''\
There is an extra heart piece on the balcony above the jail in
Gerudo's Fortress if accessed as child. Normally, this is not
reachable without glitches, so it's not shuffled and not
considered in logic. With the Thieves' Hideout entrances
shuffled, it becomes reachable, so use this setting to decide
how to handle that.

'Remove':
The heart piece is completely removed from the game. There is
no freestanding item to collect above the jail.

'Vanilla':
The heart piece exists as an unshuffled heart piece. It is
considered in logic for the purpose of heart win conditions.

'Shuffle':
There is a shuffled item above the jail and the extra heart
piece is shuffled into the item pool. Both are considered in
logic.
''',
shared = True,
gui_params = {
"hide_when_disabled": True,
},
)

shuffle_grotto_entrances = Checkbutton(
gui_text = 'Shuffle Grotto Entrances',
gui_tooltip = '''\
Expand Down
1 change: 1 addition & 0 deletions data/Glitched World/Overworld.json
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@
"GF Chest": "(is_child and can_mega) or
(is_adult and can_use(Hover_Boots) or can_use(Scarecrow) or can_use(Longshot) or can_mega)",
#// known softlock if child opens this chest, so only put it in logic for adult
"GF Freestanding PoH": "is_child and can_hover",
"GF HBA 1000 Points": "
Gerudo_Membership_Card and can_ride_epona and Bow and is_adult",
"GF HBA 1500 Points": "
Expand Down
15 changes: 13 additions & 2 deletions data/World/Overworld.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"exits": {
"Root Exits": "is_starting_age or Time_Travel",
"HC Garden Skippable Locations": "skip_child_zelda",
"Beyond Door of Time Skippable Locations": "skip_reward_from_rauru"
"Beyond Door of Time Skippable Locations": "skip_reward_from_rauru",
# Hack to make ALR work if hideout entrances are unshuffled
"GF Above Jail Child Locations": "shuffle_gerudo_fortress_heart_piece == 'remove' or not shuffle_hideout_entrances"
fenhl marked this conversation as resolved.
Show resolved Hide resolved
}
},
{
Expand Down Expand Up @@ -854,7 +856,16 @@
"Hideout Hall to Balcony": "True",
"Gerudo Fortress": "True",
"GF Chest Roof": "can_use(Longshot)",
"GF Break Room Entrance": "damage_multiplier != 'ohko' or can_use(Nayrus_Love)"
"GF Break Room Entrance": "damage_multiplier != 'ohko' or can_use(Nayrus_Love)",
"GF Above Jail Child Locations": "is_child"
}
},
{
"region_name": "GF Above Jail Child Locations",
"scene": "Gerudo Fortress",
"hint": "GERUDO_FORTRESS",
"locations": {
"GF Freestanding PoH": "True"
}
},
{
Expand Down
11 changes: 11 additions & 0 deletions data/presets_default.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -247,6 +248,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -452,6 +454,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -664,6 +667,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -879,6 +883,7 @@
"empty_dungeons_count": 3,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -1090,6 +1095,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -1301,6 +1307,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "all",
"shuffle_hideout_entrances": true,
"shuffle_gerudo_fortress_heart_piece": "shuffle",
"shuffle_grotto_entrances": true,
"shuffle_dungeon_entrances": "all",
"shuffle_bosses": "full",
Expand Down Expand Up @@ -1686,6 +1693,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -1882,6 +1890,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "off",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -2113,6 +2122,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "remove",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "simple",
"shuffle_bosses": "off",
Expand Down Expand Up @@ -2324,6 +2334,7 @@
"empty_dungeons_count": 2,
"shuffle_interior_entrances": "off",
"shuffle_hideout_entrances": false,
"shuffle_gerudo_fortress_heart_piece": "vanilla",
"shuffle_grotto_entrances": false,
"shuffle_dungeon_entrances": "all",
"shuffle_bosses": "off",
Expand Down
1 change: 1 addition & 0 deletions data/settings_mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
"empty_dungeons_count",
"shuffle_interior_entrances",
"shuffle_hideout_entrances",
"shuffle_gerudo_fortress_heart_piece",
"shuffle_grotto_entrances",
"shuffle_dungeon_entrances",
"shuffle_bosses",
Expand Down
Loading