diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ce2262ca5..b79e80c9f 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -87,8 +87,8 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude # ZR Zora's River entrance_shuffle_table = [ - ('Dungeon', ('KF Outside Deku Tree -> Deku Tree Lobby', { 'index': 0x0000 }), - ('Deku Tree Lobby -> KF Outside Deku Tree', { 'index': 0x0209 })), + ('Dungeon', ('KF Outside Deku Tree -> Deku Tree Lobby', { 'index': 0x0000, 'forest': True, 'deku': True }), + ('Deku Tree Lobby -> KF Outside Deku Tree', { 'index': 0x0209, 'forest': True, 'deku': True })), ('Dungeon', ('Death Mountain -> Dodongos Cavern Beginning', { 'index': 0x0004 }), ('Dodongos Cavern Beginning -> Death Mountain', { 'index': 0x0242 })), ('Dungeon', ('Zoras Fountain -> Jabu Jabus Belly Beginning', { 'index': 0x0028 }), @@ -113,8 +113,8 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('DungeonSpecial', ('Ganons Castle Grounds -> Ganons Castle Lobby', { 'index': 0x0467 }), ('Ganons Castle Lobby -> Castle Grounds From Ganons Castle', { 'index': 0x023D })), - ('ChildBoss', ('Deku Tree Before Boss -> Queen Gohma Boss Room', { 'index': 0x040f, 'savewarp_addresses': [ 0xB06292, 0xBC6162, 0xBC60AE ] }), - ('Queen Gohma Boss Room -> Deku Tree Before Boss', { 'index': 0x0252 })), + ('ChildBoss', ('Deku Tree Before Boss -> Queen Gohma Boss Room', { 'index': 0x040f, 'savewarp_addresses': [ 0xB06292, 0xBC6162, 0xBC60AE ], 'forest': True, 'deku': True }), + ('Queen Gohma Boss Room -> Deku Tree Before Boss', { 'index': 0x0252, 'forest': True, 'deku': True })), ('ChildBoss', ('Dodongos Cavern Before Boss -> King Dodongo Boss Room', { 'index': 0x040b, 'savewarp_addresses': [ 0xB062B6, 0xBC616E ] }), ('King Dodongo Boss Room -> Dodongos Cavern Mouth', { 'index': 0x00c5 })), ('ChildBoss', ('Jabu Jabus Belly Before Boss -> Barinade Boss Room', { 'index': 0x0301, 'savewarp_addresses': [ 0xB062C2, 0xBC60C2 ] }), @@ -130,16 +130,16 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('AdultBoss', ('Spirit Temple Before Boss -> Twinrova Boss Room', { 'index': 0x008D, 'savewarp_addresses': [ 0xB062F2, 0xBC6122 ] }), ('Twinrova Boss Room -> Spirit Temple Before Boss', { 'index': 0x02F5 })), - ('Interior', ('Kokiri Forest -> KF Midos House', { 'index': 0x0433 }), - ('KF Midos House -> Kokiri Forest', { 'index': 0x0443 })), - ('Interior', ('Kokiri Forest -> KF Sarias House', { 'index': 0x0437 }), - ('KF Sarias House -> Kokiri Forest', { 'index': 0x0447 })), - ('Interior', ('Kokiri Forest -> KF House of Twins', { 'index': 0x009C }), - ('KF House of Twins -> Kokiri Forest', { 'index': 0x033C })), - ('Interior', ('Kokiri Forest -> KF Know It All House', { 'index': 0x00C9 }), - ('KF Know It All House -> Kokiri Forest', { 'index': 0x026A })), - ('Interior', ('Kokiri Forest -> KF Kokiri Shop', { 'index': 0x00C1 }), - ('KF Kokiri Shop -> Kokiri Forest', { 'index': 0x0266 })), + ('Interior', ('Kokiri Forest -> KF Midos House', { 'index': 0x0433, 'forest': True }), + ('KF Midos House -> Kokiri Forest', { 'index': 0x0443, 'forest': True })), + ('Interior', ('Kokiri Forest -> KF Sarias House', { 'index': 0x0437, 'forest': True }), + ('KF Sarias House -> Kokiri Forest', { 'index': 0x0447, 'forest': True })), + ('Interior', ('Kokiri Forest -> KF House of Twins', { 'index': 0x009C, 'forest': True }), + ('KF House of Twins -> Kokiri Forest', { 'index': 0x033C, 'forest': True })), + ('Interior', ('Kokiri Forest -> KF Know It All House', { 'index': 0x00C9, 'forest': True }), + ('KF Know It All House -> Kokiri Forest', { 'index': 0x026A, 'forest': True })), + ('Interior', ('Kokiri Forest -> KF Kokiri Shop', { 'index': 0x00C1, 'forest': True }), + ('KF Kokiri Shop -> Kokiri Forest', { 'index': 0x0266, 'forest': True })), ('Interior', ('Lake Hylia -> LH Lab', { 'index': 0x0043 }), ('LH Lab -> Lake Hylia', { 'index': 0x03CC })), ('Interior', ('LH Fishing Island -> LH Fishing Hole', { 'index': 0x045F }), @@ -203,8 +203,8 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('Interior', ('Zoras Fountain -> ZF Great Fairy Fountain', { 'index': 0x0371 }), ('ZF Great Fairy Fountain -> Zoras Fountain', { 'index': 0x0394, 'addresses': [0xBEFD7E] })), - ('SpecialInterior', ('Kokiri Forest -> KF Links House', { 'index': 0x0272 }), - ('KF Links House -> Kokiri Forest', { 'index': 0x0211 })), + ('SpecialInterior', ('Kokiri Forest -> KF Links House', { 'index': 0x0272, 'forest': True }), + ('KF Links House -> Kokiri Forest', { 'index': 0x0211, 'forest': True })), ('SpecialInterior', ('ToT Entrance -> Temple of Time', { 'index': 0x0053 }), ('Temple of Time -> ToT Entrance', { 'index': 0x0472 })), ('SpecialInterior', ('Kakariko Village -> Kak Windmill', { 'index': 0x0453 }), @@ -287,16 +287,16 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('LLR Grotto -> Lon Lon Ranch', { 'grotto_id': 0x15, 'savewarp_fallback': 0x05D4 })), ('Grotto', ('SFM Entryway -> SFM Wolfos Grotto', { 'grotto_id': 0x16, 'entrance': 0x05B4, 'content': 0xED, 'scene': 0x56 }), ('SFM Wolfos Grotto -> SFM Entryway', { 'grotto_id': 0x16, 'savewarp_fallback': 0x00FC })), - ('Grotto', ('Sacred Forest Meadow -> SFM Storms Grotto', { 'grotto_id': 0x17, 'entrance': 0x05BC, 'content': 0xEE, 'scene': 0x56 }), - ('SFM Storms Grotto -> Sacred Forest Meadow', { 'grotto_id': 0x17, 'savewarp_fallback': 0x0600 })), - ('Grotto', ('Sacred Forest Meadow -> SFM Fairy Grotto', { 'grotto_id': 0x18, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x56 }), - ('SFM Fairy Grotto -> Sacred Forest Meadow', { 'grotto_id': 0x18, 'savewarp_fallback': 0x0600 })), + ('Grotto', ('Sacred Forest Meadow -> SFM Storms Grotto', { 'grotto_id': 0x17, 'entrance': 0x05BC, 'content': 0xEE, 'scene': 0x56, 'forest': True }), + ('SFM Storms Grotto -> Sacred Forest Meadow', { 'grotto_id': 0x17, 'savewarp_fallback': 0x0600, 'forest': True })), + ('Grotto', ('Sacred Forest Meadow -> SFM Fairy Grotto', { 'grotto_id': 0x18, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x56, 'forest': True }), + ('SFM Fairy Grotto -> Sacred Forest Meadow', { 'grotto_id': 0x18, 'savewarp_fallback': 0x0600, 'forest': True })), ('Grotto', ('LW Beyond Mido -> LW Scrubs Grotto', { 'grotto_id': 0x19, 'entrance': 0x05B0, 'content': 0xF5, 'scene': 0x5B }), ('LW Scrubs Grotto -> LW Beyond Mido', { 'grotto_id': 0x19, 'savewarp_fallback': 0x01A9 })), ('Grotto', ('Lost Woods -> LW Near Shortcuts Grotto', { 'grotto_id': 0x1A, 'entrance': 0x003F, 'content': 0x14, 'scene': 0x5B }), ('LW Near Shortcuts Grotto -> Lost Woods', { 'grotto_id': 0x1A, 'savewarp_fallback': 0x04D6 })), - ('Grotto', ('Kokiri Forest -> KF Storms Grotto', { 'grotto_id': 0x1B, 'entrance': 0x003F, 'content': 0x2C, 'scene': 0x55 }), - ('KF Storms Grotto -> Kokiri Forest', { 'grotto_id': 0x1B, 'savewarp_fallback': 0x0286 })), + ('Grotto', ('Kokiri Forest -> KF Storms Grotto', { 'grotto_id': 0x1B, 'entrance': 0x003F, 'content': 0x2C, 'scene': 0x55, 'forest': True }), + ('KF Storms Grotto -> Kokiri Forest', { 'grotto_id': 0x1B, 'savewarp_fallback': 0x0286, 'forest': True })), ('Grotto', ('Zoras Domain -> ZD Storms Grotto', { 'grotto_id': 0x1C, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x58 }), ('ZD Storms Grotto -> Zoras Domain', { 'grotto_id': 0x1C, 'savewarp_fallback': 0x0108 })), ('Grotto', ('GF Entrances Behind Crates -> GF Storms Grotto', { 'grotto_id': 0x1D, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x5D }), @@ -305,8 +305,8 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('GV Storms Grotto -> GV Fortress Side', { 'grotto_id': 0x1E, 'savewarp_fallback': 0x022D })), ('Grotto', ('GV Grotto Ledge -> GV Octorok Grotto', { 'grotto_id': 0x1F, 'entrance': 0x05AC, 'content': 0xF2, 'scene': 0x5A }), ('GV Octorok Grotto -> GV Grotto Ledge', { 'grotto_id': 0x1F, 'savewarp_fallback': 0x0117 })), #TODO (out-of-logic access to Gerudo Valley) - ('Grotto', ('LW Beyond Mido -> Deku Theater', { 'grotto_id': 0x20, 'entrance': 0x05C4, 'content': 0xF3, 'scene': 0x5B }), - ('Deku Theater -> LW Beyond Mido', { 'grotto_id': 0x20, 'savewarp_fallback': 0x01A9 })), + ('Grotto', ('LW Beyond Mido -> Deku Theater', { 'grotto_id': 0x20, 'entrance': 0x05C4, 'content': 0xF3, 'scene': 0x5B, 'forest': True }), + ('Deku Theater -> LW Beyond Mido', { 'grotto_id': 0x20, 'savewarp_fallback': 0x01A9, 'forest': True })), ('Grave', ('Graveyard -> Graveyard Shield Grave', { 'index': 0x004B }), ('Graveyard Shield Grave -> Graveyard', { 'index': 0x035D })), @@ -319,14 +319,14 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('Overworld', ('Kokiri Forest -> LW Bridge From Forest', { 'index': 0x05E0 }), ('LW Bridge -> Kokiri Forest', { 'index': 0x020D })), - ('Overworld', ('Kokiri Forest -> Lost Woods', { 'index': 0x011E }), - ('LW Forest Exit -> Kokiri Forest', { 'index': 0x0286 })), - ('Overworld', ('Lost Woods -> GC Woods Warp', { 'index': 0x04E2 }), - ('GC Woods Warp -> Lost Woods', { 'index': 0x04D6 })), + ('Overworld', ('Kokiri Forest -> Lost Woods', { 'index': 0x011E, 'forest': True }), + ('LW Forest Exit -> Kokiri Forest', { 'index': 0x0286, 'forest': True })), + ('Overworld', ('Lost Woods -> GC Woods Warp', { 'index': 0x04E2, 'forest': True }), + ('GC Woods Warp -> Lost Woods', { 'index': 0x04D6, 'forest': True })), ('Overworld', ('Lost Woods -> Zora River', { 'index': 0x01DD }), ('Zora River -> LW Underwater Entrance', { 'index': 0x04DA })), - ('Overworld', ('LW Beyond Mido -> SFM Entryway', { 'index': 0x00FC }), - ('SFM Entryway -> LW Beyond Mido', { 'index': 0x01A9 })), + ('Overworld', ('LW Beyond Mido -> SFM Entryway', { 'index': 0x00FC, 'forest': True }), + ('SFM Entryway -> LW Beyond Mido', { 'index': 0x01A9, 'forest': True })), ('Overworld', ('LW Bridge -> Hyrule Field', { 'index': 0x0185 }), ('Hyrule Field -> LW Bridge', { 'index': 0x04DE })), ('Overworld', ('Hyrule Field -> Lake Hylia', { 'index': 0x0102 }), @@ -375,17 +375,17 @@ def build_one_way_targets(world: World, types_to_include: Iterable[str], exclude ('OwlDrop', ('LH Owl Flight -> Hyrule Field', { 'index': 0x027E, 'addresses': [0xAC9F26] })), ('OwlDrop', ('DMT Owl Flight -> Kak Impas Rooftop', { 'index': 0x0554, 'addresses': [0xAC9EF2] })), - ('Spawn', ('Child Spawn -> KF Links House', { 'index': 0x00BB, 'addresses': [0xB06342] })), + ('Spawn', ('Child Spawn -> KF Links House', { 'index': 0x00BB, 'addresses': [0xB06342], 'forest': True })), ('Spawn', ('Adult Spawn -> Temple of Time', { 'index': 0x05F4, 'addresses': [0xB06332] })), - ('WarpSong', ('Minuet of Forest Warp -> Sacred Forest Meadow', { 'index': 0x0600, 'addresses': [0xBF023C] })), + ('WarpSong', ('Minuet of Forest Warp -> Sacred Forest Meadow', { 'index': 0x0600, 'addresses': [0xBF023C], 'forest': True })), ('WarpSong', ('Bolero of Fire Warp -> DMC Central Local', { 'index': 0x04F6, 'addresses': [0xBF023E] })), ('WarpSong', ('Serenade of Water Warp -> Lake Hylia', { 'index': 0x0604, 'addresses': [0xBF0240] })), ('WarpSong', ('Requiem of Spirit Warp -> Desert Colossus', { 'index': 0x01F1, 'addresses': [0xBF0242] })), ('WarpSong', ('Nocturne of Shadow Warp -> Graveyard Warp Pad Region', { 'index': 0x0568, 'addresses': [0xBF0244] })), ('WarpSong', ('Prelude of Light Warp -> Temple of Time', { 'index': 0x05F4, 'addresses': [0xBF0246] })), - ('BlueWarp', ('Queen Gohma Boss Room -> KF Outside Deku Tree', { 'index': 0x0457, 'addresses': [0xAC93A2, 0xCA3142] })), + ('BlueWarp', ('Queen Gohma Boss Room -> KF Outside Deku Tree', { 'index': 0x0457, 'addresses': [0xAC93A2, 0xCA3142], 'forest': True, 'deku': True })), ('BlueWarp', ('King Dodongo Boss Room -> Death Mountain', { 'index': 0x047A, 'addresses': [0xAC9336, 0xCA30CA] })), ('BlueWarp', ('Barinade Boss Room -> Zoras Fountain', { 'index': 0x010E, 'addresses': [0xAC936A, 0xCA31B2] })), ('BlueWarp', ('Phantom Ganon Boss Room -> Sacred Forest Meadow', { 'index': 0x0608, 'addresses': [0xAC9F96, 0xCA3D66, 0xCA3D5A], 'child_index': 0x0600 })), @@ -503,14 +503,14 @@ def shuffle_random_entrances(worlds: list[World]) -> None: if worlds[0].settings.shuffle_bosses == 'full': entrance_pools['Boss'] = world.get_shufflable_entrances(type='ChildBoss', only_primary=True) entrance_pools['Boss'] += world.get_shufflable_entrances(type='AdultBoss', only_primary=True) - if worlds[0].settings.open_forest == 'closed': + if worlds[0].settings.require_gohma: # Deku is forced vanilla below, so Queen Gohma must be vanilla to ensure she is reachable. # This is already enforced by the fill algorithm in most cases, but this covers the odd settings combination where it isn't. entrance_pools['Boss'].remove(world.get_entrance('Deku Tree Before Boss -> Queen Gohma Boss Room')) elif worlds[0].settings.shuffle_bosses == 'limited': entrance_pools['ChildBoss'] = world.get_shufflable_entrances(type='ChildBoss', only_primary=True) entrance_pools['AdultBoss'] = world.get_shufflable_entrances(type='AdultBoss', only_primary=True) - if worlds[0].settings.open_forest == 'closed': + if worlds[0].settings.require_gohma: # Deku is forced vanilla below, so Queen Gohma must be vanilla to ensure she is reachable. # This is already enforced by the fill algorithm in most cases, but this covers the odd settings combination where it isn't. entrance_pools['ChildBoss'].remove(world.get_entrance('Deku Tree Before Boss -> Queen Gohma Boss Room')) @@ -519,8 +519,8 @@ def shuffle_random_entrances(worlds: list[World]) -> None: entrance_pools['Dungeon'] = world.get_shufflable_entrances(type='Dungeon', only_primary=True) # The fill algorithm will already make sure gohma is reachable, however it can end up putting # a forest escape via the hands of spirit on Deku leading to Deku on spirit in logic. This is - # not really a closed forest anymore, so specifically remove Deku Tree from closed forest. - if worlds[0].settings.open_forest == 'closed': + # contrary to the idea of Require Gohma, so specifically place Deku Tree in its vanilla location. + if worlds[0].settings.require_gohma: entrance_pools['Dungeon'].remove(world.get_entrance('KF Outside Deku Tree -> Deku Tree Lobby')) if worlds[0].shuffle_special_dungeon_entrances: entrance_pools['Dungeon'] += world.get_shufflable_entrances(type='DungeonSpecial', only_primary=True) @@ -607,7 +607,17 @@ def shuffle_random_entrances(worlds: list[World]) -> None: # Shuffle all entrances among the pools to shuffle for pool_type, entrance_pool in one_way_entrance_pools.items(): - placed_one_way_entrances += shuffle_entrance_pool(world, worlds, entrance_pool, one_way_target_entrance_pools[pool_type], locations_to_ensure_reachable, check_all=True, placed_one_way_entrances=placed_one_way_entrances) + if world.settings.require_gohma and pool_type not in ('OverworldOneWay', 'OwlDrop'): + # These entrance pools can potentially be accessed from inside the forest. + # To prevent a forest escape, shuffle entrances of this type inside and outside the forest separately. + forest_entrance_pool = list(filter(lambda entrance: entrance.data.get('forest', False), entrance_pool)) + outside_entrance_pool = list(filter(lambda entrance: not entrance.data.get('forest', False), entrance_pool)) + forest_target_pool = list(filter(lambda entrance: entrance.replaces.data.get('forest', False) and not entrance.replaces.data.get('deku', False), one_way_target_entrance_pools[pool_type])) + outside_target_pool = list(filter(lambda entrance: not entrance.replaces.data.get('forest', False) or entrance.replaces.data.get('deku', False), one_way_target_entrance_pools[pool_type])) + placed_one_way_entrances += shuffle_entrance_pool(world, worlds, forest_entrance_pool, forest_target_pool, locations_to_ensure_reachable, check_all=True, placed_one_way_entrances=placed_one_way_entrances) + placed_one_way_entrances += shuffle_entrance_pool(world, worlds, outside_entrance_pool, outside_target_pool, locations_to_ensure_reachable, check_all=True, placed_one_way_entrances=placed_one_way_entrances) + else: + placed_one_way_entrances += shuffle_entrance_pool(world, worlds, entrance_pool, one_way_target_entrance_pools[pool_type], locations_to_ensure_reachable, check_all=True, placed_one_way_entrances=placed_one_way_entrances) # Delete all targets that we just placed from other one way target pools so multiple one way entrances don't use the same target replaced_entrances = [entrance.replaces for entrance in entrance_pool] for remaining_target in chain.from_iterable(one_way_target_entrance_pools.values()): @@ -618,7 +628,26 @@ def shuffle_random_entrances(worlds: list[World]) -> None: delete_target_entrance(unused_target) for pool_type, entrance_pool in entrance_pools.items(): - shuffle_entrance_pool(world, worlds, entrance_pool, target_entrance_pools[pool_type], locations_to_ensure_reachable, placed_one_way_entrances=placed_one_way_entrances) + if world.settings.require_gohma and ( + pool_type in ('Dungeon', 'ChildBoss', 'Boss', 'Overworld') + or (pool_type == 'Interior' and ( + world.shuffle_special_interior_entrances + or (world.shuffle_interior_entrances and ( + 'child' in world.settings.spawn_positions # to avoid spawning in a forest interior that has been placed outside the forest + or world.settings.warp_songs # to avoid Minuet leading inside a forest interior that has been placed outside the forest + )) + )) + ): + # These entrance pools can potentially be accessed from inside the forest. + # To prevent a forest escape, shuffle entrances of this type inside and outside the forest separately. + forest_entrance_pool = list(filter(lambda entrance: entrance.data.get('forest', False), entrance_pool)) + outside_entrance_pool = list(filter(lambda entrance: not entrance.data.get('forest', False), entrance_pool)) + forest_target_pool = list(filter(lambda entrance: entrance.replaces.data.get('forest', False), target_entrance_pools[pool_type])) + outside_target_pool = list(filter(lambda entrance: not entrance.replaces.data.get('forest', False), target_entrance_pools[pool_type])) + shuffle_entrance_pool(world, worlds, forest_entrance_pool, forest_target_pool, locations_to_ensure_reachable, placed_one_way_entrances=placed_one_way_entrances) + shuffle_entrance_pool(world, worlds, outside_entrance_pool, outside_target_pool, locations_to_ensure_reachable, placed_one_way_entrances=placed_one_way_entrances) + else: + shuffle_entrance_pool(world, worlds, entrance_pool, target_entrance_pools[pool_type], locations_to_ensure_reachable, placed_one_way_entrances=placed_one_way_entrances) # Determine blue warp targets # if a boss room is inside a boss door, make the blue warp go outside the dungeon's entrance @@ -830,7 +859,10 @@ def place_one_way_priority_entrance(worlds: list[World], world: World, priority_ # Shuffle this list. # Pick the first one not already set, not adult spawn, that has a valid target entrance. # Assemble then clear entrances from the pool and target pools as appropriate. - avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools)) + avail_pool = chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools) + if world.settings.require_gohma: + avail_pool = filter(lambda entrance: not entrance.data.get('forest', False), avail_pool) + avail_pool = list(avail_pool) random.shuffle(avail_pool) for entrance in avail_pool: if entrance.replaces: @@ -984,8 +1016,8 @@ def validate_world(world: World, worlds: list[World], entrance_placed: Optional[ if not any(region for region in valid_starting_regions if no_items_search.can_reach(world.get_region(region))): raise EntranceShuffleError('Invalid starting area') - # Check that a region where time passes is always reachable as both ages without having collected any items - time_travel_search = Search.with_items([w.state for w in worlds], [ItemFactory('Time Travel', world=w) for w in worlds]) + # Check that after leaving the forest, a region where time passes is always reachable as both ages without having collected any items + time_travel_search = Search.with_items([w.state for w in worlds], [ItemFactory('Time Travel', world=w) for w in worlds] + [ItemFactory('Deku Tree Clear', world=w, event=True) for w in worlds]) if not (any(region for region in time_travel_search.reachable_regions('child') if region.time_passes and region.world == world) and any(region for region in time_travel_search.reachable_regions('adult') if region.time_passes and region.world == world)): @@ -1003,7 +1035,8 @@ def validate_world(world: World, worlds: list[World], entrance_placed: Optional[ # The Big Poe Shop should always be accessible as adult without the need to use any bottles # This is important to ensure that players can never lock their only bottles by filling them with Big Poes they can't sell # We can use starting items in this check as long as there are no exits requiring the use of a bottle without refills - time_travel_search = Search.with_items([w.state for w in worlds], [ItemFactory('Time Travel', world=w) for w in worlds]) + # We can assume forest exit since Hyrule Field is not in the forest and Bottle with Big Poe is not a logical bottle + time_travel_search = Search.with_items([w.state for w in worlds], [ItemFactory('Time Travel', world=w) for w in worlds] + [ItemFactory('Deku Tree Clear', world=w, event=True) for w in worlds]) if not time_travel_search.can_reach(world.get_region('Market Guard House'), age='adult'): raise EntranceShuffleError('Big Poe Shop access is not guaranteed as adult') diff --git a/HintList.py b/HintList.py index de94ad3bd..ee81d8461 100644 --- a/HintList.py +++ b/HintList.py @@ -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'), diff --git a/ItemList.py b/ItemList.py index 22ecf1f1a..6214fafbc 100644 --- a/ItemList.py +++ b/ItemList.py @@ -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), @@ -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), diff --git a/ItemPool.py b/ItemPool.py index c0869dd6d..9a0a7c8da 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -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', @@ -34,7 +49,7 @@ 'Deku Stick Capacity', 'Deku Nut Capacity', 'Bow', - 'Slingshot', + 'Deku Seed Bag', 'Bomb Bag', 'Double Defense'] + ['Heart Container'] * 8 @@ -70,7 +85,7 @@ 'Progressive Wallet', 'Magic Meter', 'Bow', - 'Slingshot', + 'Deku Seed Bag', 'Bomb Bag', 'Bombchus (10)', 'Lens of Truth', @@ -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, }, @@ -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, diff --git a/LocationList.py b/LocationList.py index f74a570f4..c69940321 100644 --- a/LocationList.py +++ b/LocationList.py @@ -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",))), @@ -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"))), diff --git a/Patches.py b/Patches.py index f64244a9e..37ac637ac 100644 --- a/Patches.py +++ b/Patches.py @@ -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]) diff --git a/Region.py b/Region.py index 1e03f748c..52ba5781b 100644 --- a/Region.py +++ b/Region.py @@ -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 @@ -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 diff --git a/Rules.py b/Rules.py index 43e568445..f96ab8a25 100644 --- a/Rules.py +++ b/Rules.py @@ -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 @@ -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 diff --git a/SaveContext.py b/SaveContext.py index 7f7475ebe..98d297da0 100644 --- a/SaveContext.py +++ b/SaveContext.py @@ -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, }, diff --git a/SettingsList.py b/SettingsList.py index 72b13a8ba..106e59432 100644 --- a/SettingsList.py +++ b/SettingsList.py @@ -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', @@ -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', @@ -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', diff --git a/SettingsListTricks.py b/SettingsListTricks.py index bcb080950..5edb2f4d5 100644 --- a/SettingsListTricks.py +++ b/SettingsListTricks.py @@ -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 diff --git a/World.py b/World.py index c06c20746..877431c00 100644 --- a/World.py +++ b/World.py @@ -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 @@ -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) @@ -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: diff --git a/data/Glitched World/Deku Tree MQ.json b/data/Glitched World/Deku Tree MQ.json index 9bb8e7769..bf60ad70f 100644 --- a/data/Glitched World/Deku Tree MQ.json +++ b/data/Glitched World/Deku Tree MQ.json @@ -31,7 +31,9 @@ "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", @@ -39,8 +41,8 @@ "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" diff --git a/data/Glitched World/Deku Tree.json b/data/Glitched World/Deku Tree.json index 6647b39a4..a5747bfe3 100644 --- a/data/Glitched World/Deku Tree.json +++ b/data/Glitched World/Deku Tree.json @@ -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'", diff --git a/data/Glitched World/Overworld.json b/data/Glitched World/Overworld.json index 357aa29bf..77ecdcd8c 100644 --- a/data/Glitched World/Overworld.json +++ b/data/Glitched World/Overworld.json @@ -53,7 +53,7 @@ "KF Kokiri Shop": "True", "KF Outside Deku Tree": "'Showed Mido Sword & Shield' or Sticks or is_adult", "Lost Woods": "True", - "LW Bridge": "can_leave_forest", + "LW Bridge": "open_forest != 'closed' or is_adult or 'Deku Tree Clear'", "KF Storms Grotto": "can_play(Song_of_Storms)" } }, @@ -192,7 +192,7 @@ "LW Gift from Saria": "True" }, "exits": { - "Kokiri Forest": "True", + "Kokiri Forest": "True", # Walking through this entrance places Link past the Kokiri guard, so there are no restrictions for Closed Forest here "Hyrule Field": "True" } }, diff --git a/data/LogicHelpers.json b/data/LogicHelpers.json index b9a9ac19c..fb0370aae 100644 --- a/data/LogicHelpers.json +++ b/data/LogicHelpers.json @@ -43,7 +43,7 @@ "can_child_damage": "is_child and (Slingshot or Sticks or Kokiri_Sword or has_explosives or can_use(Dins_Fire))", "can_cut_shrubs": "is_adult or Sticks or Kokiri_Sword or Boomerang or has_explosives", "can_dive": "Progressive_Scale", - "can_leave_forest": "open_forest != 'closed' or is_adult or is_glitched or 'Defeat Queen Gohma'", + "can_leave_forest": "is_adult or 'Deku Tree Clear' or not require_gohma", "can_plant_bugs": "is_child and Bugs", "can_ride_epona": "is_adult and Epona and (can_play(Eponas_Song) or (is_glitched and can_hover))", "can_stun_deku": "is_adult or (Slingshot or Boomerang or Sticks or Kokiri_Sword or has_explosives or can_use(Dins_Fire) or Nuts or Deku_Shield)", diff --git a/data/World/Bosses.json b/data/World/Bosses.json index a45db2d99..ee379fb60 100644 --- a/data/World/Bosses.json +++ b/data/World/Bosses.json @@ -7,7 +7,9 @@ "scene": "Deku Tree Boss", "is_boss_room": "True", "events": { - "Defeat Queen Gohma": "(Nuts or can_use(Slingshot)) and can_jumpslash" + "Defeat Queen Gohma": "(Nuts or can_use(Slingshot)) and can_jumpslash", + # 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'", diff --git a/data/World/Overworld.json b/data/World/Overworld.json index 96ca25f86..7afc17be7 100644 --- a/data/World/Overworld.json +++ b/data/World/Overworld.json @@ -188,7 +188,7 @@ "KF Kokiri Shop": "True", "KF Outside Deku Tree": "is_adult or open_forest == 'open' or 'Showed Mido Sword & Shield'", "Lost Woods": "True", - "LW Bridge From Forest": "can_leave_forest", + "LW Bridge From Forest": "open_forest != 'closed' or is_adult or 'Deku Tree Clear'", "KF Storms Grotto": "can_open_storm_grotto" } }, @@ -445,7 +445,7 @@ "scene": "Lost Woods", "hint": "LOST_WOODS", "exits": { - "Kokiri Forest": "True", + "Kokiri Forest": "True", # Walking through this entrance places Link past the Kokiri guard, so there are no restrictions for Closed Forest here "Hyrule Field": "True", "Lost Woods": "can_use(Longshot)" } diff --git a/data/presets_default.json b/data/presets_default.json index 7b39d019c..2ee6b3ad8 100644 --- a/data/presets_default.json +++ b/data/presets_default.json @@ -43,6 +43,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": false, "open_forest": "closed", + "require_gohma": true, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", @@ -206,6 +207,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": false, "open_forest": "closed_deku", + "require_gohma": false, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", @@ -381,6 +383,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": false, "open_forest": "closed_deku", + "require_gohma": false, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", @@ -558,6 +561,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": false, "open_forest": "open", + "require_gohma": false, "open_kakariko": "closed", "open_door_of_time": true, "zora_fountain": "closed", @@ -741,6 +745,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": true, "open_forest": "closed_deku", + "require_gohma": false, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", @@ -920,6 +925,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": true, "open_forest": "open", + "require_gohma": false, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", @@ -1092,7 +1098,8 @@ "silver_rupee_pouches": [], "shuffle_mapcompass": "remove", "enhance_map_compass": true, - "open_forest": "closed_deku", + "open_forest": "closed", + "require_gohma": false, "open_kakariko": "closed", "open_door_of_time": false, "zora_fountain": "closed", @@ -1445,6 +1452,7 @@ "shuffle_mapcompass": "dungeon", "enhance_map_compass": false, "open_forest": "open", + "require_gohma": false, "open_kakariko": "closed", "open_door_of_time": true, "zora_fountain": "closed", @@ -1608,6 +1616,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": false, "open_forest": "closed_deku", + "require_gohma": false, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", @@ -1788,6 +1797,7 @@ "shuffle_mapcompass": "startwith", "enhance_map_compass": false, "open_forest": "open", + "require_gohma": false, "open_kakariko": "open", "open_door_of_time": true, "zora_fountain": "closed", diff --git a/data/settings_mapping.json b/data/settings_mapping.json index 3b0f82e33..61a13d585 100644 --- a/data/settings_mapping.json +++ b/data/settings_mapping.json @@ -136,6 +136,7 @@ "row_span": [6,6,9], "settings": [ "open_forest", + "require_gohma", "open_kakariko", "open_door_of_time", "zora_fountain",