From dad1ee8336b927b6447bfed67509d29bdaee3a2b Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sun, 12 Feb 2023 09:44:52 +0100 Subject: [PATCH 01/23] Implement basic HMG logic * Ice Palace Lobby Clip * Kikiskip to Pod * Mire -> Hera -> Swamp - Mire small door to rupee room is removed - Hera and Swamp keys are placed without logic - Swamp locked by vanilla rules or having all mire smalls * Above as connectors in ER --- BaseClasses.py | 7 +- Doors.py | 6 +- EntranceShuffle.py | 2 +- Fill.py | 5 +- ItemList.py | 6 +- KeyDoorShuffle.py | 16 ++ Main.py | 14 +- Rules.py | 18 +- UnderworldGlitchRules.py | 173 ++++++++++++++++++ resources/app/cli/args.json | 1 + resources/app/cli/lang/en.json | 12 +- resources/app/gui/lang/en.json | 1 + resources/app/gui/randomize/item/widgets.json | 1 + source/overworld/EntranceShuffle2.py | 2 +- source/tools/MysteryUtils.py | 3 +- test/customizer/hmg/fireless_ice.yaml | 35 ++++ test/customizer/hmg/hammer_in_swamp.yaml | 42 +++++ test/customizer/hmg/mearl_in_pod.yaml | 34 ++++ test/customizer/hmg/mirrorless_swamp.yaml | 42 +++++ test/customizer/hmg/pod_as_connector.yaml | 44 +++++ test/customizer/hmg/swamp_as_connector.yaml | 55 ++++++ .../hmg/swamp_small_in_swamp_back.yaml | 44 +++++ 22 files changed, 540 insertions(+), 23 deletions(-) create mode 100644 UnderworldGlitchRules.py create mode 100644 test/customizer/hmg/fireless_ice.yaml create mode 100644 test/customizer/hmg/hammer_in_swamp.yaml create mode 100644 test/customizer/hmg/mearl_in_pod.yaml create mode 100644 test/customizer/hmg/mirrorless_swamp.yaml create mode 100644 test/customizer/hmg/pod_as_connector.yaml create mode 100644 test/customizer/hmg/swamp_as_connector.yaml create mode 100644 test/customizer/hmg/swamp_small_in_swamp_back.yaml diff --git a/BaseClasses.py b/BaseClasses.py index 242161e6..dc977cde 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -111,7 +111,7 @@ class World(object): set_player_attr('can_access_trock_front', None) set_player_attr('can_access_trock_big_chest', None) set_player_attr('can_access_trock_middle', None) - set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] + set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'hybridglitches', 'nologic'] or shuffle[player] in ['lean', 'swapped', 'crossed', 'insanity']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) @@ -1099,6 +1099,9 @@ class CollectionState(object): def can_lift_rocks(self, player): return self.has('Power Glove', player) or self.has('Titans Mitts', player) + + def can_bomb_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) and self.can_use_bombs(player) def has_bottle(self, player): return self.bottle_count(player) > 0 @@ -2973,7 +2976,7 @@ er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, 'swapped': 10} # byte 1: LLLW WSS? (logic, mode, sword) -logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} +logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5} world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} diff --git a/Doors.py b/Doors.py index ad83b67f..a3934e9c 100644 --- a/Doors.py +++ b/Doors.py @@ -938,8 +938,10 @@ def create_doors(world, player): create_door(player, 'Mire Dark Shooters Up Stairs', Sprl).dir(Up, 0x93, 0, LTH).ss(A, 0x32, 0xec), create_door(player, 'Mire Dark Shooters SW', Intr).dir(So, 0x93, Left, High).pos(0), create_door(player, 'Mire Block X NW', Intr).dir(No, 0x93, Left, High).pos(0), - create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).small_key().pos(1), - create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).small_key().pos(1), + create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).pos(1) if world.logic[player] == 'hybridglitches' else create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).small_key().pos(1), + create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).pos(1) if world.logic[player] == 'hybridglitches' else create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).small_key().pos(1), + # create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).small_key().pos(1), + # create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).small_key().pos(1), create_door(player, 'Mire Block X WS', Nrml).dir(We, 0x93, Bot, High).pos(2), create_door(player, 'Mire Tall Dark and Roomy ES', Nrml).dir(Ea, 0x92, Bot, High).pos(4), create_door(player, 'Mire Tall Dark and Roomy WN', Intr).dir(We, 0x92, Top, High).pos(0), diff --git a/EntranceShuffle.py b/EntranceShuffle.py index b95048c4..a1e15991 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1112,7 +1112,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): invalid_connections[entrance] = set() diff --git a/Fill.py b/Fill.py index d72c5b6c..49b9f113 100644 --- a/Fill.py +++ b/Fill.py @@ -175,6 +175,9 @@ def valid_key_placement(item, location, key_pool, collection_state, world): if dungeon: if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True + # Small key and big key in Swamp and Hera are placed without logic + if world.logic[item.player] == 'hybridglitches' and dungeon.name in ['Tower of Hera', 'Swamp Palace'] and dungeon.name in item.name: + return True key_logic = world.key_logic[item.player][dungeon.name] unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None @@ -398,7 +401,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in range(1, world.players + 1): if (not gftower_trash or not world.ganonstower_vanilla[player] - or world.logic[player] in ['owglitches', 'nologic']): + or world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']): continue gt_count, total_count = calc_trash_locations(world, player) scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7) diff --git a/ItemList.py b/ItemList.py index a0247619..5bfe04d5 100644 --- a/ItemList.py +++ b/ItemList.py @@ -881,7 +881,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt return random.choice([True, False]) if progressive == 'random' else progressive == 'on' # provide boots to boots glitch dependent modes - if logic in ['owglitches', 'nologic']: + if logic in ['owglitches', 'hybridglitches', 'nologic']: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.extend(['Rupees (20)']) @@ -1174,7 +1174,7 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer pool.extend(['Nothing'] * nothings) start_inventory = [x for x in world.precollected_items if x.player == player] - if world.logic[player] in ['owglitches', 'nologic'] and all(x.name !=' Pegasus Boots' for x in start_inventory): + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and all(x.name !=' Pegasus Boots' for x in start_inventory): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: pool.remove('Pegasus Boots') @@ -1316,7 +1316,7 @@ def make_customizer_pool(world, player): sphere_0 = world.customizer.get_start_inventory() no_start_inventory = not sphere_0 or not sphere_0[player] init_equip = [] if no_start_inventory else sphere_0[player] - if (world.logic[player] in ['owglitches', 'nologic'] + if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 9a0b6f89..5331ad4c 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -2106,6 +2106,22 @@ def validate_key_placement(key_layout, world, player): if i.player == player and i.name == smallkey_name: keys_outside += 1 + if world.logic[player] == 'hybridglitches': + # Swamp keylogic + if smallkey_name.endswith('(Swamp Palace)'): + swamp_entrance = world.get_location('Swamp Palace - Entrance', player) + # Swamp small not vanilla + if swamp_entrance.item is None or (swamp_entrance.item.name != smallkey_name or swamp_entrance.item.player != player): + mire_keylayout = world.key_layout[player]['Misery Mire'] + mire_smallkey_name = dungeon_keys[mire_keylayout.sector.name] + # Check if any mire keys are in swamp (excluding entrance), if none then add one to keys_outside + mire_keys_in_swamp = sum([1 if x.item.name == mire_smallkey_name else 0 for x in key_layout.item_locations if x.item is not None and x != swamp_entrance]) + if mire_keys_in_swamp == 0: + keys_outside +=1 + # Mire keylogic + if smallkey_name.endswith('(Tower of Hera)'): + # TODO: Make sure that mire medallion isn't in hera basement, or if it it, the small key is available downstairs + big_key_outside = True for code, counter in key_layout.key_counters.items(): if len(counter.child_doors) == 0: continue diff --git a/Main.py b/Main.py index ac68d48c..b273d922 100644 --- a/Main.py +++ b/Main.py @@ -27,6 +27,7 @@ from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dunge from Fill import dungeon_tracking from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, fill_specific_items +from UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config @@ -205,7 +206,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): create_regions(world, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(world, player) create_dungeon_regions(world, player) create_shops(world, player) @@ -216,6 +217,8 @@ def main(args, seed=None, fish=None): world.data_tables[player] = init_data_tables(world, player) place_bosses(world, player) randomize_enemies(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(world, player) adjust_locations(world, player) if world.customizer and world.customizer.get_start_inventory(): @@ -245,6 +248,8 @@ def main(args, seed=None, fish=None): link_entrances_new(world, player) else: link_entrances(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(world, player) logger.info(world.fish.translate("cli", "cli", "shuffling.prep")) for player in range(1, world.players + 1): @@ -497,8 +502,11 @@ def copy_world(world): create_shops(ret, player) create_rooms(ret, player) create_dungeons(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) + # there are region references here they must be migrated to preserve integrity # ret.exp_cache = world.exp_cache.copy() @@ -579,6 +587,8 @@ def copy_world(world): ret.sanc_portal = world.sanc_portal for player in range(1, world.players + 1): + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) set_rules(ret, player) return ret diff --git a/Rules.py b/Rules.py index f98523a8..6e1f40f8 100644 --- a/Rules.py +++ b/Rules.py @@ -8,6 +8,7 @@ from BaseClasses import PotFlags from Dungeons import dungeon_table from RoomData import DoorKind from OverworldGlitchRules import overworld_glitches_rules +from UnderworldGlitchRules import underworld_glitches_rules from source.logic.Rule import RuleFactory from source.dungeon.EnemyList import EnemySprite, Sprite @@ -48,7 +49,7 @@ def set_rules(world, player): logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') no_glitches_rules(world, player) fake_flipper_rules(world, player) - elif world.logic[player] == 'owglitches': + elif world.logic[player] in ['owglitches', 'hybridglitches']: logging.getLogger('').info('There is a chance OWG has bugged edge case rulesets, especially in inverted. Definitely file a report on GitHub if you see anything strange.') # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. @@ -73,7 +74,7 @@ def set_rules(world, player): if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) - if world.logic[player] == 'owglitches' and world.shuffle[player] != 'insanity': + if world.logic[player] in ['owglitches', 'hybridglitches'] and world.shuffle[player] != 'insanity': path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.world.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or') else: @@ -85,7 +86,11 @@ def set_rules(world, player): set_bunny_rules(world, player, world.mode[player] == 'inverted') - if world.mode[player] != 'inverted' and world.logic[player] == 'owglitches': + # These rules go here because the overwrite/add to some of the above rules + if world.logic[player] == 'hybridglitches': + underworld_glitches_rules(world, player) + + if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'hybridglitches']: add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or') @@ -1898,7 +1903,7 @@ def set_bunny_rules(world, player, inverted): def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type != RegionType.Dungeon \ and (location is None or location.name not in OverworldGlitchRules.get_superbunny_accessible_locations()) \ and not is_link(region): @@ -1928,7 +1933,7 @@ def set_bunny_rules(world, player, inverted): new_path = path + [entrance.access_rule] new_seen = seen.union({new_region}) if not is_link(new_region): - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): continue @@ -2154,6 +2159,9 @@ def add_key_logic_rules(world, player): add_rule(dep.entrance, eval_func(door_name, d_name, player)) for location in d_logic.bk_restricted: if not location.forced_item: + # Skip BK restricted locations in hybrid glitches. Bad, but necessary for now. + if world.logic[player] == 'hybridglitches' and d_name in ['Tower of Hera', 'Swamp Palace']: + continue forbid_item(location, d_logic.bk_name, player) for location in d_logic.sm_restricted: forbid_item(location, d_logic.small_key_name, player) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py new file mode 100644 index 00000000..ca99a5bb --- /dev/null +++ b/UnderworldGlitchRules.py @@ -0,0 +1,173 @@ +from BaseClasses import Entrance +import Rules +from OverworldGlitchRules import create_no_logic_connections + + +def get_kikiskip_spots(): + """ + Spectacle Rock Cave (Bottom) -> Palace of Darkness Exit, a.k.a. Kiki Skip + """ + yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal") + + +# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions +def get_kikiskip_connectors(world, player): + if world.fix_palaceofdarkness_exit[player] or world.fix_fake_world[player]: + yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", world.get_entrance("Palace of Darkness Exit", player).connected_region) + + +def get_mireheraswamp_connectors(world, player): + if world.fix_palaceofdarkness_exit[player] or world.fix_fake_world[player]: + yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Tower of Hera Exit", player).connected_region) + yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Swamp Palace Exit", player).connected_region) + + +def get_mireheraswamp_spots(): + """ + "Mire Torches Top -> Tower of Hera Exit, a.k.a. Mire to Hera Clip + "Mire Torches Top -> Swamp Palace Exit, a.k.a. Hera to Swamp Clip + """ + + yield ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal") + yield ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal") + + +def get_icepalace_spots(): + """ + "Ice Palace Exit -> Ice Palace Exit, a.k.a. Ice Palace Clip + """ + yield ("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop") + + +# Create connections between dungeons +def create_hybridmajor_connections(world, player): + create_no_logic_connections(player, world, get_kikiskip_spots()) + create_no_logic_connections(player, world, get_mireheraswamp_spots()) + create_no_logic_connections(player, world, get_icepalace_spots()) + + +# Turn dungeons into connectors 4 +def create_hybridmajor_connectors(world, player): + create_no_logic_connections(player, world, get_kikiskip_connectors(world, player)) + create_no_logic_connections(player, world, get_mireheraswamp_connectors(world, player)) + + +# For some entrances, we need to fake having pearl, because we're in fake DW/LW. +# This creates a copy of the input state that has Moon Pearl. +def fake_pearl_state(state, player): + if state.has("Moon Pearl", player): + return state + fake_state = state.copy() + fake_state.prog_items["Moon Pearl", player] += 1 + return fake_state + + +# Sets the rules on where we can actually go using this clip. +# Behavior differs based on what type of ER shuffle we're playing. +def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str): + fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] + fix_fake_worlds = world.fix_fake_world[player] + + dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0] + if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix + # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. + + # entrance doesn't exist until you fire rod it from the other side + if dungeon_entrance.name == "Skull Woods Final Section": + Rules.set_rule(clip, lambda state: False) + + elif dungeon_entrance.name == "Misery Mire": + if world.swords[player] == "swordless": + Rules.add_rule(clip, lambda state: state.has_misery_mire_medallion(player)) + else: + Rules.add_rule(clip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + + elif dungeon_entrance.name == "Agahnims Tower": + Rules.add_rule(clip, lambda state: state.has("Cape", player) or state.has_beam_sword(player) or state.has("Beat Agahnim 1", player)) + + # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + elif not fix_fake_worlds: # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix + # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. + Rules.add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) + # exiting restriction + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + + # Otherwise, the shuffle type is lean, crossed, or insanity; all of these do not need additional rules on where we can go, + # since the clip links directly to the exterior region. + + +def underworld_glitches_rules(world, player): + # Ice Palace Entrance Clip + Rules.add_rule(world.get_entrance("Ice Lobby SE", player), lambda state: state.can_bomb_clip(world.get_region("Ice Lobby", player), player), combine="or") + + # Kiki Skip + kks = world.get_entrance("Kiki Skip", player) + Rules.set_rule(kks, lambda state: state.can_bomb_clip(kks.parent_region, player)) + dungeon_reentry_rules(world, player, kks, "Palace of Darkness Portal", "Palace of Darkness Exit") + + # Mire -> Hera -> Swamp + def mire_clip(state): + return state.can_reach("Mire Torches Top", "Region", player) and state.can_bomb_clip(world.get_region("Mire Torches Top", player), player) and state.has_fire_source(player) + + def hera_clip(state): + return state.can_reach("Hera 4F", "Region", player) and state.can_bomb_clip(world.get_region("Hera 4F", player), player) + + Rules.add_rule(world.get_entrance("Hera Startile Corner NW", player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") + + # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots + # Flippers required for the doors going _into_ the dungeon + for door in [ + "Swamp Entrance Down Stairs", + "Swamp Pot Row WS", + "Swamp Trench 1 Key Ledge NW", + "Swamp Hub WN", + "Swamp Hub North Ledge N", + "Swamp Crystal Switch EN", + "Swamp Push Statue S", + "Swamp Waterway NW", + "Swamp T SW", + ]: + Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") + + # These doors let us go backwards so we don't require flippers + for door in [ "Swamp Trench 1 Approach ES", "Swamp Hammer Switch SW",]: + Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6), combine="or") + + Rules.add_rule(world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or") + + # Build the rule for SP moat. + # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. + # First we require a certain type of entrance shuffle, then build the rule from its pieces. + if not world.swamp_patch_required[player]: + if world.shuffle[player] in [ + "vanilla", + "dungeonssimple", + "dungeonsfull", + "dungeonscrossed", + ]: + rule_map = { + "Mire Portal": (lambda state: True), + "Hera Portal": (lambda state: state.can_reach("Hera Startile Corner NW", "Entrance", player)), + } + inverted = world.mode[player] == "inverted" + + def hera_rule(state): + return (state.has("Moon Pearl", player) or not inverted) and rule_map.get(world.get_entrance("Tower of Hera", player).connected_region.name, lambda state: False)(state) + + def gt_rule(state): + return (state.has("Moon Pearl", player) or inverted) and rule_map.get( + world.get_entrance(("Ganons Tower" if not inverted else "Inverted Ganons Tower"), player).connected_region.name, lambda state: False)(state) + + def mirrorless_moat_rule(state): + return state.can_reach("Old Man S&Q", "Entrance", player) and mire_clip(state) and (hera_rule(state) or gt_rule(state)) + + Rules.add_rule(world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or") + + # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys + mire_to_hera = world.get_entrance("Mire to Hera Clip", player) + mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) + Rules.set_rule(mire_to_hera, mire_clip) + Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) + dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") + dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 1d7dbcb2..6aedcad8 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -23,6 +23,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 024f16e0..3cd6aa19 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -63,11 +63,13 @@ "bps": [ "Output BPS patches instead of ROMs"], "logic": [ "Select Enforcement of Item Requirements. (default: %(default)s)", - "No Glitches: No Glitch knowledge required.", - "Minor Glitches: May require Fake Flippers, Bunny Revival", - " and Dark Room Navigation.", - "No Logic: Distribute items without regard for", - " item requirements." + "No Glitches: No Glitch knowledge required.", + "Minor Glitches: May require Fake Flippers, Bunny Revival", + " and Dark Room Navigation.", + "Overworld Glitches: May require overworld clips and teleports.", + "Hybrid Major Glitches: May require underworld clips.", + "No Logic: Distribute items without regard for", + " item requirements." ], "mode": [ "Select game mode. (default: %(default)s)", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index bd8e755b..c14dee25 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -237,6 +237,7 @@ "randomizer.item.logiclevel.noglitches": "No Glitches", "randomizer.item.logiclevel.minorglitches": "Minor Glitches", "randomizer.item.logiclevel.owglitches": "Overworld Glitches", + "randomizer.item.logiclevel.hybridglitches": "Hybrid Major Glitches", "randomizer.item.logiclevel.nologic": "No Logic", "randomizer.item.goal": "Goal", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 6e7ee6b8..0a895d3c 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -20,6 +20,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index e6fe631d..9847e472 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -957,7 +957,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if avail.world.logic[avail.player] in ['owglitches', 'nologic']: + if avail.world.logic[avail.player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(avail.inverted): invalid_connections[entrance] = set() diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index eb6f0fee..ec3518fb 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -80,7 +80,8 @@ def roll_settings(weights): ret.algorithm = get_choice('algorithm') - glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches', + glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'hybridglitches': 'hybridglitches', + 'hmg': 'hybridglitches', 'owglitches': 'owglitches', 'owg': 'owglitches', 'minorglitches': 'minorglitches'} glitches_required = get_choice('glitches_required') if glitches_required is not None: diff --git a/test/customizer/hmg/fireless_ice.yaml b/test/customizer/hmg/fireless_ice.yaml new file mode 100644 index 00000000..86affd94 --- /dev/null +++ b/test/customizer/hmg/fireless_ice.yaml @@ -0,0 +1,35 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Ice Palace - Compass Chest: Fire Rod + Ice Palace - Freezor Chest: Bombos diff --git a/test/customizer/hmg/hammer_in_swamp.yaml b/test/customizer/hmg/hammer_in_swamp.yaml new file mode 100644 index 00000000..f2e3aae7 --- /dev/null +++ b/test/customizer/hmg/hammer_in_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Hammer diff --git a/test/customizer/hmg/mearl_in_pod.yaml b/test/customizer/hmg/mearl_in_pod.yaml new file mode 100644 index 00000000..ec2d5b9a --- /dev/null +++ b/test/customizer/hmg/mearl_in_pod.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Palace of Darkness - Shooter Room: Moon Pearl diff --git a/test/customizer/hmg/mirrorless_swamp.yaml b/test/customizer/hmg/mirrorless_swamp.yaml new file mode 100644 index 00000000..c8a109c3 --- /dev/null +++ b/test/customizer/hmg/mirrorless_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Magic Mirror diff --git a/test/customizer/hmg/pod_as_connector.yaml b/test/customizer/hmg/pod_as_connector.yaml new file mode 100644 index 00000000..48f72d6e --- /dev/null +++ b/test/customizer/hmg/pod_as_connector.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Palace of Darkness Exit + Lake Hylia Fortune Teller: Spectacle Rock Cave Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl diff --git a/test/customizer/hmg/swamp_as_connector.yaml b/test/customizer/hmg/swamp_as_connector.yaml new file mode 100644 index 00000000..ae343937 --- /dev/null +++ b/test/customizer/hmg/swamp_as_connector.yaml @@ -0,0 +1,55 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Swamp Palace Exit + Lake Hylia Fortune Teller: Misery Mire Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl + diff --git a/test/customizer/hmg/swamp_small_in_swamp_back.yaml b/test/customizer/hmg/swamp_small_in_swamp_back.yaml new file mode 100644 index 00000000..b57cc9b6 --- /dev/null +++ b/test/customizer/hmg/swamp_small_in_swamp_back.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +# placements: +# 1: +# Swamp Palace - Entrance: Boss Heart Container From 0ff25d7fd9ce2fd258f601f78f6b573a3d927490 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 13 Feb 2023 20:25:02 +0100 Subject: [PATCH 02/23] Give access to correct IP door for lobby clip --- UnderworldGlitchRules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index ca99a5bb..e94df74b 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -99,7 +99,7 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du def underworld_glitches_rules(world, player): # Ice Palace Entrance Clip - Rules.add_rule(world.get_entrance("Ice Lobby SE", player), lambda state: state.can_bomb_clip(world.get_region("Ice Lobby", player), player), combine="or") + Rules.add_rule(world.get_entrance("Ice Bomb Drop SE", player), lambda state: state.can_bomb_clip(world.get_region("Ice Lobby", player), player), combine="or") # Kiki Skip kks = world.get_entrance("Kiki Skip", player) From 95703fe2b5e6277ecb38221d1f03701fec654a91 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 13 Feb 2023 20:25:59 +0100 Subject: [PATCH 03/23] Fix flippers logic preventing locks on drain --- UnderworldGlitchRules.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index e94df74b..af51de57 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -116,8 +116,10 @@ def underworld_glitches_rules(world, player): Rules.add_rule(world.get_entrance("Hera Startile Corner NW", player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots - # Flippers required for the doors going _into_ the dungeon + # Flippers required for all of these doors to prevent locks when flooding for door in [ + "Swamp Trench 1 Approach ES", + "Swamp Hammer Switch SW", "Swamp Entrance Down Stairs", "Swamp Pot Row WS", "Swamp Trench 1 Key Ledge NW", @@ -130,10 +132,6 @@ def underworld_glitches_rules(world, player): ]: Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") - # These doors let us go backwards so we don't require flippers - for door in [ "Swamp Trench 1 Approach ES", "Swamp Hammer Switch SW",]: - Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6), combine="or") - Rules.add_rule(world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or") # Build the rule for SP moat. From 9352067d696fba251eb63e1cec7147997636cfdc Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 14 Feb 2023 11:35:00 +0100 Subject: [PATCH 04/23] Fix swamp smalls in pottery to get out of swamp --- ItemList.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ItemList.py b/ItemList.py index 5bfe04d5..39584262 100644 --- a/ItemList.py +++ b/ItemList.py @@ -287,6 +287,11 @@ def generate_itempool(world, player): for _ in range(0, amt): pool.append('Rupees (20)') + if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: + # In HMG force swamp smalls in pots to allow getting out of swamp palace + placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' + placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)' + start_inventory = list(world.precollected_items) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) From 2b826077fc93a53e6efa270b9f305353e12b9440 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 14 Feb 2023 12:14:28 +0100 Subject: [PATCH 05/23] Undo test doors check --- Doors.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Doors.py b/Doors.py index a3934e9c..ad83b67f 100644 --- a/Doors.py +++ b/Doors.py @@ -938,10 +938,8 @@ def create_doors(world, player): create_door(player, 'Mire Dark Shooters Up Stairs', Sprl).dir(Up, 0x93, 0, LTH).ss(A, 0x32, 0xec), create_door(player, 'Mire Dark Shooters SW', Intr).dir(So, 0x93, Left, High).pos(0), create_door(player, 'Mire Block X NW', Intr).dir(No, 0x93, Left, High).pos(0), - create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).pos(1) if world.logic[player] == 'hybridglitches' else create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).small_key().pos(1), - create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).pos(1) if world.logic[player] == 'hybridglitches' else create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).small_key().pos(1), - # create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).small_key().pos(1), - # create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).small_key().pos(1), + create_door(player, 'Mire Dark Shooters SE', Intr).dir(So, 0x93, Right, High).small_key().pos(1), + create_door(player, 'Mire Key Rupees NE', Intr).dir(No, 0x93, Right, High).small_key().pos(1), create_door(player, 'Mire Block X WS', Nrml).dir(We, 0x93, Bot, High).pos(2), create_door(player, 'Mire Tall Dark and Roomy ES', Nrml).dir(Ea, 0x92, Bot, High).pos(4), create_door(player, 'Mire Tall Dark and Roomy WN', Intr).dir(We, 0x92, Top, High).pos(0), From 228f18fed4ea2684e48ee25eaa7771933c7f527c Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Fri, 17 Feb 2023 20:29:31 +0100 Subject: [PATCH 06/23] Logic updates - Add Thieves -> Desert clip (+ as connector) - Spec rock bomb clip (+ as connector) - Paradox teleport for chests in crystal area and from front - Require bombs or Somaria to get out of IP clip - Add dash clip checks --- BaseClasses.py | 3 ++ UnderworldGlitchRules.py | 98 ++++++++++++++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index dc977cde..cff79a25 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1102,6 +1102,9 @@ class CollectionState(object): def can_bomb_clip(self, region, player: int) -> bool: return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) and self.can_use_bombs(player) + + def can_dash_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) def has_bottle(self, player): return self.bottle_count(player) > 0 diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index af51de57..a7988d52 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -10,18 +10,6 @@ def get_kikiskip_spots(): yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal") -# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions -def get_kikiskip_connectors(world, player): - if world.fix_palaceofdarkness_exit[player] or world.fix_fake_world[player]: - yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", world.get_entrance("Palace of Darkness Exit", player).connected_region) - - -def get_mireheraswamp_connectors(world, player): - if world.fix_palaceofdarkness_exit[player] or world.fix_fake_world[player]: - yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Tower of Hera Exit", player).connected_region) - yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Swamp Palace Exit", player).connected_region) - - def get_mireheraswamp_spots(): """ "Mire Torches Top -> Tower of Hera Exit, a.k.a. Mire to Hera Clip @@ -39,17 +27,67 @@ def get_icepalace_spots(): yield ("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop") -# Create connections between dungeons +def get_thievesdesert_spots(): + """ + "Thieves' Town -> Desert Palace , a.k.a. Thieves to Desert Clip + Accessing any of the exits will be in logic because of the ability to dungeon bunny revive + """ + yield ("Thieves to Desert Clip", "Thieves Attic", "Desert West Portal") + yield ("Thieves to Desert Clip", "Thieves Attic", "Desert South Portal") + yield ("Thieves to Desert Clip", "Thieves Attic", "Desert East Portal") + + +def get_specrock_spots(): + """ + "Spectacle Rock Cave (Peak) -> Spectacle Rock Cave (Top), a.k.a. Spectacle Rock Cave Clip + """ + yield ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)") + + +def get_paradox_spots(): + """ + "Paradox Cave Front -> Paradox Cave Chest Area, a.k.a. Paradox Cave Teleport (dash citrus, 1f right, teleport up) + """ + yield ("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area") + + +# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions +def get_kikiskip_connectors(world, player): + yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", world.get_entrance("Palace of Darkness Exit", player).connected_region) + + +def get_mireheraswamp_connectors(world, player): + yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Tower of Hera Exit", player).connected_region) + yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Swamp Palace Exit", player).connected_region) + + +def get_thievesdesert_connectors(world, player): + yield ("Thieves to Desert Clip", "Thieves Attic", world.get_entrance("Desert Palace Exit (West)", player).connected_region) + yield ("Thieves to Desert Clip", "Thieves Attic", world.get_entrance("Desert Palace Exit (South)", player).connected_region) + yield ("Thieves to Desert Clip", "Thieves Attic", world.get_entrance("Desert Palace Exit (East)", player).connected_region) + +def get_specrock_connectors(world, player): + yield ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", world.get_entrance("Spectacle Rock Cave Exit (Top)", player).connected_region) + yield ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", world.get_entrance("Spectacle Rock Cave Exit", player).connected_region) + + + +# Create connections between dungeons/locations def create_hybridmajor_connections(world, player): create_no_logic_connections(player, world, get_kikiskip_spots()) create_no_logic_connections(player, world, get_mireheraswamp_spots()) create_no_logic_connections(player, world, get_icepalace_spots()) + create_no_logic_connections(player, world, get_thievesdesert_spots()) + create_no_logic_connections(player, world, get_specrock_spots()) + create_no_logic_connections(player, world, get_paradox_spots()) -# Turn dungeons into connectors 4 +# Turn dungeons into connectors def create_hybridmajor_connectors(world, player): create_no_logic_connections(player, world, get_kikiskip_connectors(world, player)) create_no_logic_connections(player, world, get_mireheraswamp_connectors(world, player)) + create_no_logic_connections(player, world, get_thievesdesert_connectors(world, player)) + create_no_logic_connections(player, world, get_specrock_connectors(world, player)) # For some entrances, we need to fake having pearl, because we're in fake DW/LW. @@ -93,13 +131,16 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du # exiting restriction Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) - # Otherwise, the shuffle type is lean, crossed, or insanity; all of these do not need additional rules on where we can go, + # Otherwise, the shuffle type is lean, lite, crossed, or insanity; all of these do not need additional rules on where we can go, # since the clip links directly to the exterior region. def underworld_glitches_rules(world, player): - # Ice Palace Entrance Clip - Rules.add_rule(world.get_entrance("Ice Bomb Drop SE", player), lambda state: state.can_bomb_clip(world.get_region("Ice Lobby", player), player), combine="or") + # Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room + Rules.add_rule(world.get_entrance("Ice Bomb Drop SE", player), + lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) and + (state.can_use_bombs(player) or state.has('Cane of Somaria', player)), + combine="or") # Kiki Skip kks = world.get_entrance("Kiki Skip", player) @@ -108,13 +149,15 @@ def underworld_glitches_rules(world, player): # Mire -> Hera -> Swamp def mire_clip(state): - return state.can_reach("Mire Torches Top", "Region", player) and state.can_bomb_clip(world.get_region("Mire Torches Top", player), player) and state.has_fire_source(player) + return state.can_reach("Mire Torches Top", "Region", player) and state.can_dash_clip(world.get_region("Mire Torches Top", player), player) def hera_clip(state): - return state.can_reach("Hera 4F", "Region", player) and state.can_bomb_clip(world.get_region("Hera 4F", player), player) + return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip(world.get_region("Hera 4F", player), player) Rules.add_rule(world.get_entrance("Hera Startile Corner NW", player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") + Rules.add_rule(world.get_entrance("Thieves to Desert Clip", player), lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player), combine="or") + # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots # Flippers required for all of these doors to prevent locks when flooding for door in [ @@ -145,7 +188,7 @@ def underworld_glitches_rules(world, player): "dungeonscrossed", ]: rule_map = { - "Mire Portal": (lambda state: True), + "Mire Portal": (lambda state: state.can_reach("Mire Torches Top", "Entrance", player)), "Hera Portal": (lambda state: state.can_reach("Hera Startile Corner NW", "Entrance", player)), } inverted = world.mode[player] == "inverted" @@ -167,5 +210,20 @@ def underworld_glitches_rules(world, player): mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) Rules.set_rule(mire_to_hera, mire_clip) Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) + dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") + dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert West Portal", "Swamp Palace Exit") + dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert South Portal", "Swamp Palace Exit") + dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert East Portal", "Swamp Palace Exit") + + # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up + paradox_left_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Middle'] + for location in paradox_left_chests: + Rules.add_rule(world.get_location(location, player), lambda state: state.can_dash_clip(world.get_location(location, player)), 'or') + + # Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch + paradox_right_chests = ['Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right'] + for location in paradox_right_chests: + Rules.add_rule(world.get_location(location, player), lambda state: (state.can_dash_clip(world.get_location(location, player)) and state.can_hit_crystal(player)), 'or') + From 4de3544e0d2c528e92ac3ed44e96476ccd717722 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Thu, 2 Mar 2023 11:10:11 +0100 Subject: [PATCH 07/23] Syntax fixes --- UnderworldGlitchRules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index a7988d52..13acc599 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -220,10 +220,10 @@ def underworld_glitches_rules(world, player): # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up paradox_left_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Middle'] for location in paradox_left_chests: - Rules.add_rule(world.get_location(location, player), lambda state: state.can_dash_clip(world.get_location(location, player)), 'or') + Rules.add_rule(world.get_location(location, player), lambda state: state.can_dash_clip(world.get_location(location, player).parent_region, player), 'or') # Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch paradox_right_chests = ['Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right'] for location in paradox_right_chests: - Rules.add_rule(world.get_location(location, player), lambda state: (state.can_dash_clip(world.get_location(location, player)) and state.can_hit_crystal(player)), 'or') + Rules.add_rule(world.get_location(location, player), lambda state: (state.can_dash_clip(world.get_location(location, player).parent_region, player) and state.can_hit_crystal(player)), 'or') From 0f28cd97d8bc2482584c726584a25521ecefacef Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sat, 1 Apr 2023 22:11:47 +0200 Subject: [PATCH 08/23] Logic fixes --- UnderworldGlitchRules.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 13acc599..4b8e248f 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -153,11 +153,18 @@ def underworld_glitches_rules(world, player): def hera_clip(state): return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip(world.get_region("Hera 4F", player), player) + Rules.add_rule(world.get_entrance("Hera Startile Corner NW", player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") - Rules.add_rule(world.get_entrance("Thieves to Desert Clip", player), lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player), combine="or") - + mire_to_hera = world.get_entrance("Mire to Hera Clip", player) + mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) + Rules.set_rule(mire_to_hera, mire_clip) + Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) + + # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys + dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") + dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots # Flippers required for all of these doors to prevent locks when flooding for door in [ @@ -173,7 +180,8 @@ def underworld_glitches_rules(world, player): "Swamp Waterway NW", "Swamp T SW", ]: - Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") + # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") + Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") Rules.add_rule(world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or") @@ -205,17 +213,11 @@ def underworld_glitches_rules(world, player): Rules.add_rule(world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or") - # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys - mire_to_hera = world.get_entrance("Mire to Hera Clip", player) - mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) - Rules.set_rule(mire_to_hera, mire_clip) - Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) - - dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") - dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") - dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert West Portal", "Swamp Palace Exit") - dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert South Portal", "Swamp Palace Exit") - dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert East Portal", "Swamp Palace Exit") + # Thieves -> Hera + Rules.add_rule(world.get_entrance("Thieves to Desert Clip", player), lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player)) + dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert West Portal", "Desert Palace Exit (West)") + dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert South Portal", "Desert Palace Exit (South)") + dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert East Portal", "Desert Palace Exit (East)") # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up paradox_left_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Middle'] From 98702176980261798d3a816f25675dd2c9aaadf5 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sun, 2 Apr 2023 09:48:38 +0200 Subject: [PATCH 09/23] Undo test --- UnderworldGlitchRules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 4b8e248f..98eb6626 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -180,8 +180,8 @@ def underworld_glitches_rules(world, player): "Swamp Waterway NW", "Swamp T SW", ]: - # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") - Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") + Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") + # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") Rules.add_rule(world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or") From e1f2369f13f444aa443e99671f0be5f52f7d5a6c Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 4 Dec 2023 19:43:13 +0100 Subject: [PATCH 10/23] Refactor OWG rules to use lists rather than generators --- OverworldGlitchRules.py | 467 +++++++++++++++++++++------------------- Rules.py | 16 +- 2 files changed, 259 insertions(+), 224 deletions(-) diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 890e8420..ba44ba25 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -4,261 +4,296 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. from BaseClasses import Entrance +# Cave regions that superbunny can get through - but only with a sword. +sword_required_superbunny_mirror_regions = ["Spiral Cave (Top)"] -def get_sword_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with a sword. - """ - yield 'Spiral Cave (Top)' +# Cave regions that superbunny can get through - but only with boots. +boots_required_superbunny_mirror_regions = ["Two Brothers House"] -def get_boots_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with boots. - """ - yield 'Two Brothers House' +# Cave locations that superbunny can access - but only with boots. +boots_required_superbunny_mirror_locations = [ + "Sahasrahla's Hut - Left", + "Sahasrahla's Hut - Middle", + "Sahasrahla's Hut - Right", +] -def get_boots_required_superbunny_mirror_locations(): - """ - Cave locations that superbunny can access - but only with boots. - """ - yield 'Sahasrahla\'s Hut - Left' - yield 'Sahasrahla\'s Hut - Middle' - yield 'Sahasrahla\'s Hut - Right' +# Entrances that can't be superbunny-mirrored into. +invalid_mirror_bunny_entrances = [ + "Skull Woods Final Section", + "Hype Cave", + "Bonk Fairy (Dark)", + "Thieves Town", + "Hammer Peg Cave", + "Brewery", + "Hookshot Cave", + "Dark Lake Hylia Ledge Fairy", + "Dark Lake Hylia Ledge Spike Cave", + "Palace of Darkness", + "Misery Mire", + "Turtle Rock", + "Bonk Rock Cave", + "Bonk Fairy (Light)", + "50 Rupee Cave", + "20 Rupee Cave", + "Checkerboard Cave", + "Light Hype Fairy", + "Waterfall of Wishing", + "Light World Bomb Hut", + "Mini Moldorm Cave", + "Ice Rod Cave", + "Sanctuary Grave", + "Kings Grave", + "Sanctuary Grave", + "Hyrule Castle Secret Entrance Drop", + "Skull Woods Second Section Hole", + "Skull Woods First Section Hole (North)", +] + +# Interior locations that can be accessed with superbunny state. +superbunny_accessible_locations = [ + "Waterfall of Wishing - Left", + "Waterfall of Wishing - Right", + "King's Tomb", + "Floodgate", + "Floodgate Chest", + "Cave 45", + "Bonk Rock Cave", + "Brewery", + "C-Shaped House", + "Chest Game", + "Mire Shed - Left", + "Mire Shed - Right", + "Secret Passage", + "Ice Rod Cave", + "Pyramid Fairy - Left", + "Pyramid Fairy - Right", + "Superbunny Cave - Top", + "Superbunny Cave - Bottom", + "Blind's Hideout - Left", + "Blind's Hideout - Right", + "Blind's Hideout - Far Left", + "Blind's Hideout - Far Right", + "Kakariko Well - Left", + "Kakariko Well - Middle", + "Kakariko Well - Right", + "Kakariko Well - Bottom", + "Kakariko Tavern", + "Library", + "Spiral Cave", +] + boots_required_superbunny_mirror_locations -def get_invalid_mirror_bunny_entrances(): - """ - Entrances that can't be superbunny-mirrored into. - """ - yield 'Skull Woods Final Section' - yield 'Hype Cave' - yield 'Bonk Fairy (Dark)' - yield 'Thieves Town' - yield 'Hammer Peg Cave' - yield 'Brewery' - yield 'Hookshot Cave' - yield 'Dark Lake Hylia Ledge Fairy' - yield 'Dark Lake Hylia Ledge Spike Cave' - yield 'Palace of Darkness' - yield 'Misery Mire' - yield 'Turtle Rock' - yield 'Bonk Rock Cave' - yield 'Bonk Fairy (Light)' - yield '50 Rupee Cave' - yield '20 Rupee Cave' - yield 'Checkerboard Cave' - yield 'Light Hype Fairy' - yield 'Waterfall of Wishing' - yield 'Light World Bomb Hut' - yield 'Mini Moldorm Cave' - yield 'Ice Rod Cave' - yield 'Sanctuary Grave' - yield 'Kings Grave' - yield 'Sanctuary Grave' - yield 'Hyrule Castle Secret Entrance Drop' - yield 'Skull Woods Second Section Hole' - yield 'Skull Woods First Section Hole (North)' +# Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit. +# The following are still be mandatory exits: +# Open: +# Turtle Rock Isolated Ledge Entrance +# Skull Woods Second Section Door (West) (or Skull Woods Final Section) +# Inverted: +# Two Brothers House (West) +# Desert Palace Entrance (East) -def get_superbunny_accessible_locations(): - """ - Interior locations that can be accessed with superbunny state. - """ +non_mandatory_exits = [ + "Bumper Cave (Top)", + "Death Mountain Return Cave (West)", + "Hookshot Cave Back Entrance", +] - yield 'Waterfall of Wishing - Left' - yield 'Waterfall of Wishing - Right' - yield 'King\'s Tomb' - yield 'Floodgate' - yield 'Floodgate Chest' - yield 'Cave 45' - yield 'Bonk Rock Cave' - yield 'Brewery' - yield 'C-Shaped House' - yield 'Chest Game' - yield 'Mire Shed - Left' - yield 'Mire Shed - Right' - yield 'Secret Passage' - yield 'Ice Rod Cave' - yield 'Pyramid Fairy - Left' - yield 'Pyramid Fairy - Right' - yield 'Superbunny Cave - Top' - yield 'Superbunny Cave - Bottom' - yield 'Blind\'s Hideout - Left' - yield 'Blind\'s Hideout - Right' - yield 'Blind\'s Hideout - Far Left' - yield 'Blind\'s Hideout - Far Right' - yield 'Kakariko Well - Left' - yield 'Kakariko Well - Middle' - yield 'Kakariko Well - Right' - yield 'Kakariko Well - Bottom' - yield 'Kakariko Tavern' - yield 'Library' - yield 'Spiral Cave' - for location in get_boots_required_superbunny_mirror_locations(): - yield location +inverted_non_mandatory_exits = [ + "Desert Palace Entrance (North)", + "Desert Palace Entrance (West)", + "Agahnims Tower", + "Hyrule Castle Entrance (West)", + "Hyrule Castle Entrance (East)", +] + non_mandatory_exits + +open_non_mandatory_exits_ = [ + "Dark Death Mountain Ledge (West)", + "Dark Death Mountain Ledge (East)", + "Mimic Cave", + "Desert Palace Entrance (East)", +] + non_mandatory_exits -def get_non_mandatory_exits(inverted): - """ - Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit. - The following are still be mandatory exits: +# Special Light World region exits that require boots clips. - Open: - Turtle Rock Isolated Ledge Entrance - Skull Woods Second Section Door (West) (or Skull Woods Final Section) +inverted_boots_clip_exits_lw = [ + ("Light World DMA Clip Spot", "Light World", "West Death Mountain (Bottom)"), + ("Hera Ascent", "West Death Mountain (Bottom)", "West Death Mountain (Top)"), + ("Death Mountain Return Ledge Clip Spot", "Light World", "Death Mountain Return Ledge"), + ("Death Mountain Entrance Clip Spot", "Light World", "Death Mountain Entrance"), + ("Death Mountain Glitched Bridge", "West Death Mountain (Bottom)", "East Death Mountain (Top)"), + ("Zora Descent Clip Spot", "East Death Mountain (Top)", "Zoras Domain"), + ("Desert Northern Cliffs", "Light World", "Desert Northern Cliffs"), + ("Desert Ledge Dropdown", "Desert Northern Cliffs", "Desert Ledge"), + ("Desert Palace Entrance Dropdown", "Desert Northern Cliffs", "Desert Palace Entrance (North) Spot"), + ("Lake Hylia Island Clip Spot", "Light World", "Lake Hylia Island"), + ("Death Mountain Descent", "West Death Mountain (Bottom)", "Light World"), + ("Kings Grave Clip Spot", "West Death Mountain (Bottom)", "Kings Grave Area"), + ("Maze Race Clip Spot", "Light World", "Maze Race Ledge"), +] - Inverted: - Two Brothers House (West) - Desert Palace Entrance (East) - """ +open_boots_clip_exits_lw = [ + ("Graveyard Ledge Clip Spot", "West Death Mountain (Bottom)", "Graveyard Ledge"), + ("Desert Ledge (Northeast) Dropdown", "Desert Northern Cliffs", "Desert Checkerboard Ledge"), + ("Spectacle Rock Clip Spot", "West Death Mountain (Top)", "Spectacle Rock"), + ("Bombos Tablet Clip Spot", "Light World", "Bombos Tablet Ledge"), + ("Floating Island Clip Spot", "East Death Mountain (Top)", "Death Mountain Floating Island"), + ("Cave 45 Clip Spot", "Light World", "Cave 45 Ledge"), +] + inverted_boots_clip_exits_lw - yield 'Bumper Cave (Top)' - yield 'Death Mountain Return Cave (West)' - yield 'Hookshot Cave Back Entrance' +# Special Dark World region exits that require boots clips. +boots_clip_exits_dw = [ + ("Dark World DMA Clip Spot", "West Dark World", "West Dark Death Mountain (Bottom)"), + ("Bumper Cave Ledge Clip Spot", "West Dark World", "Bumper Cave Ledge"), + ("Bumper Cave Entrance Clip Spot", "West Dark World", "Bumper Cave Entrance"), + ("Catfish Descent", "Dark Death Mountain (Top)", "Catfish Area"), + ("Hammer Pegs River Clip Spot", "East Dark World", "Hammer Peg Area"), + ("Dark Lake Hylia Ledge Clip Spot", "East Dark World", "Southeast Dark World"), + ("Dark Desert Cliffs Clip Spot", "South Dark World", "Dark Desert"), + ("DW Floating Island Clip Spot", "East Dark Death Mountain (Bottom)", "Dark Death Mountain Floating Island"), +] - if inverted: - yield 'Desert Palace Entrance (North)' - yield 'Desert Palace Entrance (West)' - yield 'Agahnims Tower' - yield 'Hyrule Castle Entrance (West)' - yield 'Hyrule Castle Entrance (East)' - else: - yield 'Dark Death Mountain Ledge (West)' - yield 'Dark Death Mountain Ledge (East)' - yield 'Mimic Cave' - yield 'Desert Palace Entrance (East)' +open_boots_clip_exits_dw = [ + ("Dark Death Mountain Descent", "West Dark Death Mountain (Bottom)", "West Dark World"), + ("Ganons Tower Ascent", "West Dark Death Mountain (Bottom)", "Dark Death Mountain (Top)"), + ("Dark Death Mountain Glitched Bridge", "West Dark Death Mountain (Bottom)", "Dark Death Mountain (Top)"), + ("Turtle Rock (Top) Clip Spot", "Dark Death Mountain (Top)", "Turtle Rock (Top)"), +] + boots_clip_exits_dw + +inverted_boots_clip_exits_dw = [ + ("Dark Desert Teleporter Clip Spot", "Dark Desert", "Dark Desert Ledge") +] + boots_clip_exits_dw -def get_boots_clip_exits_lw(inverted = False): - """ - Special Light World region exits that require boots clips. - """ - - yield ('Bat Cave River Clip Spot', 'Light World', 'Bat Cave Ledge') - yield ('Light World DMA Clip Spot', 'Light World', 'West Death Mountain (Bottom)') - yield ('Hera Ascent', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)') - yield ('Death Mountain Return Ledge Clip Spot', 'Light World', 'Death Mountain Return Ledge') - yield ('Death Mountain Entrance Clip Spot', 'Light World', 'Death Mountain Entrance') - yield ('Death Mountain Glitched Bridge', 'West Death Mountain (Bottom)', 'East Death Mountain (Top)') - yield ('Zora Descent Clip Spot', 'East Death Mountain (Top)', 'Zoras Domain') - yield ('Desert Northern Cliffs', 'Light World', 'Desert Northern Cliffs') - yield ('Desert Ledge Dropdown', 'Desert Northern Cliffs', 'Desert Ledge') - yield ('Desert Palace Entrance Dropdown', 'Desert Northern Cliffs', 'Desert Palace Entrance (North) Spot') - yield ('Lake Hylia Island Clip Spot', 'Light World', 'Lake Hylia Island') - yield ('Death Mountain Descent', 'West Death Mountain (Bottom)', 'Light World') - yield ('Kings Grave Clip Spot', 'West Death Mountain (Bottom)', 'Kings Grave Area') - - if not inverted: - yield ('Graveyard Ledge Clip Spot', 'West Death Mountain (Bottom)', 'Graveyard Ledge') - yield ('Desert Ledge (Northeast) Dropdown', 'Desert Northern Cliffs', 'Desert Checkerboard Ledge') - yield ('Spectacle Rock Clip Spot', 'West Death Mountain (Top)', 'Spectacle Rock') - yield ('Bombos Tablet Clip Spot', 'Light World', 'Bombos Tablet Ledge') - yield ('Floating Island Clip Spot', 'East Death Mountain (Top)', 'Death Mountain Floating Island') - yield ('Cave 45 Clip Spot', 'Light World', 'Cave 45 Ledge') +# Dark World drop-down ledges that require glitched speed. +glitched_speed_drops_dw = [ + ("Dark Death Mountain Ledge Clip Spot", "Dark Death Mountain (Top)", "Dark Death Mountain Ledge") +] -def get_boots_clip_exits_dw(inverted): - """ - Special Dark World region exits that require boots clips. - """ +# Out of bounds transitions using the mirror +mirror_clip_spots_dw = [ + ("Dark Death Mountain Bunny Descent Mirror Spot", "West Dark Death Mountain (Bottom)", "West Dark World"), + ( + "Dark Death Mountain Bunny Mirror To East Jump", + "West Dark Death Mountain (Bottom)", + "East Dark Death Mountain (Bottom)", + ), + ("Desert East Mirror Clip", "Dark Desert", "Desert Palace Mouth"), +] - yield ('Dark World DMA Clip Spot', 'West Dark World', 'West Dark Death Mountain (Bottom)') - yield ('Bumper Cave Ledge Clip Spot', 'West Dark World', 'Bumper Cave Ledge') - yield ('Bumper Cave Entrance Clip Spot', 'West Dark World', 'Bumper Cave Entrance') - yield ('Catfish Descent', 'Dark Death Mountain (Top)', 'Catfish Area') - yield ('Hammer Pegs River Clip Spot', 'East Dark World', 'Hammer Peg Area') - yield ('Dark Lake Hylia Ledge Clip Spot', 'East Dark World', 'Southeast Dark World') - yield ('Dark Desert Cliffs Clip Spot', 'South Dark World', 'Dark Desert') - yield ('DW Floating Island Clip Spot', 'East Dark Death Mountain (Bottom)', 'Dark Death Mountain Floating Island') +# Mirror shenanigans placing a mirror portal with a broken camera +mirror_offset_spots_dw = [("Dark Death Mountain Offset Mirror", "West Dark Death Mountain (Bottom)", "East Dark World")] - if not inverted: - yield ('Dark Death Mountain Descent', 'West Dark Death Mountain (Bottom)', 'West Dark World') - yield ('Ganons Tower Ascent', 'West Dark Death Mountain (Bottom)', 'Dark Death Mountain (Top)') # This only gets you to the GT entrance - yield ('Dark Death Mountain Glitched Bridge', 'West Dark Death Mountain (Bottom)', 'Dark Death Mountain (Top)') - yield ('Turtle Rock (Top) Clip Spot', 'Dark Death Mountain (Top)', 'Turtle Rock (Top)') - else: - yield ('Dark Desert Teleporter Clip Spot', 'Dark Desert', 'Dark Desert Ledge') +# Mirror shenanigans placing a mirror portal with a broken camera - -def get_glitched_speed_drops_dw(inverted = False): - """ - Dark World drop-down ledges that require glitched speed. - """ - yield ('Dark Death Mountain Ledge Clip Spot', 'Dark Death Mountain (Top)', 'Dark Death Mountain Ledge') - - -def get_mirror_clip_spots_dw(): - """ - Out of bounds transitions using the mirror - """ - yield ('Dark Death Mountain Bunny Descent Mirror Spot', 'West Dark Death Mountain (Bottom)', 'West Dark World') - yield ('Dark Death Mountain Bunny Mirror To East Jump', 'West Dark Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)') - yield ('Desert East Mirror Clip', 'Dark Desert', 'Desert Palace Mouth') - - -def get_mirror_offset_spots_dw(): - """ - Mirror shenanigans placing a mirror portal with a broken camera - """ - yield ('Dark Death Mountain Offset Mirror', 'West Dark Death Mountain (Bottom)', 'East Dark World') - - -def get_mirror_offset_spots_lw(player): - """ - Mirror shenanigans placing a mirror portal with a broken camera - """ - yield ('Death Mountain Offset Mirror', 'West Death Mountain (Bottom)', 'Light World') - yield ('Death Mountain Uncle Offset Mirror', 'West Death Mountain (Bottom)', 'Hyrule Castle Secret Entrance Area') - yield ('Death Mountain Castle Ledge Offset Mirror', 'West Death Mountain (Bottom)', 'Hyrule Castle Ledge') +mirror_offset_spots_lw = [ + ("Death Mountain Offset Mirror", "West Death Mountain (Bottom)", "Light World"), + ("Death Mountain Uncle Offset Mirror", "West Death Mountain (Bottom)", "Hyrule Castle Secret Entrance Area"), + ("Death Mountain Castle Ledge Offset Mirror", "West Death Mountain (Bottom)", "Hyrule Castle Ledge"), +] def create_owg_connections(world, player): """ Add OWG transitions to player's world without logic """ - create_no_logic_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) - create_no_logic_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted')) - - # Glitched speed drops. - create_no_logic_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted')) - - # Mirror clip spots. - if world.mode[player] != 'inverted': - create_no_logic_connections(player, world, get_mirror_clip_spots_dw()) - create_no_logic_connections(player, world, get_mirror_offset_spots_dw()) + if world.mode[player] == "inverted": + connections = ( + inverted_boots_clip_exits_dw + + inverted_boots_clip_exits_lw + + glitched_speed_drops_dw + + mirror_offset_spots_lw + ) else: - create_no_logic_connections(player, world, get_mirror_offset_spots_lw(player)) + connections = ( + open_boots_clip_exits_dw + + open_boots_clip_exits_lw + + glitched_speed_drops_dw + + mirror_clip_spots_dw + + mirror_offset_spots_dw + ) + + create_no_logic_connections(player, world, connections) def overworld_glitches_rules(world, player): - # Boots-accessible locations. - set_owg_rules(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: state.can_boots_clip_lw(player)) - set_owg_rules(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted'), lambda state: state.can_boots_clip_dw(player)) + inverted = world.mode[player] == "inverted" + # Boots-accessible locations. + set_owg_rules( + player, + world, + inverted_boots_clip_exits_lw if inverted else open_boots_clip_exits_lw, + lambda state: state.can_boots_clip_lw(player), + ) + set_owg_rules( + player, + world, + inverted_boots_clip_exits_dw if inverted else open_boots_clip_exits_dw, + lambda state: state.can_boots_clip_dw(player), + ) # Glitched speed drops. - set_owg_rules(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: state.can_get_glitched_speed_dw(player)) + set_owg_rules( + player, + world, + glitched_speed_drops_dw, + lambda state: state.can_get_glitched_speed_dw(player), + ) # Dark Death Mountain Ledge Clip Spot also accessible with mirror. - if world.mode[player] != 'inverted': - add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has_Mirror(player)) + if not inverted: + add_alternate_rule( + world.get_entrance("Dark Death Mountain Ledge Clip Spot", player), lambda state: state.has_Mirror(player) + ) # Mirror clip spots. - if world.mode[player] != 'inverted': - set_owg_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has_Mirror(player)) - set_owg_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player)) + if inverted: + set_owg_rules( + player, + world, + mirror_offset_spots_lw, + lambda state: state.has_Mirror(player) and state.can_boots_clip_dw(player), + ) else: - set_owg_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has_Mirror(player) and state.can_boots_clip_dw(player)) - + set_owg_rules(player, world, mirror_clip_spots_dw, lambda state: state.has_Mirror(player)) + set_owg_rules( + player, + world, + mirror_offset_spots_dw, + lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player), + ) + # Regions that require the boots and some other stuff. - if world.mode[player] != 'inverted': - world.get_entrance('Turtle Rock Teleporter', player).access_rule = lambda state: (state.can_boots_clip_lw(player) or state.can_lift_heavy_rocks(player)) and state.has('Hammer', player) - add_alternate_rule(world.get_entrance('Waterfall Fairy Access', player), lambda state: state.has_Pearl(player) or state.has_Boots(player)) # assumes access to Waterwalk ability (boots case) - else: - add_alternate_rule(world.get_entrance('Waterfall Fairy Access', player), lambda state: state.has_Pearl(player)) + if not inverted: + world.get_entrance("Turtle Rock Teleporter", player).access_rule = lambda state: ( + state.can_boots_clip_lw(player) or state.can_lift_heavy_rocks(player) + ) and state.has("Hammer", player) - world.get_entrance('Dark Desert Teleporter', player).access_rule = lambda state: (state.can_flute(player) or state.can_boots_clip_dw(player)) and state.can_lift_heavy_rocks(player) - add_alternate_rule(world.get_entrance('Dark Witch Rock (North)', player), lambda state: state.can_boots_clip_dw(player)) - add_alternate_rule(world.get_entrance('Broken Bridge Pass (Top)', player), lambda state: state.can_boots_clip_dw(player)) - add_alternate_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.can_boots_clip_lw(player)) # assumes access to Waterwalk ability + add_alternate_rule( + world.get_entrance("Waterfall Fairy Access", player), + lambda state: state.has_Pearl(player) or state.has_Boots(player), + ) # assumes access to Waterwalk ability (boots case) + else: + add_alternate_rule(world.get_entrance("Waterfall Fairy Access", player), lambda state: state.has_Pearl(player)) + + world.get_entrance("Dark Desert Teleporter", player).access_rule = lambda state: ( + state.can_flute(player) or state.can_boots_clip_dw(player) + ) and state.can_lift_heavy_rocks(player) + + add_alternate_rule( + world.get_entrance("Dark Witch Rock (North)", player), lambda state: state.can_boots_clip_dw(player) + ) + add_alternate_rule( + world.get_entrance("Broken Bridge Pass (Top)", player), lambda state: state.can_boots_clip_dw(player) + ) + add_alternate_rule( + world.get_location("Zora's Ledge", player), lambda state: state.can_boots_clip_lw(player) + ) # assumes access to Waterwalk ability + + # This is doable even with bad enemies + add_alternate_rule(world.get_location("Hobo", player), lambda state: state.can_boots_clip_lw(player)) + def add_alternate_rule(entrance, rule): diff --git a/Rules.py b/Rules.py index 6e1f40f8..94100773 100644 --- a/Rules.py +++ b/Rules.py @@ -1905,7 +1905,7 @@ def set_bunny_rules(world, player, inverted): # bunny revival accessible. if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type != RegionType.Dungeon \ - and (location is None or location.name not in OverworldGlitchRules.get_superbunny_accessible_locations()) \ + and (location is None or location.name not in OverworldGlitchRules.superbunny_accessible_locations) \ and not is_link(region): return lambda state: state.has_Pearl(player) else: @@ -1935,7 +1935,7 @@ def set_bunny_rules(world, player, inverted): if not is_link(new_region): if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue if entrance.name in drop_dungeon_entrances: lobby = entrance.connected_region @@ -1949,21 +1949,21 @@ def set_bunny_rules(world, player, inverted): possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) continue elif region.type == RegionType.Cave and new_region.type != RegionType.Cave: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue - if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): + if region.name in OverworldGlitchRules.sword_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) - elif region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions(): + elif region.name in OverworldGlitchRules.boots_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) - elif location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): - if location.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_locations(): + elif location and location.name in OverworldGlitchRules.superbunny_accessible_locations: + if location.name in OverworldGlitchRules.boots_required_superbunny_mirror_locations: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) elif region.name == 'Kakariko Well (top)': possible_options.append(path_to_access_rule(new_path, entrance)) else: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player)], entrance)) continue - elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): + elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.superbunny_accessible_locations: possible_options.append(path_to_access_rule(new_path, entrance)) else: continue From 15558250da200dea19593f4a39185060744d68e9 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:43:55 +0100 Subject: [PATCH 11/23] Change Maze race to collect location rather than reach region - Brother West is not reachable from the clip --- OverworldGlitchRules.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index ba44ba25..9ee11a29 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -130,7 +130,6 @@ inverted_boots_clip_exits_lw = [ ("Lake Hylia Island Clip Spot", "Light World", "Lake Hylia Island"), ("Death Mountain Descent", "West Death Mountain (Bottom)", "Light World"), ("Kings Grave Clip Spot", "West Death Mountain (Bottom)", "Kings Grave Area"), - ("Maze Race Clip Spot", "Light World", "Maze Race Ledge"), ] open_boots_clip_exits_lw = [ @@ -291,6 +290,10 @@ def overworld_glitches_rules(world, player): world.get_location("Zora's Ledge", player), lambda state: state.can_boots_clip_lw(player) ) # assumes access to Waterwalk ability + add_alternate_rule( + world.get_location('Maze Race', player), lambda state: state.can_boots_clip_lw(player) + ) + # This is doable even with bad enemies add_alternate_rule(world.get_location("Hobo", player), lambda state: state.can_boots_clip_lw(player)) From 7f5fb1645360cda83078f270a62b7c0709a49d44 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:44:48 +0100 Subject: [PATCH 12/23] Refactor hmg key logic --- Rules.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Rules.py b/Rules.py index 94100773..f4de86c2 100644 --- a/Rules.py +++ b/Rules.py @@ -737,6 +737,9 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Crystal Circles to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_blunt_weapon(player) or state.has('Cane of Byrna', player)) # or state.has_beam_sword(player) add_key_logic_rules(world, player) + + if world.logic[player] == 'hybridglitches': + add_hmg_key_logic_rules(world, player) # End of door rando rules. if world.restrict_boss_items[player] != 'none': @@ -2142,6 +2145,11 @@ bunny_impassible_if_trapped = { 'GT Speed Torch WN', 'Ice Lobby SE' } +def add_hmg_key_logic_rules(world, player): + for toh_loc in world.key_logic[player]['Tower of Hera'].bk_restricted: + set_always_allow(world.get_location(toh_loc.name, player), allow_big_key_in_big_chest('Big Key (Tower of Hera)', player)) + set_always_allow(world.get_location('Swamp Palace - Entrance', player), allow_big_key_in_big_chest('Big Key (Swamp Palace)', player)) + def add_key_logic_rules(world, player): key_logic = world.key_logic[player] @@ -2159,9 +2167,6 @@ def add_key_logic_rules(world, player): add_rule(dep.entrance, eval_func(door_name, d_name, player)) for location in d_logic.bk_restricted: if not location.forced_item: - # Skip BK restricted locations in hybrid glitches. Bad, but necessary for now. - if world.logic[player] == 'hybridglitches' and d_name in ['Tower of Hera', 'Swamp Palace']: - continue forbid_item(location, d_logic.bk_name, player) for location in d_logic.sm_restricted: forbid_item(location, d_logic.small_key_name, player) From 6a41dff98b55b009a1eb1806cefc8240c5dee0e8 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:45:29 +0100 Subject: [PATCH 13/23] Raise error on doors + hmg --- Main.py | 4 ++++ resources/app/cli/lang/en.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index b273d922..99b257e2 100644 --- a/Main.py +++ b/Main.py @@ -79,6 +79,10 @@ def main(args, seed=None, fish=None): seed = customized.determine_seed(seed) seeded = True customized.adjust_args(args) + for i in zip(args.logic.values(), args.door_shuffle.values()): + if i[0] == 'hybridglitches' and i[1] != 'vanilla': + raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) + # print(args) world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 3cd6aa19..1f0defb9 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -55,7 +55,8 @@ "building.collection.spheres": "Building up collection spheres", "building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.", "building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.", - "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s" + "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s", + "hybridglitches.door.shuffle": "Hybrid Major Glitches is not currently compatible with Door Shuffle." }, "help": { "lang": [ "App Language, if available, defaults to English" ], From 6510968401ce9754f5b05964aab66901976f90e8 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:48:12 +0100 Subject: [PATCH 14/23] Support bunny pocket for SW back and voo hammer house --- BaseClasses.py | 39 +++++++++++++++++++++++++++++++++++++++ OverworldGlitchRules.py | 10 +++++++--- Rules.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index cff79a25..fe1f3c96 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -550,6 +550,42 @@ class CollectionState(object): self.placing_items = None # self.trace = None + def can_reach_from(self, spot, start, player=None): + old_state = self.copy() + # old_state.path = {old_state.world.get_region(start, player)} + old_state.stale[player] = False + old_state.reachable_regions[player] = dict() + old_state.blocked_connections[player] = dict() + rrp = old_state.reachable_regions[player] + bc = old_state.blocked_connections[player] + + # init on first call - this can't be done on construction since the regions don't exist yet + start = self.world.get_region(start, player) + if start in self.reachable_regions[player]: + rrp[start] = self.reachable_regions[player][start] + for conn in start.exits: + bc[conn] = self.blocked_connections[player][conn] + else: + rrp[start] = CrystalBarrier.Orange + for conn in start.exits: + bc[conn] = CrystalBarrier.Orange + + queue = deque(old_state.blocked_connections[player].items()) + + old_state.traverse_world(queue, rrp, bc, player) + if old_state.world.key_logic_algorithm[player] == 'default': + unresolved_events = [x for y in old_state.reachable_regions[player] for x in y.locations + if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) + and x not in old_state.locations_checked and x.can_reach(old_state)] + unresolved_events = old_state._do_not_flood_the_keys(unresolved_events) + if len(unresolved_events) == 0: + old_state.check_key_doors_in_dungeons(rrp, player) + + if self.world.get_region(spot, player) in rrp: + return True + else: + return False + def update_reachable_regions(self, player): self.stale[player] = False rrp = self.reachable_regions[player] @@ -1274,6 +1310,9 @@ class CollectionState(object): def can_superbunny_mirror_with_sword(self, player): return self.has_Mirror(player) and self.has_sword(player) + + def can_bunny_pocket(self, player): + return self.has_Boots(player) and (self.has_Mirror(player) or self.has_bottle(player)) def collect(self, item, event=False, location=None): if location: diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 9ee11a29..7e03b64c 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -19,7 +19,6 @@ boots_required_superbunny_mirror_locations = [ # Entrances that can't be superbunny-mirrored into. invalid_mirror_bunny_entrances = [ - "Skull Woods Final Section", "Hype Cave", "Bonk Fairy (Dark)", "Thieves Town", @@ -107,7 +106,7 @@ inverted_non_mandatory_exits = [ "Hyrule Castle Entrance (East)", ] + non_mandatory_exits -open_non_mandatory_exits_ = [ +open_non_mandatory_exits = [ "Dark Death Mountain Ledge (West)", "Dark Death Mountain Ledge (East)", "Mimic Cave", @@ -296,7 +295,12 @@ def overworld_glitches_rules(world, player): # This is doable even with bad enemies add_alternate_rule(world.get_location("Hobo", player), lambda state: state.can_boots_clip_lw(player)) - + + # Bunny pocket + if not inverted: + add_alternate_rule(world.get_entrance("Skull Woods Final Section", player), lambda state: state.can_bunny_pocket(player) and state.has("Fire Rod", player)) + add_alternate_rule(world.get_entrance("Dark World Shop", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + def add_alternate_rule(entrance, rule): diff --git a/Rules.py b/Rules.py index f4de86c2..7e414424 100644 --- a/Rules.py +++ b/Rules.py @@ -1902,6 +1902,34 @@ def set_bunny_rules(world, player, inverted): return region.is_dark_world else: return region.is_light_world + + # Is it possible to do bunny pocket here + def can_bunny_pocket_skull_woods(world, player): + # return world.get_entrance( + # "Skull Woods Second Section Door (West)", player + # ).connected_region.type != RegionType.Dungeon and ( + # not world.state.can_reach_from("Skull Woods Forest (West)", "Light World", 1) + # or not world.state.can_reach_from("Light World", "Skull Woods Forest (West)", 1) + # ) + return world.get_entrance( + "Skull Woods Second Section Door (West)", player + ).connected_region.type == RegionType.Dungeon or ( + world.state.can_reach_from("Skull Woods Forest (West)", "Light World", 1) + and world.state.can_reach_from("Light World", "Skull Woods Forest (West)", 1) + ) + + def can_bunny_pocket_voo_shop(world, player): + # return world.get_entrance( + # "Dark World Shop", player + # ).connected_region.type != RegionType.Dungeon and ( + # not world.state.can_reach_from("West Dark World", "Light World", 1) + # or not world.state.can_reach_from("Light World", "West Dark World", 1) + # ) + return ( + world.state.can_reach_from("West Dark World", "Light World", 1) + and world.state.can_reach_from("Light World", "West Dark World", 1) + ) + def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or @@ -1940,6 +1968,10 @@ def set_bunny_rules(world, player, inverted): if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue + # Is this a bunny pocketable entrance? + if entrance.name == 'Skull Woods Final Section' and not can_bunny_pocket_skull_woods(world, player) or \ + entrance.name == 'Dark World Shop' and not can_bunny_pocket_voo_shop(world, player): + continue if entrance.name in drop_dungeon_entrances: lobby = entrance.connected_region else: @@ -1954,6 +1986,9 @@ def set_bunny_rules(world, player, inverted): elif region.type == RegionType.Cave and new_region.type != RegionType.Cave: if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue + if entrance.name == 'Skull Woods Final Section' and not can_bunny_pocket_skull_woods(world, player) or \ + entrance.name == 'Dark World Shop' and not can_bunny_pocket_voo_shop(world, player): + continue if region.name in OverworldGlitchRules.sword_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) elif region.name in OverworldGlitchRules.boots_required_superbunny_mirror_regions: From a4a523ce4cc64ce3ea8512f74adabbc8d2adcf73 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:49:15 +0100 Subject: [PATCH 15/23] Small cleanup --- EntranceShuffle.py | 7 ++++--- source/overworld/EntranceShuffle2.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index a1e15991..020e95a7 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1106,7 +1106,8 @@ def connect_random(world, exitlist, targetlist, player, two_way=False): def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): # Keeps track of entrances that cannot be used to access each exit / cave - if world.mode[player] == 'inverted': + inverted = world.mode[player] == 'inverted' + if inverted: invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy() else: invalid_connections = Must_Exit_Invalid_Connections.copy() @@ -1114,7 +1115,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules - for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): + for entrance in OverworldGlitchRules.inverted_non_mandatory_exits if inverted else OverworldGlitchRules.open_non_mandatory_exits: invalid_connections[entrance] = set() if entrance in must_be_exits: must_be_exits.remove(entrance) @@ -1125,7 +1126,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): random.shuffle(caves) # Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge - if world.mode[player] == 'inverted': + if inverted: for entrance in invalid_connections: if world.get_entrance(entrance, player).connected_region == world.get_region('Agahnims Tower Portal', player): for exit in invalid_connections[entrance]: diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 9847e472..1d36e43c 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -959,7 +959,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if avail.world.logic[avail.player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules - for entrance in OverworldGlitchRules.get_non_mandatory_exits(avail.inverted): + for entrance in OverworldGlitchRules.inverted_non_mandatory_exits if avail.inverted else OverworldGlitchRules.open_non_mandatory_exits: invalid_connections[entrance] = set() if entrance in must_exit: must_exit.remove(entrance) From e1a2e1bb5f40e0ebcd88b430186fecb8b2ef2de4 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:49:55 +0100 Subject: [PATCH 16/23] Only return lobbies rather than dungeon regions --- Rules.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index 7e414424..f204e7ac 100644 --- a/Rules.py +++ b/Rules.py @@ -1975,7 +1975,8 @@ def set_bunny_rules(world, player, inverted): if entrance.name in drop_dungeon_entrances: lobby = entrance.connected_region else: - lobby = next(exit.connected_region for exit in current.exits if exit.connected_region.type == RegionType.Dungeon) + portal_regions = [world.get_region(reg, player) for reg in region.dungeon.regions if reg.endswith('Portal')] + lobby = next(reg.connected_region for portal_reg in portal_regions for reg in portal_reg.exits if reg.name.startswith('Enter ')) if lobby.name in bunny_revivable_entrances: possible_options.append(path_to_access_rule(new_path, entrance)) elif lobby.name in superbunny_revivable_entrances: From 6d79e48ab0436daae86e8b26304aa72c3a8b0d51 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:51:42 +0100 Subject: [PATCH 17/23] Don't explore dungeons with a single exit for bunny logic --- Rules.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Rules.py b/Rules.py index f204e7ac..3e1aa061 100644 --- a/Rules.py +++ b/Rules.py @@ -1860,6 +1860,8 @@ def set_inverted_big_bomb_rules(world, player): def set_bunny_rules(world, player, inverted): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. + all_single_exit_dungeons = ['Eastern Palace', 'Tower of Hera', 'Castle Tower', 'Palace of Darkness', 'Swamp Palace', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Ganons Tower'] + hmg_single_exit_dungeons = [d for d in all_single_exit_dungeons if d not in ['Tower of Hera', 'Misery Mire', 'Thieves Town']] bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', @@ -1961,6 +1963,13 @@ def set_bunny_rules(world, player, inverted): new_region = entrance.parent_region if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: continue + # We don't need to navigate through single entrance dungeons. HMG has more multi-entrance dungeons + if (region.type == RegionType.Dungeon and new_region.type == RegionType.Dungeon): + if ( + world.logic[player] == 'hybridglitches' and new_region.dungeon != None and new_region.dungeon.name in hmg_single_exit_dungeons) or ( + world.logic[player] not in ['hybridglitches', 'nologic'] and new_region.dungeon != None and new_region.dungeon.name in all_single_exit_dungeons + ): + continue new_path = path + [entrance.access_rule] new_seen = seen.union({new_region}) if not is_link(new_region): From fce43eb2899454dc0257569b22d6eef88d89d5eb Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:55:51 +0100 Subject: [PATCH 18/23] HMG tests --- test/suite/hmg/bunny_pocket_sw.yaml | 34 ++++++++++++++++++++++ test/suite/hmg/fireless_ice.yaml | 17 +++++++++++ test/suite/hmg/hera_from_mire.yaml | 26 +++++++++++++++++ test/suite/hmg/moon_pearl_locs.yaml | 44 +++++++++++++++++++++++++++++ test/suite/hmg/pearlless_sw.yaml | 25 ++++++++++++++++ test/suite/hmg/swamp_from_mire.yaml | 30 ++++++++++++++++++++ 6 files changed, 176 insertions(+) create mode 100644 test/suite/hmg/bunny_pocket_sw.yaml create mode 100644 test/suite/hmg/fireless_ice.yaml create mode 100644 test/suite/hmg/hera_from_mire.yaml create mode 100644 test/suite/hmg/moon_pearl_locs.yaml create mode 100644 test/suite/hmg/pearlless_sw.yaml create mode 100644 test/suite/hmg/swamp_from_mire.yaml diff --git a/test/suite/hmg/bunny_pocket_sw.yaml b/test/suite/hmg/bunny_pocket_sw.yaml new file mode 100644 index 00000000..2b263db2 --- /dev/null +++ b/test/suite/hmg/bunny_pocket_sw.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches + shuffle: crossed +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Pyramid Fairy - Left: True +entrances: + 1: + entrances: + Skull Woods Final Section: Pyramid Fairy + two-way: + Thieves Town: Two Brothers House Exit (West) + Spectacle Rock Cave (Bottom): Two Brothers House Exit (East) + diff --git a/test/suite/hmg/fireless_ice.yaml b/test/suite/hmg/fireless_ice.yaml new file mode 100644 index 00000000..79b31f59 --- /dev/null +++ b/test/suite/hmg/fireless_ice.yaml @@ -0,0 +1,17 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove +placements: + 1: + Ice Palace - Map Chest: Bombos + Ice Palace - Iced T Room: Fire Rod diff --git a/test/suite/hmg/hera_from_mire.yaml b/test/suite/hmg/hera_from_mire.yaml new file mode 100644 index 00000000..3e7d0c49 --- /dev/null +++ b/test/suite/hmg/hera_from_mire.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Big Key (Tower of Hera) + locations: + Tower of Hera - Big Key Chest: True + Tower of Hera - Basement Cage: True + Tower of Hera - Map Chest: True + Tower of Hera - Compass Chest: True + Tower of Hera - Big Chest: True + Tower of Hera - Boss: True \ No newline at end of file diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml new file mode 100644 index 00000000..f5d06745 --- /dev/null +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Palace of Darkness - Shooter Room: True + Palace of Darkness - The Arena - Bridge: True + Palace of Darkness - Stalfos Basement: True + Palace of Darkness - Big Key Chest: True + Palace of Darkness - The Arena - Ledge: True + Palace of Darkness - Map Chest: True + Palace of Darkness - Compass Chest: True + Palace of Darkness - Dark Basement - Left: True + Palace of Darkness - Dark Basement - Right: True + Palace of Darkness - Dark Maze - Top: True + Palace of Darkness - Dark Maze - Bottom: True + Palace of Darkness - Big Chest: True + Palace of Darkness - Harmless Hellway: True + Palace of Darkness - Boss: True + Bombos Tablet: True + C-Shaped House: True + Pyramid Fairy - Left: True + Swamp Palace - Entrance: False + Thieves' Town - Map Chest: False + + + + diff --git a/test/suite/hmg/pearlless_sw.yaml b/test/suite/hmg/pearlless_sw.yaml new file mode 100644 index 00000000..c26ce1d9 --- /dev/null +++ b/test/suite/hmg/pearlless_sw.yaml @@ -0,0 +1,25 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Bridge Room: True diff --git a/test/suite/hmg/swamp_from_mire.yaml b/test/suite/hmg/swamp_from_mire.yaml new file mode 100644 index 00000000..5892f07c --- /dev/null +++ b/test/suite/hmg/swamp_from_mire.yaml @@ -0,0 +1,30 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Small Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True \ No newline at end of file From 80c8c189a8253bde6f5850a6c482d87c2a1237ee Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Tue, 12 Dec 2023 18:56:21 +0100 Subject: [PATCH 19/23] Minor HMG changes --- UnderworldGlitchRules.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 98eb6626..1b6b276b 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -156,6 +156,7 @@ def underworld_glitches_rules(world, player): Rules.add_rule(world.get_entrance("Hera Startile Corner NW", player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") + Rules.add_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") mire_to_hera = world.get_entrance("Mire to Hera Clip", player) mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) @@ -206,14 +207,14 @@ def underworld_glitches_rules(world, player): def gt_rule(state): return (state.has("Moon Pearl", player) or inverted) and rule_map.get( - world.get_entrance(("Ganons Tower" if not inverted else "Inverted Ganons Tower"), player).connected_region.name, lambda state: False)(state) + world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False)(state) def mirrorless_moat_rule(state): return state.can_reach("Old Man S&Q", "Entrance", player) and mire_clip(state) and (hera_rule(state) or gt_rule(state)) Rules.add_rule(world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or") - # Thieves -> Hera + # Thieves -> Desert Rules.add_rule(world.get_entrance("Thieves to Desert Clip", player), lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player)) dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert West Portal", "Desert Palace Exit (West)") dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert South Portal", "Desert Palace Exit (South)") From 9fb7cf9f719be31a7f3986652ce71a3480700bca Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sun, 17 Dec 2023 09:45:22 +0100 Subject: [PATCH 20/23] Refactor UW Glitches --- README.md | 33 ++++++ UnderworldGlitchRules.py | 231 +++++++++++++++++++++++---------------- 2 files changed, 171 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 24e79807..dc1d3f25 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ See https://alttpr.com/ for more details on the normal randomizer. 7. [Standard Changes](#standard-changes) 8. [Game Options](#game-options) 9. [Generation Setup & Miscellaneous](#generation-setup--miscellaneous) + 10. [Glitched Logic](#glitched-logic) ## Setup and Installation @@ -597,4 +598,36 @@ Can be used to set a seed number to generate. Using the same seed with same sett Use to batch generate multiple seeds with same settings. If a seed number is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with the same seed number given will produce the same 10 (different) roms each time). +## Glitched Logic + +Overworld glitches, Hybrid Major Glitches (HMG) and No Logic are currently supported. + +### Overworld Glitches +_Support added by qadan and compiling_ + +Overworld Glitches logic includes (but is not limited to) the following: +* Overworld teleports and clips to reach various items/entrances +* Use of superbunny to obtain items and/or bonk open entrances +* Use of mirror to access Desert Palace East Entrance +* Use of bunny pocket to access the Back of Skull Woods and VOO Hammer house entrances + + +### Hybrid Major Glitches +_Support added by Muffins (ported from work by Espeon)_. + +**Not currently compatible with Door Shuffle** + +Hybrid Major Glitches logic includes the following: +* All Overworld Glitches logic +* Kikiskip to access PoD wihtout MP or DW access +* IP Lobby clip to skip fire requirement +* Traversal between Mire -> Hera -> Swamp +* Stealing SK from Mire to open SP +* Using the Mire big key to open Hera doors and big chest +* Traversal between TT -> Desert +* Traveral between Spec rock upper -> Spec rock mid +* Traveral between Paradox lower -> Paradox mid + upper + + + diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 1b6b276b..7ddfa792 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -2,92 +2,71 @@ from BaseClasses import Entrance import Rules from OverworldGlitchRules import create_no_logic_connections +kikiskip_spots = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")] -def get_kikiskip_spots(): - """ - Spectacle Rock Cave (Bottom) -> Palace of Darkness Exit, a.k.a. Kiki Skip - """ - yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal") +mireheraswamp_spots = [ + ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal"), + ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal"), +] +icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop")] -def get_mireheraswamp_spots(): - """ - "Mire Torches Top -> Tower of Hera Exit, a.k.a. Mire to Hera Clip - "Mire Torches Top -> Swamp Palace Exit, a.k.a. Hera to Swamp Clip - """ +thievesdesert_spots = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert West Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert South Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert East Portal"), +] - yield ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal") - yield ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal") +specrock_spots = [("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)")] - -def get_icepalace_spots(): - """ - "Ice Palace Exit -> Ice Palace Exit, a.k.a. Ice Palace Clip - """ - yield ("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop") - - -def get_thievesdesert_spots(): - """ - "Thieves' Town -> Desert Palace , a.k.a. Thieves to Desert Clip - Accessing any of the exits will be in logic because of the ability to dungeon bunny revive - """ - yield ("Thieves to Desert Clip", "Thieves Attic", "Desert West Portal") - yield ("Thieves to Desert Clip", "Thieves Attic", "Desert South Portal") - yield ("Thieves to Desert Clip", "Thieves Attic", "Desert East Portal") - - -def get_specrock_spots(): - """ - "Spectacle Rock Cave (Peak) -> Spectacle Rock Cave (Top), a.k.a. Spectacle Rock Cave Clip - """ - yield ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)") - - -def get_paradox_spots(): - """ - "Paradox Cave Front -> Paradox Cave Chest Area, a.k.a. Paradox Cave Teleport (dash citrus, 1f right, teleport up) - """ - yield ("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area") +paradox_spots = [("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area")] # We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions -def get_kikiskip_connectors(world, player): - yield ("Kiki Skip", "Spectacle Rock Cave (Bottom)", world.get_entrance("Palace of Darkness Exit", player).connected_region) +kikiskip_connectors = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit")] -def get_mireheraswamp_connectors(world, player): - yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Tower of Hera Exit", player).connected_region) - yield ("Mire to Hera Clip", "Mire Torches Top", world.get_entrance("Swamp Palace Exit", player).connected_region) +mireheraswamp_connectors = [ + ("Mire to Hera Clip", "Mire Torches Top", "Tower of Hera Exit"), + ("Mire to Hera Clip", "Mire Torches Top", "Swamp Palace Exit"), +] -def get_thievesdesert_connectors(world, player): - yield ("Thieves to Desert Clip", "Thieves Attic", world.get_entrance("Desert Palace Exit (West)", player).connected_region) - yield ("Thieves to Desert Clip", "Thieves Attic", world.get_entrance("Desert Palace Exit (South)", player).connected_region) - yield ("Thieves to Desert Clip", "Thieves Attic", world.get_entrance("Desert Palace Exit (East)", player).connected_region) - -def get_specrock_connectors(world, player): - yield ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", world.get_entrance("Spectacle Rock Cave Exit (Top)", player).connected_region) - yield ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", world.get_entrance("Spectacle Rock Cave Exit", player).connected_region) +thievesdesert_connectors = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (West)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (South)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (East)"), +] +specrock_connectors = [ + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit (Top)"), + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit"), +] # Create connections between dungeons/locations def create_hybridmajor_connections(world, player): - create_no_logic_connections(player, world, get_kikiskip_spots()) - create_no_logic_connections(player, world, get_mireheraswamp_spots()) - create_no_logic_connections(player, world, get_icepalace_spots()) - create_no_logic_connections(player, world, get_thievesdesert_spots()) - create_no_logic_connections(player, world, get_specrock_spots()) - create_no_logic_connections(player, world, get_paradox_spots()) + for spots in [ + kikiskip_spots, + mireheraswamp_spots, + icepalace_spots, + thievesdesert_spots, + specrock_spots, + paradox_spots, + ]: + create_no_logic_connections(player, world, spots) # Turn dungeons into connectors def create_hybridmajor_connectors(world, player): - create_no_logic_connections(player, world, get_kikiskip_connectors(world, player)) - create_no_logic_connections(player, world, get_mireheraswamp_connectors(world, player)) - create_no_logic_connections(player, world, get_thievesdesert_connectors(world, player)) - create_no_logic_connections(player, world, get_specrock_connectors(world, player)) + for connectors in [ + kikiskip_connectors, + mireheraswamp_connectors, + thievesdesert_connectors, + specrock_connectors, + ]: + new_connectors = [(connector[0], connector[1], world.get_entrance(connector[2], player).connected_region) for connector in connectors] + create_no_logic_connections(player, world, new_connectors) # For some entrances, we need to fake having pearl, because we're in fake DW/LW. @@ -121,7 +100,12 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du Rules.add_rule(clip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) elif dungeon_entrance.name == "Agahnims Tower": - Rules.add_rule(clip, lambda state: state.has("Cape", player) or state.has_beam_sword(player) or state.has("Beat Agahnim 1", player)) + Rules.add_rule( + clip, + lambda state: state.has("Cape", player) + or state.has_beam_sword(player) + or state.has("Beat Agahnim 1", player), + ) # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) @@ -137,10 +121,12 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du def underworld_glitches_rules(world, player): # Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room - Rules.add_rule(world.get_entrance("Ice Bomb Drop SE", player), - lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) and - (state.can_use_bombs(player) or state.has('Cane of Somaria', player)), - combine="or") + Rules.add_rule( + world.get_entrance("Ice Bomb Drop SE", player), + lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) + and (state.can_use_bombs(player) or state.has("Cane of Somaria", player)), + combine="or", + ) # Kiki Skip kks = world.get_entrance("Kiki Skip", player) @@ -149,15 +135,26 @@ def underworld_glitches_rules(world, player): # Mire -> Hera -> Swamp def mire_clip(state): - return state.can_reach("Mire Torches Top", "Region", player) and state.can_dash_clip(world.get_region("Mire Torches Top", player), player) + return state.can_reach("Mire Torches Top", "Region", player) and state.can_dash_clip( + world.get_region("Mire Torches Top", player), player + ) def hera_clip(state): - return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip(world.get_region("Hera 4F", player), player) - + return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip( + world.get_region("Hera 4F", player), player + ) + + Rules.add_rule( + world.get_entrance("Hera Startile Corner NW", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + Rules.add_rule( + world.get_location("Tower of Hera - Big Chest", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) - Rules.add_rule(world.get_entrance("Hera Startile Corner NW", player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") - Rules.add_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or") - mire_to_hera = world.get_entrance("Mire to Hera Clip", player) mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) Rules.set_rule(mire_to_hera, mire_clip) @@ -165,7 +162,7 @@ def underworld_glitches_rules(world, player): # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") - dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") + dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots # Flippers required for all of these doors to prevent locks when flooding for door in [ @@ -181,10 +178,18 @@ def underworld_glitches_rules(world, player): "Swamp Waterway NW", "Swamp T SW", ]: - Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has("Small Key (Misery Mire)", player, count=6) and state.has('Flippers', player), combine="or") + Rules.add_rule( + world.get_entrance(door, player), + lambda state: mire_clip(state) + and state.has("Small Key (Misery Mire)", player, count=6) + and state.has("Flippers", player), + combine="or", + ) # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") - Rules.add_rule(world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or") + Rules.add_rule( + world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or" + ) # Build the rule for SP moat. # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. @@ -203,30 +208,70 @@ def underworld_glitches_rules(world, player): inverted = world.mode[player] == "inverted" def hera_rule(state): - return (state.has("Moon Pearl", player) or not inverted) and rule_map.get(world.get_entrance("Tower of Hera", player).connected_region.name, lambda state: False)(state) + return (state.has("Moon Pearl", player) or not inverted) and rule_map.get( + world.get_entrance("Tower of Hera", player).connected_region.name, lambda state: False + )(state) def gt_rule(state): return (state.has("Moon Pearl", player) or inverted) and rule_map.get( - world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False)(state) + world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False + )(state) def mirrorless_moat_rule(state): - return state.can_reach("Old Man S&Q", "Entrance", player) and mire_clip(state) and (hera_rule(state) or gt_rule(state)) + return ( + state.can_reach("Old Man S&Q", "Entrance", player) + and mire_clip(state) + and (hera_rule(state) or gt_rule(state)) + ) - Rules.add_rule(world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or") + Rules.add_rule( + world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or" + ) # Thieves -> Desert - Rules.add_rule(world.get_entrance("Thieves to Desert Clip", player), lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player)) - dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert West Portal", "Desert Palace Exit (West)") - dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert South Portal", "Desert Palace Exit (South)") - dungeon_reentry_rules(world, player, world.get_entrance("Thieves to Desert Clip", player), "Desert East Portal", "Desert Palace Exit (East)") + Rules.add_rule( + world.get_entrance("Thieves to Desert Clip", player), + lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player), + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert West Portal", + "Desert Palace Exit (West)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert South Portal", + "Desert Palace Exit (South)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert East Portal", + "Desert Palace Exit (East)", + ) # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up - paradox_left_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Middle'] + paradox_left_chests = ["Paradox Cave Lower - Far Left", "Paradox Cave Lower - Left", "Paradox Cave Lower - Middle"] for location in paradox_left_chests: - Rules.add_rule(world.get_location(location, player), lambda state: state.can_dash_clip(world.get_location(location, player).parent_region, player), 'or') - + Rules.add_rule( + world.get_location(location, player), + lambda state: state.can_dash_clip(world.get_location(location, player).parent_region, player), + "or", + ) + # Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch - paradox_right_chests = ['Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right'] + paradox_right_chests = ["Paradox Cave Lower - Right", "Paradox Cave Lower - Far Right"] for location in paradox_right_chests: - Rules.add_rule(world.get_location(location, player), lambda state: (state.can_dash_clip(world.get_location(location, player).parent_region, player) and state.can_hit_crystal(player)), 'or') - + Rules.add_rule( + world.get_location(location, player), + lambda state: ( + state.can_dash_clip(world.get_location(location, player).parent_region, player) + and state.can_hit_crystal(player) + ), + "or", + ) From 22c4dcdfd393d19d9846d8ecc33386b4c9e2ba22 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sun, 17 Dec 2023 14:03:04 +0100 Subject: [PATCH 21/23] More test updates + disable bunny check skip --- Rules.py | 12 ++++----- ..._sw.yaml => entrance_bunny_pocket_sw.yaml} | 2 +- .../hmg/inverted_inaccessible_desert.yaml | 26 ++++++++++++++++++ test/suite/hmg/inverted_moon_pearl_locs.yaml | 27 +++++++++++++++++++ test/suite/hmg/moon_pearl_locs.yaml | 6 ++++- test/suite/hmg/swamp_from_mire.yaml | 13 +++++++++ 6 files changed, 78 insertions(+), 8 deletions(-) rename test/suite/hmg/{bunny_pocket_sw.yaml => entrance_bunny_pocket_sw.yaml} (88%) create mode 100644 test/suite/hmg/inverted_inaccessible_desert.yaml create mode 100644 test/suite/hmg/inverted_moon_pearl_locs.yaml diff --git a/Rules.py b/Rules.py index 3e1aa061..03bcaba6 100644 --- a/Rules.py +++ b/Rules.py @@ -1964,12 +1964,12 @@ def set_bunny_rules(world, player, inverted): if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: continue # We don't need to navigate through single entrance dungeons. HMG has more multi-entrance dungeons - if (region.type == RegionType.Dungeon and new_region.type == RegionType.Dungeon): - if ( - world.logic[player] == 'hybridglitches' and new_region.dungeon != None and new_region.dungeon.name in hmg_single_exit_dungeons) or ( - world.logic[player] not in ['hybridglitches', 'nologic'] and new_region.dungeon != None and new_region.dungeon.name in all_single_exit_dungeons - ): - continue + # if (region.type == RegionType.Dungeon and new_region.type == RegionType.Dungeon): + # if ( + # world.logic[player] == 'hybridglitches' and new_region.dungeon != None and new_region.dungeon.name in hmg_single_exit_dungeons) or ( + # world.logic[player] not in ['hybridglitches', 'nologic'] and new_region.dungeon != None and new_region.dungeon.name in all_single_exit_dungeons + # ): + # continue new_path = path + [entrance.access_rule] new_seen = seen.union({new_region}) if not is_link(new_region): diff --git a/test/suite/hmg/bunny_pocket_sw.yaml b/test/suite/hmg/entrance_bunny_pocket_sw.yaml similarity index 88% rename from test/suite/hmg/bunny_pocket_sw.yaml rename to test/suite/hmg/entrance_bunny_pocket_sw.yaml index 2b263db2..48f5021a 100644 --- a/test/suite/hmg/bunny_pocket_sw.yaml +++ b/test/suite/hmg/entrance_bunny_pocket_sw.yaml @@ -30,5 +30,5 @@ entrances: Skull Woods Final Section: Pyramid Fairy two-way: Thieves Town: Two Brothers House Exit (West) - Spectacle Rock Cave (Bottom): Two Brothers House Exit (East) + Skull Woods Second Section Door (West): Two Brothers House Exit (East) diff --git a/test/suite/hmg/inverted_inaccessible_desert.yaml b/test/suite/hmg/inverted_inaccessible_desert.yaml new file mode 100644 index 00000000..bb8ad8aa --- /dev/null +++ b/test/suite/hmg/inverted_inaccessible_desert.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted + shuffle: crossed + +start_inventory: + 1: + - Pegasus Boots + - Progressive Sword + - Hammer + - Fire Rod +placements: + 1: + Desert Palace - Boss: Moon Pearl +entrances: + 1: + two-way: + Skull Woods Final Section: Desert Palace Exit (West) + Skull Woods Second Section Door (West): Desert Palace Exit (East) + Thieves Town: Thieves Town Exit + Hyrule Castle Entrance (East): Desert Palace Exit (South) + Hyrule Castle Entrance (West): Desert Palace Exit (North) + diff --git a/test/suite/hmg/inverted_moon_pearl_locs.yaml b/test/suite/hmg/inverted_moon_pearl_locs.yaml new file mode 100644 index 00000000..65d23371 --- /dev/null +++ b/test/suite/hmg/inverted_moon_pearl_locs.yaml @@ -0,0 +1,27 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted +start_inventory: + 1: + - Flippers + - Progressive Sword + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Desert Palace - Big Chest: True + Eastern Palace - Big Chest: True + + + + diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml index f5d06745..a18d53d1 100644 --- a/test/suite/hmg/moon_pearl_locs.yaml +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -5,8 +5,10 @@ settings: logic: hybridglitches start_inventory: 1: + - Pegasus Boots - Flippers - - Moon Pearl + - Fire Rod + - Book of Mudora - Progressive Sword - Progressive Sword - Lamp @@ -19,6 +21,8 @@ advanced_placements: - type: Verification item: Moon Pearl locations: + Skull Woods - Compass Chest: True + Skull Woods - Bridge Room: True Palace of Darkness - Shooter Room: True Palace of Darkness - The Arena - Bridge: True Palace of Darkness - Stalfos Basement: True diff --git a/test/suite/hmg/swamp_from_mire.yaml b/test/suite/hmg/swamp_from_mire.yaml index 5892f07c..f3873909 100644 --- a/test/suite/hmg/swamp_from_mire.yaml +++ b/test/suite/hmg/swamp_from_mire.yaml @@ -17,6 +17,19 @@ advanced_placements: 1: - type: Verification item: Small Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True + - type: Verification + item: Big Key (Swamp Palace) locations: Swamp Palace - Entrance: True Swamp Palace - Map Chest: True From 81ace17889c057f37ae8d29689a451b6219fed24 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sun, 17 Dec 2023 21:02:47 +0100 Subject: [PATCH 22/23] More tests fixes --- test/suite/hmg/entrance_bunny_pocket_sw.yaml | 2 +- test/suite/hmg/inverted_moon_pearl_locs.yaml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/suite/hmg/entrance_bunny_pocket_sw.yaml b/test/suite/hmg/entrance_bunny_pocket_sw.yaml index 48f5021a..eba7743d 100644 --- a/test/suite/hmg/entrance_bunny_pocket_sw.yaml +++ b/test/suite/hmg/entrance_bunny_pocket_sw.yaml @@ -29,6 +29,6 @@ entrances: entrances: Skull Woods Final Section: Pyramid Fairy two-way: - Thieves Town: Two Brothers House Exit (West) + Chicken House: Two Brothers House Exit (West) Skull Woods Second Section Door (West): Two Brothers House Exit (East) diff --git a/test/suite/hmg/inverted_moon_pearl_locs.yaml b/test/suite/hmg/inverted_moon_pearl_locs.yaml index 65d23371..9ae1a9e1 100644 --- a/test/suite/hmg/inverted_moon_pearl_locs.yaml +++ b/test/suite/hmg/inverted_moon_pearl_locs.yaml @@ -9,6 +9,7 @@ start_inventory: - Flippers - Progressive Sword - Progressive Sword + - Book of Mudora - Lamp - Magic Mirror - Ether @@ -19,8 +20,11 @@ advanced_placements: - type: Verification item: Moon Pearl locations: + Tower of Hera - Big Chest: True Desert Palace - Big Chest: True Eastern Palace - Big Chest: True + Bombos Tablet: True + Cave 45: True From 36c8b5aaa653ad1218afea6263686aae5e5f765c Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 18 Dec 2023 20:46:18 +0100 Subject: [PATCH 23/23] Code cleanup and more tests --- Main.py | 1 - README.md | 10 +++---- Rules.py | 19 ------------- test/suite/hmg/flippers_locked_flippers.yaml | 20 ++++++++++++++ test/suite/hmg/flippers_wraps.yaml | 29 ++++++++++++++++++++ 5 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 test/suite/hmg/flippers_locked_flippers.yaml create mode 100644 test/suite/hmg/flippers_wraps.yaml diff --git a/Main.py b/Main.py index 99b257e2..ad817ec2 100644 --- a/Main.py +++ b/Main.py @@ -82,7 +82,6 @@ def main(args, seed=None, fish=None): for i in zip(args.logic.values(), args.door_shuffle.values()): if i[0] == 'hybridglitches' and i[1] != 'vanilla': raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) - # print(args) world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) diff --git a/README.md b/README.md index dc1d3f25..cac13120 100644 --- a/README.md +++ b/README.md @@ -621,13 +621,11 @@ Hybrid Major Glitches logic includes the following: * All Overworld Glitches logic * Kikiskip to access PoD wihtout MP or DW access * IP Lobby clip to skip fire requirement +* Traversal between TT -> Desert +* Traversal between Spec rock upper -> Spec rock mid +* Traversal between Paradox lower -> Paradox mid + upper * Traversal between Mire -> Hera -> Swamp * Stealing SK from Mire to open SP * Using the Mire big key to open Hera doors and big chest -* Traversal between TT -> Desert -* Traveral between Spec rock upper -> Spec rock mid -* Traveral between Paradox lower -> Paradox mid + upper - - - +All traversals mentioned are considered connectors in entrance shuffle diff --git a/Rules.py b/Rules.py index 03bcaba6..deb809a4 100644 --- a/Rules.py +++ b/Rules.py @@ -1907,12 +1907,6 @@ def set_bunny_rules(world, player, inverted): # Is it possible to do bunny pocket here def can_bunny_pocket_skull_woods(world, player): - # return world.get_entrance( - # "Skull Woods Second Section Door (West)", player - # ).connected_region.type != RegionType.Dungeon and ( - # not world.state.can_reach_from("Skull Woods Forest (West)", "Light World", 1) - # or not world.state.can_reach_from("Light World", "Skull Woods Forest (West)", 1) - # ) return world.get_entrance( "Skull Woods Second Section Door (West)", player ).connected_region.type == RegionType.Dungeon or ( @@ -1921,12 +1915,6 @@ def set_bunny_rules(world, player, inverted): ) def can_bunny_pocket_voo_shop(world, player): - # return world.get_entrance( - # "Dark World Shop", player - # ).connected_region.type != RegionType.Dungeon and ( - # not world.state.can_reach_from("West Dark World", "Light World", 1) - # or not world.state.can_reach_from("Light World", "West Dark World", 1) - # ) return ( world.state.can_reach_from("West Dark World", "Light World", 1) and world.state.can_reach_from("Light World", "West Dark World", 1) @@ -1963,13 +1951,6 @@ def set_bunny_rules(world, player, inverted): new_region = entrance.parent_region if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: continue - # We don't need to navigate through single entrance dungeons. HMG has more multi-entrance dungeons - # if (region.type == RegionType.Dungeon and new_region.type == RegionType.Dungeon): - # if ( - # world.logic[player] == 'hybridglitches' and new_region.dungeon != None and new_region.dungeon.name in hmg_single_exit_dungeons) or ( - # world.logic[player] not in ['hybridglitches', 'nologic'] and new_region.dungeon != None and new_region.dungeon.name in all_single_exit_dungeons - # ): - # continue new_path = path + [entrance.access_rule] new_seen = seen.union({new_region}) if not is_link(new_region): diff --git a/test/suite/hmg/flippers_locked_flippers.yaml b/test/suite/hmg/flippers_locked_flippers.yaml new file mode 100644 index 00000000..be71e07b --- /dev/null +++ b/test/suite/hmg/flippers_locked_flippers.yaml @@ -0,0 +1,20 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword +advanced_placements: + 1: + - type: Verification + item: Flippers + locations: + Zora's Ledge: True + Hobo: True + Ice Palace - Boss: True + Swamp Palace - Entrance: False + diff --git a/test/suite/hmg/flippers_wraps.yaml b/test/suite/hmg/flippers_wraps.yaml new file mode 100644 index 00000000..3418b4ec --- /dev/null +++ b/test/suite/hmg/flippers_wraps.yaml @@ -0,0 +1,29 @@ +meta: + players: 1 +settings: + 1: + logic: owglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword + - Flippers +placements: + 1: + Peg Cave: Magic Mirror +advanced_placements: + 1: + - type: Verification + item: Hammer + locations: + Link's House: True + Magic Bat: False +advanced_placements: + 1: + - type: Verification + item: Progressive Glove + locations: + Link's House: True + Ice Palace - Freezor Chest: False +