From c3b8347c600d724b377eb224cf67735fc1c0d015 Mon Sep 17 00:00:00 2001 From: Kris Davie Date: Sun, 5 Jan 2025 09:31:42 +0100 Subject: [PATCH 1/5] Make sure we can actually reach clips --- UnderworldGlitchRules.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 623a1dcc..036658eb 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -174,20 +174,23 @@ def dungeon_reentry_rules( def underworld_glitches_rules(world, player): def mire_clip(state): torches = world.get_region("Mire Torches Top", player) - return state.can_dash_clip(torches, player) or ( - state.can_bomb_clip(torches, player) and state.has_fire_source(player) + return state.can_reach(torches, player) and ( + state.can_dash_clip(torches, player) + or (state.can_bomb_clip(torches, player) and state.has_fire_source(player)) ) def hera_clip(state): hera = world.get_region("Hera 4F", player) - return state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player) + return state.can_reach(hera) and ( + state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player) + ) # We use these plus functool.partial because lambdas don't work in loops properly. def bomb_clip(state, region, player): - return state.can_bomb_clip(region, player) + return state.can_reach(region, player) and state.can_bomb_clip(region, player) def dash_clip(state, region, player): - return state.can_dash_clip(region, player) + return state.can_reach(region, player) and state.can_dash_clip(region, player) # Bomb clips for clip in ( kikiskip_spots From 757b2447651a8767e23a3d481fe3971c0dea3fa4 Mon Sep 17 00:00:00 2001 From: Kris Davie Date: Sun, 5 Jan 2025 12:40:18 +0100 Subject: [PATCH 2/5] Extra logic changes and early exits @codemann8 --- UnderworldGlitchRules.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 036658eb..5d8dd806 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -48,7 +48,6 @@ def create_hmg_entrances_regions(world, player): ip_clip_entrance = Entrance(player, "Ice Bomb Drop Clip", ip_bomb_top_reg) ip_bomb_top_reg.exits.append(ip_clip_entrance) - def connect_hmg_entrances_regions(world, player): for spots in [ kikiskip_spots, @@ -174,23 +173,21 @@ def dungeon_reentry_rules( def underworld_glitches_rules(world, player): def mire_clip(state): torches = world.get_region("Mire Torches Top", player) - return state.can_reach(torches, player) and ( - state.can_dash_clip(torches, player) + return (state.can_dash_clip(torches, player) or (state.can_bomb_clip(torches, player) and state.has_fire_source(player)) - ) + ) and state.can_reach(torches, player) def hera_clip(state): hera = world.get_region("Hera 4F", player) - return state.can_reach(hera) and ( - state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player) - ) + return (state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player)) \ + and state.has("Flippers", player) and state.can_reach(hera) and mire_clip(state) # We use these plus functool.partial because lambdas don't work in loops properly. def bomb_clip(state, region, player): - return state.can_reach(region, player) and state.can_bomb_clip(region, player) + return state.can_bomb_clip(region, player) and state.can_reach(region, player) def dash_clip(state, region, player): - return state.can_reach(region, player) and state.can_dash_clip(region, player) + return state.can_dash_clip(region, player) and state.can_reach(region, player) # Bomb clips for clip in ( kikiskip_spots @@ -237,18 +234,24 @@ def dash_clip(state, region, player): # Allow mire big key to be used in Hera Rules.add_rule( world.get_entrance("Hera Startile Corner NW", player), - lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + lambda state: state.has("Big Key (Misery Mire)", player) and mire_clip(state), combine="or", ) Rules.add_rule( world.get_location("Tower of Hera - Big Chest", player), - lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + lambda state: state.has("Big Key (Misery Mire)", player) and mire_clip(state), combine="or", ) # This uses the mire clip because it's always expected to come from mire Rules.set_rule( world.get_entrance("Hera to Swamp Clip", player), - lambda state: mire_clip(state) and state.has("Flippers", player), + lambda state: state.has("Flippers", player) and mire_clip(state), + ) + Rules.add_rule( + world.get_location("Swamp Palace - Big Chest", player), + lambda state: (state.has("Big Key (Misery Mire)", player) or state.has("Big Key (Tower of Hera)", player)) \ + and state.has("Flippers", player) and mire_clip(state), + combine="or", ) # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots # Flippers required for all of these doors to prevent locks when flooding @@ -267,9 +270,9 @@ def dash_clip(state, region, player): ]: Rules.add_rule( world.get_entrance(door, player), - lambda state: mire_clip(state) + lambda state: state.has("Flippers", player) and state.has("Small Key (Misery Mire)", player, count=6) - and state.has("Flippers", player), + and mire_clip(state), combine="or", ) @@ -297,16 +300,16 @@ def dash_clip(state, region, player): ) ), } - inverted = world.mode[player] == "inverted" + inverted_dm = world.mode[player] == "inverted" def hera_rule(state): - return (state.has("Moon Pearl", player) or not inverted) and rule_map.get( + return (state.has("Moon Pearl", player) or not inverted_dm) 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( + return (state.has("Moon Pearl", player) or inverted_dm) and rule_map.get( world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False, )(state) @@ -315,8 +318,8 @@ def mirrorless_moat_rule(state): return ( state.can_reach("Old Man S&Q", "Entrance", player) and state.has("Flippers", player) - and mire_clip(state) and (hera_rule(state) or gt_rule(state)) + and mire_clip(state) ) Rules.add_rule( From 5ea4c4066c763f8d6571f7f09b519dd019a13213 Mon Sep 17 00:00:00 2001 From: Kris Davie Date: Wed, 8 Jan 2025 19:18:40 +0100 Subject: [PATCH 3/5] Fixed issue with 2 extra SP keys in pool for HMG (codemann) --- Fill.py | 31 +++++++++++++++++++++++++++++++ ItemList.py | 10 ---------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Fill.py b/Fill.py index feacb10b..83f921f4 100644 --- a/Fill.py +++ b/Fill.py @@ -57,6 +57,37 @@ def fill(base_state, items, key_pool): fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) all_state_base = world.get_all_state() + for player in range(1, world.players + 1): + if world.logic[player] == 'hybridglitches' and world.keyshuffle[i.player] in ['none', 'nearby'] \ + and world.pottery[player] not in ['none', 'cave']: + # remove 2 keys from main pool + count_to_remove = 2 + to_remove = [] + for wix, wi in enumerate(smalls): + if wi.name == 'Small Key (Swamp Palace)' and wi.player == player: + to_remove.append(wix) + if count_to_remove == len(to_remove): + break + for wix in reversed(to_remove): + del smalls[wix] + # remove 2 swamp locations from pool + hybrid_locations = [] + to_remove = [] + for i, loc in enumerate(shuffled_locations): + if loc.name in ['Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Pot Row Pot Key'] and loc.player == player: + to_remove.append(i) + hybrid_locations.append(loc) + if count_to_remove == len(to_remove): + break + for i in reversed(to_remove): + shuffled_locations.pop(i) + # place 2 HMG keys + hybrid_state_base = all_state_base.copy() + for x in bigs + smalls + prizes + others: + hybrid_state_base.collect(x, True) + hybrid_smalls = [ItemFactory('Small Key (Swamp Palace)', player)] * 2 + fill(hybrid_state_base, hybrid_smalls, hybrid_locations, unplaced_smalls) + big_state_base = all_state_base.copy() for x in smalls + others: big_state_base.collect(x, True) diff --git a/ItemList.py b/ItemList.py index 3a74d106..31af7a4b 100644 --- a/ItemList.py +++ b/ItemList.py @@ -368,16 +368,6 @@ def generate_itempool(world, player): or (item.map and world.mapshuffle[player]) or (item.compass and world.compassshuffle[player]))]) - if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: - keys_to_remove = 2 - to_remove = [] - for wix, wi in enumerate(world.itempool): - if wi.name == 'Small Key (Swamp Palace)' and wi.player == player: - to_remove.append(wix) - if keys_to_remove == len(to_remove): - break - for wix in reversed(to_remove): - del world.itempool[wix] # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # rather than making all hearts/heart pieces progression items (which slows down generation considerably) From 3a8a4c956ade1345fb65ea2b0926723514852e35 Mon Sep 17 00:00:00 2001 From: Kris Davie Date: Wed, 8 Jan 2025 19:19:47 +0100 Subject: [PATCH 4/5] Fix HMG multiworld --- UnderworldGlitchRules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 5d8dd806..235dcce8 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -67,7 +67,7 @@ def connect_hmg_entrances_regions(world, player): connection.connect(target) # Add the new Ice path (back of bomb drop to front) to the world and model it properly - ip_clip_entrance = world.get_entrance('Ice Bomb Drop Clip', 1) + ip_clip_entrance = world.get_entrance('Ice Bomb Drop Clip', player) clip_door = Door(player, "Ice Bomb Drop Clip", DoorType.Logical, ip_clip_entrance) world.doors += [clip_door] world.initialize_doors([clip_door]) From 34859ffdfd14966970c0270d216dcffade31496e Mon Sep 17 00:00:00 2001 From: Kris Davie Date: Sun, 12 Jan 2025 22:20:54 +0100 Subject: [PATCH 5/5] Restore native dungeon item behavior for glitched modes. baserom handles dungeon IDs --- Rom.py | 6 +++--- source/dungeon/EnemyList.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Rom.py b/Rom.py index a3654c8c..345f8b3b 100644 --- a/Rom.py +++ b/Rom.py @@ -434,7 +434,9 @@ def patch_rom(world, rom, player, team, is_mystery=False): if location.item.name in valid_pot_items and location.item.player == player: location.pot.item = valid_pot_items[location.item.name] else: - code = handle_native_dungeon(location, itemid) + code = itemid + if world.pottery[player] == 'none' or location.locked: + code = handle_native_dungeon(location, itemid) standing_item_flag = 0x80 if location.item.player != player: standing_item_flag |= 0x40 @@ -452,8 +454,6 @@ def patch_rom(world, rom, player, team, is_mystery=False): if not location.crystal: if location.item is not None: - # Keys in their native dungeon should use the original item code for keys - itemid = handle_native_dungeon(location, itemid) if world.remote_items[player]: itemid = list(location_table.keys()).index(location.name) + 1 assert itemid < 0x100 diff --git a/source/dungeon/EnemyList.py b/source/dungeon/EnemyList.py index 8f168a8d..a23bf525 100644 --- a/source/dungeon/EnemyList.py +++ b/source/dungeon/EnemyList.py @@ -557,7 +557,9 @@ def sprite_data(self): item_id = self.location.item.code if self.location.item is not None else 0x5A code = 0xF9 if self.location.item.player != self.location.player else 0xF8 if code == 0xF8: - item_id = handle_native_dungeon(self.location, item_id) + world = self.location.parent_region.world + if world.dropshuffle[self.location.player] == 'none' or self.location.locked: + item_id = handle_native_dungeon(self.location, item_id) data.append(item_id) data.append(0 if code == 0xF8 else self.location.item.player) data.append(code)