From ac31671914702b4cc980dfa7c4c8a36cd2668aa5 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Mon, 7 Jun 2021 00:38:30 -0500 Subject: [PATCH 01/16] initial hybridmg logic file commit --- worlds/alttp/UnderworldGlitchRules.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 worlds/alttp/UnderworldGlitchRules.py diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py new file mode 100644 index 00000000..e69de29b From 16c6e17a49873e8d432567ddd7bcdd3636e6b83f Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Mon, 7 Jun 2021 01:19:27 -0500 Subject: [PATCH 02/16] Initial handling of hybrid glitch logic outside of UnderworldGlitchRules --- BaseClasses.py | 3 +++ Mystery.py | 2 +- Options.py | 2 ++ worlds/alttp/ItemPool.py | 2 +- worlds/alttp/Rules.py | 10 ++++++++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 0f5e5e6b..4c103b4d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -813,6 +813,9 @@ class CollectionState(object): rules.append(self.has('Moon Pearl', player)) return all(rules) + def can_bomb_clip(self, region: Region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) + # Minecraft logic functions def has_iron_ingots(self, player: int): return self.has('Progressive Tools', player) and self.has('Ingot Crafting', player) diff --git a/Mystery.py b/Mystery.py index a80872b8..8f26afdc 100644 --- a/Mystery.py +++ b/Mystery.py @@ -586,7 +586,7 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): logging.warning("Only NMG, OWG and No Logic supported") glitches_required = 'none' ret.logic = {None: 'noglitches', 'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches', - 'minor_glitches': 'minorglitches'}[ + 'minor_glitches': 'minorglitches', 'hybrid_major_glitches': 'hybridglitches'}[ glitches_required] ret.dark_room_logic = get_choice("dark_room_logic", weights, "lamp") diff --git a/Options.py b/Options.py index 3795b817..9582a3ea 100644 --- a/Options.py +++ b/Options.py @@ -146,8 +146,10 @@ class Logic(Choice): option_no_glitches = 0 option_minor_glitches = 1 option_overworld_glitches = 2 + option_hybrid_major_glitches = 3 option_no_logic = 4 alias_owg = 2 + alias_hmg = 3 class Objective(Choice): diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index d03b686b..a42598d1 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -571,7 +571,7 @@ def get_pool_core(world, player: int): return world.random.choice([True, False]) if progressive == 'random' else progressive == 'on' # provide boots to major glitch dependent seeds - if logic in {'owglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt': + if logic in {'owglitches', 'hybridglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt': precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.append('Rupees (20)') diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index faf37308..d2c9ac49 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -47,12 +47,17 @@ def set_rules(world, player): if world.logic[player] == 'noglitches': no_glitches_rules(world, player) - elif world.logic[player] in ['owglitches', 'nologic']: + elif world.logic[player] == 'owglitches': # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) + elif world.logic[player] in ['hybridglitches', 'nologic']: + no_glitches_rules(world, player) + fake_flipper_rules(world, player) + overworld_glitches_rules(world, player) + underworld_glitches_rules(world, player) elif world.logic[player] == 'minorglitches': no_glitches_rules(world, player) fake_flipper_rules(world, player) @@ -75,7 +80,8 @@ def set_rules(world, player): set_inverted_big_bomb_rules(world, player) # if swamp and dam have not been moved we require mirror for swamp palace - if not world.swamp_patch_required[player]: + # however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself. + if not world.swamp_patch_required[player] and world.logic[player] not in ['hybridglitches', 'nologic']: add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) # GT Entrance may be required for Turtle Rock for OWG and < 7 required From fae14ad2836789ec7e467c67169f5fff62de3d2e Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Mon, 7 Jun 2021 19:34:00 -0500 Subject: [PATCH 03/16] Mystery.py correctly recognizes HMG as an option --- Mystery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mystery.py b/Mystery.py index 8f26afdc..f250740c 100644 --- a/Mystery.py +++ b/Mystery.py @@ -582,8 +582,8 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): glitches_required = get_choice('glitches_required', weights) - if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']: - logging.warning("Only NMG, OWG and No Logic supported") + if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'hybrid_major_glitches', 'minor_glitches']: + logging.warning("Only NMG, OWG, HMG and No Logic supported") glitches_required = 'none' ret.logic = {None: 'noglitches', 'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches', 'minor_glitches': 'minorglitches', 'hybrid_major_glitches': 'hybridglitches'}[ From eb9ee9f41ea105e28a1a28f8776355e0fde1e3e5 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Mon, 7 Jun 2021 20:19:03 -0500 Subject: [PATCH 04/16] Hybrid Major Glitches connections and logic --- worlds/alttp/EntranceShuffle.py | 10 ++- worlds/alttp/Regions.py | 4 +- worlds/alttp/Rules.py | 1 + worlds/alttp/UnderworldGlitchRules.py | 113 ++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 3 deletions(-) diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index f26c59ab..87d9911d 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -1,6 +1,6 @@ # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. from collections import defaultdict - +from worlds.alttp.UnderworldGlitchRules import underworld_glitch_connections def link_entrances(world, player): connect_two_way(world, 'Links House', 'Links House Exit', player) # unshuffled. For now @@ -17,6 +17,10 @@ def link_entrances(world, player): for exitname, regionname in mandatory_connections: connect_simple(world, exitname, regionname, player) + # mandatory hybrid major glitches connections + if world.logic[player] in ['hybridglitches', 'nologic']: + underworld_glitch_connections(world, player) + # if we do not shuffle, set default connections if world.shuffle[player] == 'vanilla': for exitname, regionname in default_connections: @@ -1096,6 +1100,10 @@ def link_inverted_entrances(world, player): for exitname, regionname in inverted_mandatory_connections: connect_simple(world, exitname, regionname, player) + # mandatory hybrid major glitches connections + if world.logic[player] in ['hybridglitches', 'nologic']: + underworld_glitch_connections(world, player) + # if we do not shuffle, set default connections if world.shuffle[player] == 'vanilla': for exitname, regionname in inverted_default_connections: diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index 89a462ac..7373a05a 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -126,7 +126,7 @@ def create_regions(world, player): create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), create_lw_region(player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']), create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), - create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), + create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit', 'Kiki Skip']), create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), create_lw_region(player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']), create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'), @@ -250,7 +250,7 @@ def create_regions(world, player): create_dungeon_region(player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']), create_dungeon_region(player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'], ['Misery Mire (West)', 'Misery Mire Big Key Door']), - create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']), + create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest'], ['Mire to Hera Clip', 'Hera to Swamp Clip']), create_dungeon_region(player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']), create_dungeon_region(player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']), create_dungeon_region(player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']), diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index d2c9ac49..6adb0f58 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -4,6 +4,7 @@ from worlds.alttp import OverworldGlitchRules from BaseClasses import RegionType, MultiWorld, Entrance from worlds.alttp.Items import ItemFactory, progression_items, item_name_groups from worlds.alttp.OverworldGlitchRules import overworld_glitches_rules, no_logic_rules +from worlds.alttp.UnderworldGlitchRules import underworld_glitches_rules from worlds.alttp.Bosses import GanonDefeatRule from worlds.generic.Rules import set_rule, add_rule, forbid_item, add_item_rule, item_in_locations, \ item_name diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index e69de29b..ed07a34b 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -0,0 +1,113 @@ + +from BaseClasses import Entrance +from worlds.generic.Rules import set_rule, add_rule + +# We actually need the logic to properly "mark" these regions as Light or Dark world. +# Therefore we need to make these connections during the normal link_entrances stage, rather than during set_rules. +def underworld_glitch_connections(world, player): + kikiskip = world.get_entrance('Kiki Skip', player) + mire_to_hera = world.get_entrance('Mire to Hera Clip', player) + mire_to_swamp = world.get_entrance('Hera to Swamp Clip', player) + + if world.fix_fake_world[player]: + kikiskip.connect(world.get_entrance('Palace of Darkness Exit', player).connected_region) + mire_to_hera.connect(world.get_entrance('Tower of Hera Exit').connected_region) + mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit').connected_region) + else: + kikiskip.connect(world.get_region('Palace of Darkness (Entrance)', player)) + mire_to_hera.connect(world.get_region('Tower of Hera (Bottom)', player)) + mire_to_swamp.connect(world.get_region('Swamp Palace (Entrance)', player)) + + +def underworld_glitches_rules(world, player): + fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] + fix_fake_worlds = world.fix_fake_world[player] + + # Ice Palace Entrance Clip + # This is the easiest one since it's a simple internal clip. Just need to also add melting to freezor chest since it's otherwise assumed. + add_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.can_bomb_clip(world.get_region('Ice Palace (Entrance)', player), player), combine='or') + add_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.can_melt_things(player)) + + + + # Kiki Skip + kikiskip = world.get_entrance('Kiki Skip', player) + set_rule(kikiskip, lambda state: state.can_bomb_clip(kikiskip.parent_region, player)) + # Behavior differs based on what type of ER shuffle we're playing. + if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple (this should always have no FWF) + # Dungeons are only shuffled among themselves. We need to check SW and MM because they can't be reentered easily. + pod = world.get_region('Palace of Darkness (Entrance)', player) + if pod.entrances[0].name == 'Skull Woods Final Section': + set_rule(kikiskip, lambda state: False) + elif pod.entrances[0].name == 'Misery Mire': + add_rule(kikiskip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + + # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. + add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod.entrances[0].can_reach(state)) + elif not fix_fake_worlds: # full, dungeonsfull; has fixed exits but no FWF + # Entry requires the entrance's requirements, but you don't have logical access to the surrounding region. + pod = world.get_region('Palace of Darkness (Entrance)', player) + add_rule(kikiskip, pod.entrances[0].access_rule) + # exiting restriction + add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod.entrances[0].can_reach(state)) + + + + # Mire -> Hera -> Swamp + # Using mire keys on other dungeon doors + mire = world.get_region('Misery Mire (West)', player) + mire_clip = lambda state: state.can_reach('Misery Mire (West)', 'Region', player) and state.can_bomb_clip(mire, player) and state.has_fire_source(player) + hera_clip = lambda state: state.can_reach('Tower of Hera (Top)', 'Region', player) and state.can_bomb_clip(world.get_region('Tower of Hera (Top)', player), player) + add_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: mire_clip(state) and state.has('Big Key (Misery Mire)', player), combine='or') + add_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: mire_clip(state), combine='or') + add_rule(world.get_entrance('Swamp Palace (Center)', 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]: + mirrorless_moat_rules = [] + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + mirrorless_moat_rules.append(lambda state: state.can_reach('Old Man S&Q', 'Entrance', player) and mire_clip(state)) + rule_map = { + 'Misery Mire (Entrance)': (lambda state: True), + 'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player)) + } + inverted = world.mode[player] == 'inverted' + hera_rule = lambda state: (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) + gt_rule = lambda state: (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) + mirrorless_moat_rules.append(lambda state: hera_rule(state) or gt_rule(state)) + else: + mirrorless_moat_rules.append(lambda state: False) # all function returns True on empty list + + add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or all([rule(state) for rule in mirrorless_moat_rules])) + + # 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) + set_rule(mire_to_hera, mire_clip) + set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has('Flippers', player)) + if not fix_dungeon_exits: + hera = world.get_region('Tower of Hera (Bottom)', player) + if hera.entrances[0].name == 'Skull Woods Final Section': + set_rule(mire_to_hera, lambda state: False) + elif hera.entrances[0].name == 'Misery Mire': + add_rule(mire_to_hera, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera.entrances[0].can_reach(state)) + + swamp = world.get_region('Swamp Palace (Entrance)', player) + if swamp.entrances[0].name == 'Skull Woods Final Section': + set_rule(mire_to_swamp, lambda state: False) + elif swamp.entrances[0].name == 'Misery Mire': + add_rule(mire_to_swamp, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp.entrances[0].can_reach(state)) + elif not fix_fake_worlds: + hera = world.get_region('Tower of Hera (Bottom)', player) + add_rule(mire_to_hera, hera.entrances[0].access_rule) + add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera.entrances[0].can_reach(state)) + + swamp = world.get_region('Swamp Palace (Entrance)', player) + add_rule(mire_to_swamp, swamp.entrances[0].access_rule) + add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp.entrances[0].can_reach(state)) From b9a783d7d70c0eaa3d85eb462aafc78d158ff226 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 00:50:28 -0500 Subject: [PATCH 05/16] Fixed open connections breaking non-HMG seed generation --- worlds/alttp/Regions.py | 4 ++-- worlds/alttp/UnderworldGlitchRules.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index 7373a05a..89a462ac 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -126,7 +126,7 @@ def create_regions(world, player): create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), create_lw_region(player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']), create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), - create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit', 'Kiki Skip']), + create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), create_lw_region(player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']), create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'), @@ -250,7 +250,7 @@ def create_regions(world, player): create_dungeon_region(player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']), create_dungeon_region(player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'], ['Misery Mire (West)', 'Misery Mire Big Key Door']), - create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest'], ['Mire to Hera Clip', 'Hera to Swamp Clip']), + create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']), create_dungeon_region(player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']), create_dungeon_region(player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']), create_dungeon_region(player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']), diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index ed07a34b..b2ecb092 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -5,14 +5,19 @@ from worlds.generic.Rules import set_rule, add_rule # We actually need the logic to properly "mark" these regions as Light or Dark world. # Therefore we need to make these connections during the normal link_entrances stage, rather than during set_rules. def underworld_glitch_connections(world, player): - kikiskip = world.get_entrance('Kiki Skip', player) - mire_to_hera = world.get_entrance('Mire to Hera Clip', player) - mire_to_swamp = world.get_entrance('Hera to Swamp Clip', player) + specrock = world.get_region('Spectacle Rock Cave (Bottom)', player) + mire = world.get_region('Misery Mire (West)', player) + + kikiskip = Entrance(player, 'Kiki Skip', specrock) + mire_to_hera = Entrance(player, 'Mire to Hera Clip', mire) + mire_to_swamp = Entrance(player, 'Hera to Swamp Clip', mire) + specrock.exits.append(kikiskip) + mire.exits.extend([mire_to_hera, mire_to_swamp]) if world.fix_fake_world[player]: kikiskip.connect(world.get_entrance('Palace of Darkness Exit', player).connected_region) - mire_to_hera.connect(world.get_entrance('Tower of Hera Exit').connected_region) - mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit').connected_region) + mire_to_hera.connect(world.get_entrance('Tower of Hera Exit', player).connected_region) + mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit', player).connected_region) else: kikiskip.connect(world.get_region('Palace of Darkness (Entrance)', player)) mire_to_hera.connect(world.get_region('Tower of Hera (Bottom)', player)) From 2001ca6566d7ab0938f193199e824d0348754076 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 01:22:16 -0500 Subject: [PATCH 06/16] Fixed the check on dungeon reentry not working properly --- worlds/alttp/UnderworldGlitchRules.py | 38 +++++++++++++-------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index b2ecb092..37678ce8 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -38,23 +38,23 @@ def underworld_glitches_rules(world, player): # Kiki Skip kikiskip = world.get_entrance('Kiki Skip', player) set_rule(kikiskip, lambda state: state.can_bomb_clip(kikiskip.parent_region, player)) + pod_entrance = [r for r in world.get_region('Palace of Darkness (Entrance)', player).entrances if r.name != 'Kiki Skip'][0] # Behavior differs based on what type of ER shuffle we're playing. if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple (this should always have no FWF) # Dungeons are only shuffled among themselves. We need to check SW and MM because they can't be reentered easily. - pod = world.get_region('Palace of Darkness (Entrance)', player) - if pod.entrances[0].name == 'Skull Woods Final Section': + if pod_entrance.name == 'Skull Woods Final Section': set_rule(kikiskip, lambda state: False) - elif pod.entrances[0].name == 'Misery Mire': + elif pod_entrance.name == 'Misery Mire': add_rule(kikiskip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. - add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod.entrances[0].can_reach(state)) + add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) elif not fix_fake_worlds: # full, dungeonsfull; has fixed exits but no FWF # Entry requires the entrance's requirements, but you don't have logical access to the surrounding region. pod = world.get_region('Palace of Darkness (Entrance)', player) - add_rule(kikiskip, pod.entrances[0].access_rule) + add_rule(kikiskip, pod_entrance.access_rule) # exiting restriction - add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod.entrances[0].can_reach(state)) + add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) @@ -94,25 +94,23 @@ def underworld_glitches_rules(world, player): mire_to_swamp = world.get_entrance('Hera to Swamp Clip', player) set_rule(mire_to_hera, mire_clip) set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has('Flippers', player)) + hera_entrance = [r for r in world.get_region('Tower of Hera (Bottom)', player).entrances if r.name != 'Mire to Hera Clip'][0] + swamp_entrance = [r for r in world.get_region('Swamp Palace (Entrance)', player).entrances if r.name != 'Hera to Swamp Clip'][0] if not fix_dungeon_exits: - hera = world.get_region('Tower of Hera (Bottom)', player) - if hera.entrances[0].name == 'Skull Woods Final Section': + if hera_entrance.name == 'Skull Woods Final Section': set_rule(mire_to_hera, lambda state: False) - elif hera.entrances[0].name == 'Misery Mire': + elif hera_entrance.name == 'Misery Mire': add_rule(mire_to_hera, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) - add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera.entrances[0].can_reach(state)) + add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera_entrance.can_reach(state)) - swamp = world.get_region('Swamp Palace (Entrance)', player) - if swamp.entrances[0].name == 'Skull Woods Final Section': + if swamp_entrance.name == 'Skull Woods Final Section': set_rule(mire_to_swamp, lambda state: False) - elif swamp.entrances[0].name == 'Misery Mire': + elif swamp_entrance.name == 'Misery Mire': add_rule(mire_to_swamp, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) - add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp.entrances[0].can_reach(state)) + add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) elif not fix_fake_worlds: - hera = world.get_region('Tower of Hera (Bottom)', player) - add_rule(mire_to_hera, hera.entrances[0].access_rule) - add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera.entrances[0].can_reach(state)) + add_rule(mire_to_hera, hera_entrance.access_rule) + add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera_entrance.can_reach(state)) - swamp = world.get_region('Swamp Palace (Entrance)', player) - add_rule(mire_to_swamp, swamp.entrances[0].access_rule) - add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp.entrances[0].can_reach(state)) + add_rule(mire_to_swamp, swamp_entrance.access_rule) + add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) From a73189338c1a45eca713041e01927d9b79bf5688 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 18:15:47 -0500 Subject: [PATCH 07/16] Fixed full ER HMG not ignoring pearl requirements on entrances --- worlds/alttp/UnderworldGlitchRules.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 37678ce8..21a322d3 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -1,5 +1,6 @@ from BaseClasses import Entrance +from worlds.alttp.Items import ItemFactory from worlds.generic.Rules import set_rule, add_rule # We actually need the logic to properly "mark" these regions as Light or Dark world. @@ -24,6 +25,16 @@ def underworld_glitch_connections(world, player): mire_to_swamp.connect(world.get_region('Swamp Palace (Entrance)', 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 + + def underworld_glitches_rules(world, player): fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] fix_fake_worlds = world.fix_fake_world[player] @@ -41,7 +52,7 @@ def underworld_glitches_rules(world, player): pod_entrance = [r for r in world.get_region('Palace of Darkness (Entrance)', player).entrances if r.name != 'Kiki Skip'][0] # Behavior differs based on what type of ER shuffle we're playing. if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple (this should always have no FWF) - # Dungeons are only shuffled among themselves. We need to check SW and MM because they can't be reentered easily. + # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. if pod_entrance.name == 'Skull Woods Final Section': set_rule(kikiskip, lambda state: False) elif pod_entrance.name == 'Misery Mire': @@ -50,9 +61,8 @@ def underworld_glitches_rules(world, player): # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) elif not fix_fake_worlds: # full, dungeonsfull; has fixed exits but no FWF - # Entry requires the entrance's requirements, but you don't have logical access to the surrounding region. - pod = world.get_region('Palace of Darkness (Entrance)', player) - add_rule(kikiskip, pod_entrance.access_rule) + # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. + add_rule(kikiskip, lambda state: pod_entrance.access_rule(fake_pearl_state(state, player))) # exiting restriction add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) @@ -109,8 +119,8 @@ def underworld_glitches_rules(world, player): add_rule(mire_to_swamp, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) elif not fix_fake_worlds: - add_rule(mire_to_hera, hera_entrance.access_rule) + add_rule(mire_to_hera, lambda state: hera_entrance.access_rule(fake_pearl_state(state, player))) add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera_entrance.can_reach(state)) - add_rule(mire_to_swamp, swamp_entrance.access_rule) + add_rule(mire_to_swamp, lambda state: swamp_entrance.access_rule(fake_pearl_state(state, player))) add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) From e0d90e0b2135c3a9f6365bd874cf5e9fd3e65233 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 18:17:21 -0500 Subject: [PATCH 08/16] Properly accounting for agatower not freely opening for dungeon reentry --- worlds/alttp/UnderworldGlitchRules.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 21a322d3..d4fc2f35 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -57,6 +57,8 @@ def underworld_glitches_rules(world, player): set_rule(kikiskip, lambda state: False) elif pod_entrance.name == 'Misery Mire': add_rule(kikiskip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + elif pod_entrance.name == 'Agahnims Tower': + add_rule(kikiskip, 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. add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) @@ -111,12 +113,16 @@ def underworld_glitches_rules(world, player): set_rule(mire_to_hera, lambda state: False) elif hera_entrance.name == 'Misery Mire': add_rule(mire_to_hera, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + elif hera_entrance.name == 'Agahnims Tower': + add_rule(mire_to_hera, lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera_entrance.can_reach(state)) if swamp_entrance.name == 'Skull Woods Final Section': set_rule(mire_to_swamp, lambda state: False) elif swamp_entrance.name == 'Misery Mire': add_rule(mire_to_swamp, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + elif swamp_entrance.name == 'Agahnims Tower': + add_rule(mire_to_swamp, lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) elif not fix_fake_worlds: add_rule(mire_to_hera, lambda state: hera_entrance.access_rule(fake_pearl_state(state, player))) From a582a3781bf70d1c5e6779665131dbf3c376e82a Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 18:32:22 -0500 Subject: [PATCH 09/16] Moved the addition of HMG-specific connections to fix crossed ER --- worlds/alttp/EntranceShuffle.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index 87d9911d..64d45d17 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -17,10 +17,6 @@ def link_entrances(world, player): for exitname, regionname in mandatory_connections: connect_simple(world, exitname, regionname, player) - # mandatory hybrid major glitches connections - if world.logic[player] in ['hybridglitches', 'nologic']: - underworld_glitch_connections(world, player) - # if we do not shuffle, set default connections if world.shuffle[player] == 'vanilla': for exitname, regionname in default_connections: @@ -1070,6 +1066,10 @@ def link_entrances(world, player): raise NotImplementedError( f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_names(player)}') + # mandatory hybrid major glitches connections + if world.logic[player] in ['hybridglitches', 'nologic']: + underworld_glitch_connections(world, player) + # check for swamp palace fix if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': world.swamp_patch_required[player] = True @@ -1100,10 +1100,6 @@ def link_inverted_entrances(world, player): for exitname, regionname in inverted_mandatory_connections: connect_simple(world, exitname, regionname, player) - # mandatory hybrid major glitches connections - if world.logic[player] in ['hybridglitches', 'nologic']: - underworld_glitch_connections(world, player) - # if we do not shuffle, set default connections if world.shuffle[player] == 'vanilla': for exitname, regionname in inverted_default_connections: @@ -1775,6 +1771,10 @@ def link_inverted_entrances(world, player): else: raise NotImplementedError('Shuffling not supported yet') + # mandatory hybrid major glitches connections + if world.logic[player] in ['hybridglitches', 'nologic']: + underworld_glitch_connections(world, player) + # patch swamp drain if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': world.swamp_patch_required[player] = True From eaf19643a94c710c6b642ca4877eb210edc7e3d3 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 19:18:28 -0500 Subject: [PATCH 10/16] Cleaned up code for assigning dungeon reentry rules --- worlds/alttp/UnderworldGlitchRules.py | 73 +++++++++++---------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index d4fc2f35..e8d3eb00 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -35,6 +35,32 @@ def fake_pearl_state(state, player): 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. + if dungeon_entrance.name == 'Skull Woods Final Section': + set_rule(clip, lambda state: False) # entrance doesn't exist until you fire rod it from the other side + elif dungeon_entrance.name == 'Misery Mire': + add_rule(clip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) # open the dungeon + elif dungeon_entrance.name == 'Agahnims Tower': + add_rule(clip, lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) # kill/bypass barrier + # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. + 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. + add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) + # exiting restriction + add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + # Otherwise, the shuffle type is crossed, dungeonscrossed, 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): fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] fix_fake_worlds = world.fix_fake_world[player] @@ -45,29 +71,10 @@ def underworld_glitches_rules(world, player): add_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.can_melt_things(player)) - # Kiki Skip kikiskip = world.get_entrance('Kiki Skip', player) set_rule(kikiskip, lambda state: state.can_bomb_clip(kikiskip.parent_region, player)) - pod_entrance = [r for r in world.get_region('Palace of Darkness (Entrance)', player).entrances if r.name != 'Kiki Skip'][0] - # Behavior differs based on what type of ER shuffle we're playing. - if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple (this should always have no FWF) - # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. - if pod_entrance.name == 'Skull Woods Final Section': - set_rule(kikiskip, lambda state: False) - elif pod_entrance.name == 'Misery Mire': - add_rule(kikiskip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) - elif pod_entrance.name == 'Agahnims Tower': - add_rule(kikiskip, 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. - add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) - elif not fix_fake_worlds: # full, dungeonsfull; has fixed exits but no FWF - # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. - add_rule(kikiskip, lambda state: pod_entrance.access_rule(fake_pearl_state(state, player))) - # exiting restriction - add_rule(world.get_entrance('Palace of Darkness Exit', player), lambda state: pod_entrance.can_reach(state)) - + dungeon_reentry_rules(world, player, kikiskip, 'Palace of Darkness (Entrance)', 'Palace of Darkness Exit') # Mire -> Hera -> Swamp @@ -106,27 +113,5 @@ def underworld_glitches_rules(world, player): mire_to_swamp = world.get_entrance('Hera to Swamp Clip', player) set_rule(mire_to_hera, mire_clip) set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has('Flippers', player)) - hera_entrance = [r for r in world.get_region('Tower of Hera (Bottom)', player).entrances if r.name != 'Mire to Hera Clip'][0] - swamp_entrance = [r for r in world.get_region('Swamp Palace (Entrance)', player).entrances if r.name != 'Hera to Swamp Clip'][0] - if not fix_dungeon_exits: - if hera_entrance.name == 'Skull Woods Final Section': - set_rule(mire_to_hera, lambda state: False) - elif hera_entrance.name == 'Misery Mire': - add_rule(mire_to_hera, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) - elif hera_entrance.name == 'Agahnims Tower': - add_rule(mire_to_hera, lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) - add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera_entrance.can_reach(state)) - - if swamp_entrance.name == 'Skull Woods Final Section': - set_rule(mire_to_swamp, lambda state: False) - elif swamp_entrance.name == 'Misery Mire': - add_rule(mire_to_swamp, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) - elif swamp_entrance.name == 'Agahnims Tower': - add_rule(mire_to_swamp, lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) - add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) - elif not fix_fake_worlds: - add_rule(mire_to_hera, lambda state: hera_entrance.access_rule(fake_pearl_state(state, player))) - add_rule(world.get_entrance('Tower of Hera Exit', player), lambda state: hera_entrance.can_reach(state)) - - add_rule(mire_to_swamp, lambda state: swamp_entrance.access_rule(fake_pearl_state(state, player))) - add_rule(world.get_entrance('Swamp Palace Exit', player), lambda state: swamp_entrance.can_reach(state)) + dungeon_reentry_rules(world, player, mire_to_hera, 'Tower of Hera (Bottom)', 'Tower of Hera Exit') + dungeon_reentry_rules(world, player, mire_to_swamp, 'Swamp Palace (Entrance)', 'Swamp Palace Exit') From 671fd50cfbaaeb48180f4ac0e48b25cd43a2f5b7 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 19:19:11 -0500 Subject: [PATCH 11/16] Moved the add_rule for mirrorless swamp to speed it up on invalid entrance shuffle type --- worlds/alttp/UnderworldGlitchRules.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index e8d3eb00..27b40845 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -102,11 +102,9 @@ def underworld_glitches_rules(world, player): rule_map.get(world.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state) gt_rule = lambda state: (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) - mirrorless_moat_rules.append(lambda state: hera_rule(state) or gt_rule(state)) + add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or all([rule(state) for rule in mirrorless_moat_rules])) else: - mirrorless_moat_rules.append(lambda state: False) # all function returns True on empty list - - add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or all([rule(state) for rule in mirrorless_moat_rules])) + add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) # 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) From b3b56fcafd3d24205737fd34eb384c17409ebe40 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Tue, 8 Jun 2021 19:32:27 -0500 Subject: [PATCH 12/16] removed unnecessary import --- worlds/alttp/UnderworldGlitchRules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 27b40845..88c7474c 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -1,6 +1,5 @@ from BaseClasses import Entrance -from worlds.alttp.Items import ItemFactory from worlds.generic.Rules import set_rule, add_rule # We actually need the logic to properly "mark" these regions as Light or Dark world. From 96e13786cd086f74160dc8ac3be1389a947311aa Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Thu, 10 Jun 2021 18:10:25 -0500 Subject: [PATCH 13/16] Fixed broken mirrorless swamp rules --- worlds/alttp/UnderworldGlitchRules.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 88c7474c..f7e77367 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -89,9 +89,7 @@ def underworld_glitches_rules(world, player): # 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]: - mirrorless_moat_rules = [] if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: - mirrorless_moat_rules.append(lambda state: state.can_reach('Old Man S&Q', 'Entrance', player) and mire_clip(state)) rule_map = { 'Misery Mire (Entrance)': (lambda state: True), 'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player)) @@ -101,7 +99,8 @@ def underworld_glitches_rules(world, player): rule_map.get(world.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state) gt_rule = lambda state: (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) - add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or all([rule(state) for rule in mirrorless_moat_rules])) + mirrorless_moat_rule = lambda state: state.can_reach('Old Man S&Q', 'Entrance', player) and mire_clip(state) and (hera_rule(state) or gt_rule(state)) + add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or mirrorless_moat_rule(state)) else: add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) From d425e5eb6a4f9859e430524022cc7cfcc1767b4e Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Sat, 12 Jun 2021 13:11:14 -0500 Subject: [PATCH 14/16] disable GT junk fill in hybrid --- Fill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fill.py b/Fill.py index 7be4a105..8ecf4428 100644 --- a/Fill.py +++ b/Fill.py @@ -93,7 +93,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in world.alttp_player_ids: if not gftower_trash or not world.ganonstower_vanilla[player] or \ - world.logic[player] in {'owglitches', "nologic"}: + world.logic[player] in {'owglitches', 'hybridmg', "nologic"}: gtower_trash_count = 0 elif 'triforcehunt' in world.goal[player] and ('local' in world.goal[player] or world.players == 1): gtower_trash_count = world.random.randint(world.crystals_needed_for_gt[player] * 2, From deff3569104e9365e082f745829fce1ca0e55b84 Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Mon, 14 Jun 2021 22:10:26 -0500 Subject: [PATCH 15/16] Added HMG check to all checks for OWG and NL --- Fill.py | 2 +- worlds/alttp/Bosses.py | 4 ++-- worlds/alttp/EntranceRandomizer.py | 3 ++- worlds/alttp/EntranceShuffle.py | 2 +- worlds/alttp/Rules.py | 14 +++++++------- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Fill.py b/Fill.py index 8ecf4428..d51e2c90 100644 --- a/Fill.py +++ b/Fill.py @@ -93,7 +93,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in world.alttp_player_ids: if not gftower_trash or not world.ganonstower_vanilla[player] or \ - world.logic[player] in {'owglitches', 'hybridmg', "nologic"}: + world.logic[player] in {'owglitches', 'hybridglitches', "nologic"}: gtower_trash_count = 0 elif 'triforcehunt' in world.goal[player] and ('local' in world.goal[player] or world.players == 1): gtower_trash_count = world.random.randint(world.crystals_needed_for_gt[player] * 2, diff --git a/worlds/alttp/Bosses.py b/worlds/alttp/Bosses.py index 3e462a54..5ea7aba5 100644 --- a/worlds/alttp/Bosses.py +++ b/worlds/alttp/Bosses.py @@ -121,8 +121,8 @@ def GanonDefeatRule(state, player: int): can_hurt = state.has_beam_sword(player) common = can_hurt and state.has_fire_source(player) - # silverless ganon may be needed in minor glitches - if state.world.logic[player] in {"owglitches", "minorglitches", "none"}: + # silverless ganon may be needed in anything higher than no glitches + if state.world.logic[player] != 'noglitches': # need to light torch a sufficient amount of times return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or ( state.has('Silver Bow', player) and state.can_shoot_arrows(player)) or diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 0a62a2c3..d4c7886b 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -21,13 +21,14 @@ def parse_arguments(argv, no_defaults=False): parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') - parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'nologic'], + parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'hybridglitches', 'nologic'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) No Glitches: Minor Glitches: May require Fake Flippers, Bunny Revival and Dark Room Navigation. Overworld Glitches: May require overworld glitches. + Hybrid Major Glitches: May require both overworld and underworld clipping. No Logic: Distribute items without regard for item requirements. ''') diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index 64d45d17..62e7dd45 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -1949,7 +1949,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']: from worlds.alttp import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): invalid_connections[entrance] = set() diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 6adb0f58..301df4aa 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -74,7 +74,7 @@ def set_rules(world, player): if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) - if world.logic[player] in {'owglitches', 'nologic'} and world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}: + if world.logic[player] in {'owglitches', 'hybridglitches', 'nologic'} and world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}: 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: @@ -87,13 +87,13 @@ def set_rules(world, player): # GT Entrance may be required for Turtle Rock for OWG and < 7 required ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.mode[player] == 'inverted' else 'Ganons Tower', player) - if world.crystals_needed_for_gt[player] == 7 and not (world.logic[player] in ['owglitches', 'nologic'] and world.mode[player] != 'inverted'): + if world.crystals_needed_for_gt[player] == 7 and not (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and world.mode[player] != 'inverted'): set_rule(ganons_tower, lambda state: False) set_trock_key_rules(world, player) set_rule(ganons_tower, lambda state: state.has_crystals(state.world.crystals_needed_for_gt[player], player)) - if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'nologic']: + if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or') set_bunny_rules(world, player, world.mode[player] == 'inverted') @@ -1394,7 +1394,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): 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] in ['minorglitches', 'owglitches', 'nologic']: + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic']: if region.name == 'Swamp Palace (Entrance)': # Need to 0hp revive - not in logic return lambda state: state.has('Moon Pearl', player) if region.name == 'Tower of Hera (Bottom)': # Need to hit the crystal switch @@ -1434,7 +1434,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): seen.add(new_region) if not is_link(new_region): # For glitch rulesets, establish superbunny and revival rules. - if world.logic[player] in ['minorglitches', 'owglitches', 'nologic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): possible_options.append(lambda state: path_to_access_rule(new_path, entrance) and state.has('Magic Mirror', player) and state.has_sword(player)) elif (region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions() @@ -1472,7 +1472,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): # Add requirements for all locations that are actually in the dark world, except those available to the bunny, including dungeon revival for entrance in world.get_entrances(): if entrance.player == player and is_bunny(entrance.connected_region): - if world.logic[player] in ['minorglitches', 'owglitches', 'nologic'] : + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] : if entrance.connected_region.type == RegionType.Dungeon: if entrance.parent_region.type != RegionType.Dungeon and entrance.connected_region.name in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): add_rule(entrance, get_rule_to_add(entrance.connected_region, None, entrance)) @@ -1480,7 +1480,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): if entrance.connected_region.name == 'Turtle Rock (Entrance)': add_rule(world.get_entrance('Turtle Rock Entrance Gap', player), get_rule_to_add(entrance.connected_region, None, entrance)) for location in entrance.connected_region.locations: - if world.logic[player] in ['minorglitches', 'owglitches', 'nologic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): continue if location.name in bunny_accessible_locations: continue From b51b094cc189264760c0907b211256fd027569de Mon Sep 17 00:00:00 2001 From: espeon65536 Date: Fri, 18 Jun 2021 23:45:03 -0500 Subject: [PATCH 16/16] Added HMG to playerSettings --- playerSettings.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/playerSettings.yaml b/playerSettings.yaml index 4912d84e..30789abc 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -119,6 +119,7 @@ glitches_required: # Determine the logic required to complete the seed none: 50 # No glitches required minor_glitches: 0 # Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic overworld_glitches: 0 # Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches + hybrid_major_glitches: 0 # In addition to overworld glitches, also requires underworld clips between dungeons. no_logic: 0 # Your own items are placed with no regard to any logic; such as your Fire Rod can be on your Trinexx. # Other players items are placed into your world under OWG logic dark_room_logic: # Logic for unlit dark rooms