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

Require Gohma setting and ER compatibility #1531

Open
wants to merge 1 commit into
base: Dev
Choose a base branch
from
Open
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
119 changes: 76 additions & 43 deletions EntranceShuffle.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions HintList.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ def tokens_required_by_settings(world: World) -> int:
'Magic Meter': (["mystic training", "pixie dust", "a green rectangle"], "a Magic Meter", 'item'),
'Double Defense': (["a white outline", "damage decrease", "strengthened love"], "Double Defense", 'item'),
'Slingshot': (["a seed shooter", "a rubberband", "a child's catapult"], "a Slingshot", 'item'),
'Deku Seed Bag': (["a seed shooter", "a rubberband", "a child's catapult"], "a Slingshot", 'item'),
'Boomerang': (["a banana", "a stun stick"], "the Boomerang", 'item'),
'Bow': (["an archery enabler", "a danger dart launcher"], "a Bow", 'item'),
'Bomb Bag': (["an explosive container", "a blast bag"], "a Bomb Bag", 'item'),
Expand Down
2 changes: 2 additions & 0 deletions ItemList.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
'Bomb Bag': ('Item', True, 0x0082, None),
'Bow': ('Item', True, 0x0083, None),
'Slingshot': ('Item', True, 0x0084, None),
'Deku Seed Bag': ('Item', True, 0x0084, {'alias': ('Slingshot', 1)}),
'Progressive Wallet': ('Item', True, 0x0085, {'progressive': 3}),
'Progressive Scale': ('Item', True, 0x0086, {'progressive': 2}),
'Deku Nut Capacity': ('Item', None, 0x0087, None),
Expand Down Expand Up @@ -225,6 +226,7 @@

# Event items otherwise generated by generic event logic
# can be defined here to enforce their appearance in playthroughs.
'Deku Tree Clear': ('Event', True, None, None),
'Water Temple Clear': ('Event', True, None, None),
'Forest Trial Clear': ('Event', True, None, None),
'Fire Trial Clear': ('Event', True, None, None),
Expand Down
23 changes: 19 additions & 4 deletions ItemPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@
from World import World


closed_forest_restricted_items: tuple[str, ...] = (
'Bomb Bag',
'Bombchus (5)',
'Bombchus (10)',
'Bombchus (20)',
'Bombchus',
'Dins Fire',
'Progressive Scale',
'Bolero of Fire',
'Serenade of Water',
'Nocturne of Shadow',
'Requiem of Spirit',
'Prelude of Light',
)

plentiful_items: list[str] = ([
'Biggoron Sword',
'Boomerang',
Expand All @@ -34,7 +49,7 @@
'Deku Stick Capacity',
'Deku Nut Capacity',
'Bow',
'Slingshot',
'Deku Seed Bag',
'Bomb Bag',
'Double Defense'] +
['Heart Container'] * 8
Expand Down Expand Up @@ -70,7 +85,7 @@
'Progressive Wallet',
'Magic Meter',
'Bow',
'Slingshot',
'Deku Seed Bag',
'Bomb Bag',
'Bombchus (10)',
'Lens of Truth',
Expand Down Expand Up @@ -226,7 +241,7 @@
'Deku Stick Capacity': 1,
'Deku Nut Capacity': 1,
'Bow': 2,
'Slingshot': 2,
'Deku Seed Bag': 1,
'Bomb Bag': 2,
'Heart Container': 0,
},
Expand All @@ -240,7 +255,7 @@
'Deku Stick Capacity': 0,
'Deku Nut Capacity': 0,
'Bow': 1,
'Slingshot': 1,
'Deku Seed Bag': 0,
'Bomb Bag': 1,
'Heart Container': 0,
'Piece of Heart': 0,
Expand Down
4 changes: 2 additions & 2 deletions LocationList.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def shop_address(shop_id: int, shelf_id: int) -> int:
# Lost Woods
("LW Gift from Saria", ("Cutscene", 0xFF, 0x02, None, 'Ocarina', ("Lost Woods", "Forest Area", "NPCs",))),
("LW Ocarina Memory Game", ("NPC", 0x5B, 0x76, None, 'Piece of Heart', ("Lost Woods", "Forest Area", "Minigames",))),
("LW Target in Woods", ("NPC", 0x5B, 0x60, None, 'Slingshot', ("Lost Woods", "Forest Area", "NPCs",))),
("LW Target in Woods", ("NPC", 0x5B, 0x60, None, 'Deku Seed Bag', ("Lost Woods", "Forest Area", "NPCs",))),
("LW Near Shortcuts Grotto Chest", ("Chest", 0x3E, 0x14, None, 'Rupees (5)', ("Lost Woods", "Forest Area", "Grottos", "Chests",))),
("LW Trade Cojiro", ("NPC", 0x5B, 0x1F, None, 'Odd Mushroom', ("the Lost Woods", "Forest",))),
("LW Trade Odd Potion", ("NPC", 0x5B, 0x21, None, 'Poachers Saw', ("the Lost Woods", "Forest",))),
Expand Down Expand Up @@ -211,7 +211,7 @@ def shop_address(shop_id: int, shelf_id: int) -> int:
("HF Inside Fence Grotto Beehive", ("Beehive", 0x3E, (1,0,0x42 + (0x06 * 2)), None, 'Rupees (20)', ("Hyrule Field", "Grottos", "Beehives",))),

