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