# Market
("Market Shooting Gallery Reward", ("NPC", 0x42, 0x60, None, 'Slingshot', ("Market", "Minigames",))),
("Market Shooting Gallery Reward", ("NPC", 0x42, 0x60, None, 'Deku Seed Bag', ("Market", "Minigames",))),
("Market Bombchu Bowling First Prize", ("NPC", 0x4B, 0x34, None, 'Bomb Bag', ("Market", "Minigames",))),
("Market Bombchu Bowling Second Prize", ("NPC", 0x4B, 0x3E, None, 'Piece of Heart', ("Market", "Minigames",))),
("Market Bombchu Bowling Bombchus", ("NPC", 0x4B, 0x03, None, 'Bombchus (10)', ("Market", "Minigames"))),
Expand Down
2 changes: 2 additions & 0 deletions Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,8 @@ def make_bytes(txt: str, size: int) -> list[int]:
rom.write_bytes(0xE5400E, [0xB4, 0xA4])
if world.settings.open_forest != 'closed':
rom.write_bytes(0xE5401C, [0x14, 0x0B])
# Move Link spawn 40 units forwards to prevent Pokey trap
rom.write_byte(0x206F0C7, 0xA3)

# Fix Shadow Temple to check for different rewards for scene
rom.write_bytes(0xCA3F32, [0x00, 0x00, 0x25, 0x4A, 0x00, 0x10])
Expand Down
13 changes: 13 additions & 0 deletions Region.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def alt_hint(self) -> Optional[HintArea]:

def can_fill(self, item: Item, manual: bool = False) -> bool:
from Hints import HintArea
from ItemPool import closed_forest_restricted_items

if not manual and self.world.settings.empty_dungeons_mode != 'none' and item.dungeonitem:
# An empty dungeon can only store its own dungeon items
Expand All @@ -101,6 +102,18 @@ def can_fill(self, item: Item, manual: bool = False) -> bool:
if item.world.empty_dungeons[dungeon.name].empty and dungeon.is_dungeon_item(item):
return False

if not manual and self.world.settings.require_gohma and item.name in (*closed_forest_restricted_items, 'Slingshot'):
hint_area = HintArea.at(self)
if hint_area.color == 'Green' and hint_area != HintArea.FOREST_TEMPLE and self.name != 'Queen Gohma Boss Room':
# Don't place items that can be used to escape the forest in Forest areas of worlds with Require Gohma
if item.name in closed_forest_restricted_items:
return False
else:
# Place at least one slingshot for each player in the Forest area, to avoid requiring one player to leave the forest to get another player's slingshot.
# This is still not a 100% guarantee because the slingshot could be behind an item that's not in the forest, such as in a bombable grotto entrance in the Lost Woods.
if item.name == 'Slingshot':
return False

is_self_dungeon_restricted = False
is_self_region_restricted = None
is_hint_color_restricted = None
Expand Down
5 changes: 4 additions & 1 deletion Rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Callable, Collection, Iterable
from typing import TYPE_CHECKING, Optional

from ItemPool import song_list
from ItemPool import closed_forest_restricted_items, song_list
from Location import Location, DisableType
from RulesCommon import AccessRule
from Search import Search
Expand Down Expand Up @@ -79,6 +79,9 @@ def set_rules(world: World) -> None:
if location.name in world.always_hints:
location.add_rule(guarantee_hint)

if world.settings.require_gohma and location in world.distribution.skipped_locations:
add_item_rule(location, lambda location, item: item.name not in closed_forest_restricted_items)

for location in world.settings.disabled_locations:
try:
world.get_location(location).disabled = DisableType.PENDING
Expand Down
4 changes: 4 additions & 0 deletions SaveContext.py
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,10 @@ def get_save_context_addresses() -> AddressesDict:
'item_slot.slingshot' : 'slingshot',
'upgrades.bullet_bag' : None,
},
"Deku Seed Bag" : {
'item_slot.slingshot' : 'slingshot',
'upgrades.bullet_bag' : None,
},
"Deku Seeds" : {
'ammo.slingshot' : None,
},
Expand Down
57 changes: 41 additions & 16 deletions SettingsList.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ class SettingInfos:
True: {
'sections': ['shuffle_section'],
'settings': [
'open_forest', 'open_kakariko', 'open_door_of_time', 'zora_fountain', 'gerudo_fortress', 'dungeon_shortcuts_choice',
'open_forest', 'require_gohma', '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',
'shuffle_grotto_entrances', 'shuffle_dungeon_entrances',
Expand Down Expand Up @@ -1657,30 +1657,28 @@ class SettingInfos:
'closed': 'Closed Forest',
},
gui_tooltip = '''\
'Open Forest': Mido no longer blocks the path to the
Deku Tree, and the Kokiri boy no longer blocks the path
out of the forest.
'Closed Forest': In the child era, Mido blocks the path
to the Deku Tree, requiring Kokiri Sword and Deku Shield
to access the Deku Tree, and another Kokiri boy blocks
the path out of the forest until Queen Gohma is defeated.
It may be logically required to "escape" the forest
(via one of the shortcuts in the Lost Woods, for example);
the setting "Closed Forest Requires Gohma" can be used to
prevent this.
'Closed Deku': The Kokiri boy no longer blocks the path
out of the forest, but Mido still blocks the path to the
Deku Tree, requiring Kokiri Sword and Deku Shield to access
the Deku Tree.
'Closed Forest': Beating Deku Tree is logically required
to leave the forest area (Kokiri Forest/Lost Woods/Sacred Forest
Meadow/Deku Tree), while the Kokiri Sword and a Deku Shield are
required to access the Deku Tree. Items needed for this will be
guaranteed inside the forest area. This setting is incompatible
with starting as adult, and so Starting Age will be locked to Child.
With either "Shuffle Interior Entrances" set to "All", "Shuffle
Overworld Entrances" on, "Randomize Warp Song Destinations" on
or "Randomize Overworld Spawns" on, Closed Forest will instead
be treated as Closed Deku with starting age Child and WILL NOT
guarantee that these items are available in the forest area.
'Open Forest': Mido no longer blocks the path to the
Deku Tree, and the Kokiri boy no longer blocks the path
out of the forest.
''',
shared = True,
disable = {
'closed': {'settings': ['starting_age']}
'!closed': {'settings': ['require_gohma']},
'closed': {'settings': ['starting_age']},
},
gui_params = {
'randomize_key': 'randomize_settings',
Expand All @@ -1692,6 +1690,33 @@ class SettingInfos:
},
)

require_gohma = Checkbutton(
gui_text = 'Closed Forest Requires Gohma',
gui_tooltip = '''\
Defeating Queen Gohma is required to leave the forest area
(Kokiri Forest/Lost Woods/Sacred Forest Meadow/Deku Tree).
Items needed for this will be guaranteed inside the forest area,
and items that could be used to escape the forest without
defeating Queen Gohma (such as explosives to enter Goron City)
will be prevented from appearing inside the forest area.
If entrances are shuffled, entrances inside and outside the
forest area will be shuffled separately. For example, "Shuffle
Dungeon Entrances" and "Shuffle Boss Entrances" don't affect the
Deku Tree. As an exception, grottos are not shuffled separately,
and neither are interiors if only simple interiors are shuffled.
This setting is incompatible with starting as adult, and so
Starting Age will be locked to Child.
''',
default = True,
disabled_default = False,
shared = True,
disable = {
True : {'settings' : ['open_forest']}
},
)

open_kakariko = Combobox(
gui_text = 'Kakariko Gate',
default = 'closed',
Expand Down
4 changes: 2 additions & 2 deletions SettingsListTricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,8 +505,8 @@
'tooltip' : '''\
A precise jump can be used to skip
needing to use the Slingshot to go
around B1 of the Deku Tree. If used
with the "Closed Forest" setting, a
around B1 of the Deku Tree. If using
"Closed Forest Requires Gohma", a
Slingshot will not be guaranteed to
exist somewhere inside the Forest.
This trick applies to both Vanilla
Expand Down
25 changes: 6 additions & 19 deletions World.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ def __init__(self, world_id: int, settings: Settings, resolve_randomized_setting
self.selected_adult_trade_item: str = ''
self.adult_trade_starting_inventory: str = ''

if (settings.open_forest == 'closed'
and (self.shuffle_special_interior_entrances or settings.shuffle_hideout_entrances or settings.shuffle_overworld_entrances
or settings.warp_songs or settings.spawn_positions)):
self.settings.open_forest = 'closed_deku'

if settings.triforce_goal_per_world > settings.triforce_count_per_world:
raise ValueError("Triforces required cannot be more than the triforce count.")
self.triforce_goal: int = settings.triforce_goal_per_world * settings.world_count
Expand Down Expand Up @@ -429,11 +424,7 @@ def resolve_random_settings(self) -> None:
if (self.settings.starting_age == 'random'
and ('starting_age' not in dist_keys
or self.distribution.distribution.src_dict['_settings']['starting_age'] == 'random')):
if self.settings.open_forest == 'closed':
# adult is not compatible
self.settings.starting_age = 'child'
else:
self.settings.starting_age = random.choice(['child', 'adult'])
self.settings.starting_age = random.choice(['child', 'adult'])
self.randomized_list.append('starting_age')
if self.settings.chicken_count_random and 'chicken_count' not in dist_keys:
self.settings.chicken_count = random.randint(0, 7)
Expand Down Expand Up @@ -1110,16 +1101,12 @@ def push_item(self, location: str | Location, item: Item, manual: bool = False)
if not isinstance(location, Location):
location = self.get_location(location)

# This check should never be false normally, but is here as a sanity check
if location.can_fill_fast(item, manual) and item.world:
location.item = item
item.location = location
item.price = location.price if location.price is not None else item.price
location.price = item.price
location.item = item
item.location = location
item.price = location.price if location.price is not None else item.price
location.price = item.price

logging.getLogger('').debug('Placed %s [World %d] at %s [World %d]', item, item.world.id if hasattr(item, 'world') else -1, location, location.world.id if hasattr(location, 'world') else -1)
else:
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
logging.getLogger('').debug('Placed %s [World %d] at %s [World %d]', item, item.world.id if hasattr(item, 'world') else -1, location, location.world.id if hasattr(location, 'world') else -1)

def get_locations(self) -> list[Location]:
if not self._cached_locations:
Expand Down
8 changes: 5 additions & 3 deletions data/Glitched World/Deku Tree MQ.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,18 @@
"region_name": "Deku Tree Boss Room",
"dungeon": "Deku Tree",
"events": {
"Deku Tree Clear": "Deku_Shield and (Kokiri_Sword or Sticks)"
"Defeat Queen Gohma": "Deku_Shield and (Kokiri_Sword or Sticks)",
# separate event which appears in the playthrough only if it's required to open the forest exit
"Deku Tree Clear": "'Defeat Queen Gohma'"
},
"locations": {
"Deku Tree MQ Before Spinning Log Chest": "True",
"Deku Tree MQ After Spinning Log Chest": "can_play(Song_of_Time)",
"Deku Tree MQ GS Basement Graves Room": "Boomerang and can_play(Song_of_Time)",
"Deku Tree MQ GS Basement Back Room": "Boomerang",
"Deku Tree MQ Deku Scrub": "True",
"Deku Tree Queen Gohma Heart": "Deku_Shield and (Kokiri_Sword or Sticks)",
"Queen Gohma": "Deku_Shield and (Kokiri_Sword or Sticks)"
"Deku Tree Queen Gohma Heart": "'Defeat Queen Gohma'",
"Queen Gohma": "'Defeat Queen Gohma'"
},
"exits": {
"Deku Tree Lobby": "True"
Expand Down
4 changes: 3 additions & 1 deletion data/Glitched World/Deku Tree.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
"dungeon": "Deku Tree",
"events": {
"Defeat Queen Gohma": "(Nuts or can_use(Slingshot) or has_bombchus or can_use(Hookshot) or can_use(Bow) or can_use(Boomerang)) and
((here(has_shield or can_use(Megaton_Hammer)) and (is_adult or Kokiri_Sword or Sticks)) or is_adult)"
((here(has_shield or can_use(Megaton_Hammer)) and (is_adult or Kokiri_Sword or Sticks)) or is_adult)",
# separate event which appears in the playthrough only if it's required to open the forest exit
"Deku Tree Clear": "'Defeat Queen Gohma'"
},
"locations": {
"Deku Tree Queen Gohma Heart": "'Defeat Queen Gohma'",
Expand Down
Loading