From 831ce9ec6aef1edd1dbbddc279f1d5176547adc4 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sat, 12 Oct 2024 16:37:22 +0200 Subject: [PATCH 1/7] Fix world copy in HMG/NL --- Main.py | 6 +++++- UnderworldGlitchRules.py | 41 +++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Main.py b/Main.py index af8ba0ab..bd249668 100644 --- a/Main.py +++ b/Main.py @@ -27,7 +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 UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors, get_hybridmajor_connector_entrances from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config, verify_item_pool_config @@ -557,6 +557,8 @@ def copy_world(world): # connect copied world copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations + hmg_entrances = get_hybridmajor_connector_entrances() + for region in world.regions: copied_region = ret.get_region(region.name, region.player) copied_region.is_light_world = region.is_light_world @@ -566,6 +568,8 @@ def copy_world(world): for location in copied_region.locations: location.parent_region = copied_region for entrance in region.entrances: + if entrance.name in hmg_entrances: + continue ret.get_entrance(entrance.name, entrance.player).connect(copied_region) # fill locations diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 67593631..4544b2ed 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -6,12 +6,11 @@ kikiskip_spots = [ ("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal") ] -mireheraswamp_spots = [ - ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal"), - ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal"), -] +mirehera_spots = [("Mire to Hera Clip", "Mire Torches Top", "Hera Portal")] -icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop")] +heraswamp_spots = [("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal")] + +icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop - Top")] thievesdesert_spots = [ ("Thieves to Desert West Clip", "Thieves Attic", "Desert West Portal"), @@ -32,14 +31,12 @@ paradox_spots = [ kikiskip_connectors = [ ("Kiki Skip Connector", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit") ] - - -mireheraswamp_connectors = [ - ("Mire to Hera Connector", "Mire Torches Top", "Tower of Hera Exit"), - ("Mire to Swamp Connector", "Mire Torches Top", "Swamp Palace Exit"), +mirehera_connectors = [ + ("Mire to Hera Connector", "Mire Torches Top", "Tower of Hera Exit") +] +heraswamp_connectors = [ + ("Mire to Swamp Connector", "Mire Torches Top", "Swamp Palace Exit") ] - - thievesdesert_connectors = [ ("Thieves to Desert West Connector", "Thieves Attic", "Desert Palace Exit (West)"), ( @@ -49,7 +46,6 @@ thievesdesert_connectors = [ ), ("Thieves to Desert East Connector", "Thieves Attic", "Desert Palace Exit (East)"), ] - specrock_connectors = [ ( "Spec Rock Top Connector", @@ -68,7 +64,8 @@ specrock_connectors = [ def create_hybridmajor_connections(world, player): for spots in [ kikiskip_spots, - mireheraswamp_spots, + mirehera_spots, + heraswamp_spots, icepalace_spots, thievesdesert_spots, specrock_spots, @@ -81,7 +78,8 @@ def create_hybridmajor_connections(world, player): def create_hybridmajor_connectors(world, player): for connectors in [ kikiskip_connectors, - mireheraswamp_connectors, + mirehera_connectors, + heraswamp_connectors, thievesdesert_connectors, specrock_connectors, ]: @@ -96,6 +94,19 @@ def create_hybridmajor_connectors(world, player): create_no_logic_connections(player, world, new_connectors) +def get_hybridmajor_connector_entrances(): + connectors = [] + for connector in ( + kikiskip_connectors + + mirehera_connectors + + heraswamp_connectors + + thievesdesert_connectors + + specrock_connectors + ): + connectors.append(connector[0]) + return set(connectors) + + # 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): From b0ca154a92ded90dff2732ead012f91c3a2a6fdf Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sat, 12 Oct 2024 16:38:27 +0200 Subject: [PATCH 2/7] Actually check if we can leave IP bomb drop --- UnderworldGlitchRules.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 4544b2ed..a03797b0 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -1,6 +1,8 @@ -from BaseClasses import Entrance +from BaseClasses import Entrance, DoorType +from DoorShuffle import connect_simple_door import Rules from OverworldGlitchRules import create_no_logic_connections +from Doors import create_door kikiskip_spots = [ ("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal") @@ -73,6 +75,17 @@ def create_hybridmajor_connections(world, player): ]: create_no_logic_connections(player, world, spots) + # Add the new Ice path (back of bomb drop to front) to the world and model it properly + clip_door = create_door(player, "Ice Bomb Drop Clip", DoorType.Logical) + world.doors += [clip_door] + world.initialize_doors([clip_door]) + + ice_bomb_top_reg = world.get_region("Ice Bomb Drop - Top", player) + ice_bomb_top_reg.exits.append( + Entrance(player, "Ice Bomb Drop Clip", ice_bomb_top_reg) + ) + connect_simple_door(world, "Ice Bomb Drop Clip", "Ice Bomb Drop", player) + # Turn dungeons into connectors def create_hybridmajor_connectors(world, player): From dc21885e1baa33568e8871be0bfba836693f3edb Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sat, 12 Oct 2024 16:42:14 +0200 Subject: [PATCH 3/7] HMG PoD Region fix + reformatting * Create a region in HMG when PoD entrance isn't pre-opened that limits access to the overworld unless already available * Change rule generations to loop * Check for bomb and dash clips properly --- OverworldShuffle.py | 3 + Regions.py | 15 +++- UnderworldGlitchRules.py | 164 +++++++++++++++++++++++++++------------ 3 files changed, 130 insertions(+), 52 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 303aeed2..768588d3 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -15,6 +15,9 @@ def link_overworld(world, player): for exitname, regionname in inverted_mandatory_connections: connect_simple(world, exitname, regionname, player) + if world.logic[player] in ['hybridglitches', 'nologic'] and not world.fix_palaceofdarkness_exit[player]: + connect_two_way(world, 'Palace of Darkness HMG Exit', 'Palace of Darkness HMG Entrance', player) + for forward_edge, back_edge in default_connections: connect_two_way(world, forward_edge, back_edge, player) diff --git a/Regions.py b/Regions.py index 6cb44847..e82dd74b 100644 --- a/Regions.py +++ b/Regions.py @@ -191,8 +191,19 @@ def create_regions(world, player): create_dw_region(player, 'Broken Bridge Area', None, ['Broken Bridge Hammer Rock (South)', 'Broken Bridge Water Drop', 'Broken Bridge SW']), create_dw_region(player, 'Broken Bridge Northeast', None, ['Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', 'Broken Bridge Northeast Water Drop', 'Broken Bridge NE']), create_dw_region(player, 'Broken Bridge West', None, ['Broken Bridge West Water Drop', 'Broken Bridge NW']), - create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], 'Dark World', Terrain.Water), - create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Palace of Darkness SW', 'Palace of Darkness SE']), + create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], 'Dark World', Terrain.Water) + ] + if world.logic[player] in ['hybridglitches', 'nologic'] and not world.fix_palaceofdarkness_exit[player]: + world.regions += [ + create_dw_region(player, 'Palace of Darkness HMG Area', None, ['Palace of Darkness', 'Palace of Darkness HMG Exit']), + create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness SW', 'Palace of Darkness SE', 'Palace of Darkness HMG Entrance']) + ] + else: + world.regions += [ + create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Palace of Darkness SW', 'Palace of Darkness SE']) + ] + + world.regions += [ create_dw_region(player, 'Hammer Pegs Area', ['Dark Blacksmith Ruins'], ['Hammer Peg Cave', 'Peg Area Rocks (East)']), create_dw_region(player, 'Hammer Pegs Entry', None, ['Peg Area Rocks (West)', 'Hammer Pegs WS']), create_dw_region(player, 'Dark Dunes Area', None, ['Dark Dunes NW', 'Dark Dunes WN', 'Dark Dunes SC']), diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index a03797b0..0f4389df 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -1,3 +1,4 @@ +import functools from BaseClasses import Entrance, DoorType from DoorShuffle import connect_simple_door import Rules @@ -133,19 +134,45 @@ def fake_pearl_state(state, player): # 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 + world, + player, + clip: Entrance, + dungeon_region: str, ): fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] fix_fake_worlds = world.fix_fake_world[player] + all_clips = [ + x[0] + for x in kikiskip_spots + + mirehera_spots + + heraswamp_spots + + icepalace_spots + + thievesdesert_spots + + specrock_spots + + paradox_spots + + kikiskip_connectors + + mirehera_connectors + + heraswamp_connectors + + thievesdesert_connectors + + specrock_connectors + ] + dungeon_entrance = [ r for r in world.get_region(dungeon_region, player).entrances - if r.name != clip.name + if r.name not in all_clips ][0] - if ( - not fix_dungeon_exits - ): # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix + + dungeon_exit = [ + r + for r in world.get_region(dungeon_region, player).exits + if r.name not in all_clips + ][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 @@ -196,32 +223,85 @@ def dungeon_reentry_rules( def underworld_glitches_rules(world, player): - # Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room - Rules.add_rule( - world.get_entrance("Ice Bomb Drop SE", player), - lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) - and (state.can_use_bombs(player) or state.has("Cane of Somaria", player)), - combine="or", - ) - - # 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_dash_clip(world.get_region("Mire Torches Top", player), player) - - def hera_clip(state): - return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip( - world.get_region("Hera 4F", player), player + 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) ) + 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) + + # 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) + + def dash_clip(state, region, player): + return state.can_dash_clip(region, player) + + if not world.fix_palaceofdarkness_exit[player]: + Rules.set_rule( + world.get_entrance("Palace of Darkness HMG Exit", player), + lambda state: state.can_reach(world.get_entrance("Palace of Darkness", player), player), + ) + + + # Bomb clips + for clip in ( + kikiskip_spots + + icepalace_spots + + thievesdesert_spots + + specrock_spots + + kikiskip_connectors + + thievesdesert_connectors + + specrock_connectors + ): + region = world.get_region(clip[1], player) + Rules.set_rule( + world.get_entrance(clip[0], player), + functools.partial(bomb_clip, region=region, player=player), + ) + # Dash clips + for clip in icepalace_spots: + region = world.get_region(clip[1], player) + Rules.add_rule( + world.get_entrance(clip[0], player), + functools.partial(dash_clip, region=region, player=player), + combine="or", + ) + + for spot in kikiskip_spots + thievesdesert_spots: + dungeon_reentry_rules( + world, + player, + world.get_entrance(spot[0], player), + spot[2], + ) + + for connector in kikiskip_connectors + thievesdesert_connectors: + dungeon_reentry_rules( + world, + player, + world.get_entrance(connector[0], player), + world.get_entrance(connector[2], player).parent_region.name, + ) + + for clip in mirehera_spots + mirehera_connectors: + Rules.set_rule( + world.get_entrance(clip[0], player), + lambda state: mire_clip(state), + ) + + # Need to be able to escape by hitting the switch from the back + Rules.set_rule( + world.get_entrance("Ice Bomb Drop Clip", player), + lambda state: ( + state.can_use_bombs(player) or state.has("Cane of Somaria", 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), @@ -232,20 +312,10 @@ def underworld_glitches_rules(world, player): lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), combine="or", ) - - mire_to_hera = world.get_entrance("Mire to Hera Clip", player) - mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) - Rules.set_rule(mire_to_hera, mire_clip) + # This uses the mire clip because it's always expected to come from mire Rules.set_rule( - mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player) - ) - - # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys - dungeon_reentry_rules( - world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit" - ) - dungeon_reentry_rules( - world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit" + world.get_entrance("Hera to Swamp Clip", player), + lambda state: mire_clip(state) and state.has("Flippers", player), ) # 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 @@ -269,7 +339,6 @@ def underworld_glitches_rules(world, player): and state.has("Flippers", player), combine="or", ) - # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") Rules.add_rule( world.get_location("Trench 1 Switch", player), @@ -322,21 +391,16 @@ def underworld_glitches_rules(world, player): lambda state: mirrorless_moat_rule(state), combine="or", ) + desert_exits = ["West", "South", "East"] - for desert_exit in ["East", "South", "West"]: + for desert_exit in desert_exits: Rules.add_rule( world.get_entrance(f"Thieves to Desert {desert_exit} Connector", player), lambda state: state.can_dash_clip( world.get_region("Thieves Attic", player), player ), ) - dungeon_reentry_rules( - world, - player, - world.get_entrance(f"Thieves to Desert {desert_exit} Connector", player), - f"Desert {desert_exit} Portal", - f"Desert Palace Exit ({desert_exit})", - ) + # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up paradox_left_chests = [ From 7575c3de8ad2a777434ad5de37caeef422b44815 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Sat, 12 Oct 2024 16:43:09 +0200 Subject: [PATCH 4/7] Tests update --- test/suite/hmg/flippers_wraps.yaml | 2 - .../hmg/inverted_inaccessible_desert.yaml | 1 - test/suite/hmg/inverted_moon_pearl_locs.yaml | 3 ++ test/suite/hmg/moon_pearl_locs.yaml | 4 +- test/suite/hmg/no_east_dw_from_kikiskip.yaml | 49 +++++++++++++++++++ test/suite/hmg/pearlless_sw.yaml | 8 +++ 6 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 test/suite/hmg/no_east_dw_from_kikiskip.yaml diff --git a/test/suite/hmg/flippers_wraps.yaml b/test/suite/hmg/flippers_wraps.yaml index 3418b4ec..9971a38e 100644 --- a/test/suite/hmg/flippers_wraps.yaml +++ b/test/suite/hmg/flippers_wraps.yaml @@ -19,8 +19,6 @@ advanced_placements: locations: Link's House: True Magic Bat: False -advanced_placements: - 1: - type: Verification item: Progressive Glove locations: diff --git a/test/suite/hmg/inverted_inaccessible_desert.yaml b/test/suite/hmg/inverted_inaccessible_desert.yaml index bb8ad8aa..5e59ca63 100644 --- a/test/suite/hmg/inverted_inaccessible_desert.yaml +++ b/test/suite/hmg/inverted_inaccessible_desert.yaml @@ -5,7 +5,6 @@ settings: logic: hybridglitches mode: inverted shuffle: crossed - start_inventory: 1: - Pegasus Boots diff --git a/test/suite/hmg/inverted_moon_pearl_locs.yaml b/test/suite/hmg/inverted_moon_pearl_locs.yaml index 9ae1a9e1..23ec40fe 100644 --- a/test/suite/hmg/inverted_moon_pearl_locs.yaml +++ b/test/suite/hmg/inverted_moon_pearl_locs.yaml @@ -15,6 +15,9 @@ start_inventory: - Ether - Quake - Bombos + - Big Key (Tower of Hera) + - Big Key (Desert Palace) + - Big Key (Eastern Palace) advanced_placements: 1: - type: Verification diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml index a18d53d1..13a37f92 100644 --- a/test/suite/hmg/moon_pearl_locs.yaml +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -3,6 +3,7 @@ meta: settings: 1: logic: hybridglitches + shuffle: vanilla start_inventory: 1: - Pegasus Boots @@ -10,9 +11,8 @@ start_inventory: - Fire Rod - Book of Mudora - Progressive Sword - - Progressive Sword - Lamp - - Magic Mirror + - Hammer - Ether - Quake - Bombos diff --git a/test/suite/hmg/no_east_dw_from_kikiskip.yaml b/test/suite/hmg/no_east_dw_from_kikiskip.yaml new file mode 100644 index 00000000..af5ac2ce --- /dev/null +++ b/test/suite/hmg/no_east_dw_from_kikiskip.yaml @@ -0,0 +1,49 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + shuffle: vanilla +start_inventory: + 1: + - Pegasus Boots + - Flippers + - Fire Rod + - Book of Mudora + - Progressive Sword + - Lamp + - Hammer + - Ether + - Quake + - Bombos + - Bombs (10) +placements: + 1: + # Put mirror in PF so we can't DMD + Pyramid Fairy - Right: Magic Mirror + # Lock all swords and cape behind pearl so we can't do aga 1 for pyramid access + Thieves' Town - Big Chest: Progressive Sword + Thieves' Town - Attic: Progressive Sword + Thieves' Town - Blind's Cell: Progressive Sword + Thieves' Town - Map Chest: Cape +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Palace of Darkness - Shooter Room: True + Palace of Darkness - The Arena - Bridge: True + Palace of Darkness - Stalfos Basement: True + Palace of Darkness - Big Key Chest: True + Palace of Darkness - The Arena - Ledge: True + Palace of Darkness - Map Chest: True + Palace of Darkness - Compass Chest: True + Palace of Darkness - Dark Basement - Left: True + Palace of Darkness - Dark Basement - Right: True + Palace of Darkness - Dark Maze - Top: True + Palace of Darkness - Dark Maze - Bottom: True + Palace of Darkness - Big Chest: True + Palace of Darkness - Harmless Hellway: True + Palace of Darkness - Boss: True + Pyramid Fairy - Left: False + Pyramid: False diff --git a/test/suite/hmg/pearlless_sw.yaml b/test/suite/hmg/pearlless_sw.yaml index c26ce1d9..3b097028 100644 --- a/test/suite/hmg/pearlless_sw.yaml +++ b/test/suite/hmg/pearlless_sw.yaml @@ -9,6 +9,7 @@ start_inventory: - Flippers - Pegasus Boots - Progressive Sword + - Progressive Sword - Hammer - Progressive Glove - Progressive Glove @@ -17,9 +18,16 @@ start_inventory: - Bottle - Magic Mirror - Lamp + - Beat Agahnim 1 + - Cane of Somaria + - Hookshot + - Quake + - Ether + - Bombos advanced_placements: 1: - type: Verification item: Moon Pearl locations: Skull Woods - Bridge Room: True + Skull Woods - Boss: True From 0d5872ef74423ac7ab54cfc7ba13ad1339ad167d Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Thu, 12 Dec 2024 20:44:26 +0100 Subject: [PATCH 5/7] Don't ever check playthough when all seeds are no logic --- Main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Main.py b/Main.py index bd249668..4d806e96 100644 --- a/Main.py +++ b/Main.py @@ -426,6 +426,10 @@ def main(args, seed=None, fish=None): if 'debug' in world.spoiler.settings: world.spoiler.extras(output_path(f'{outfilebase}_Spoiler.txt')) + player_logics = set(world.logic.values()) + if len(player_logics) == 1 and 'nologic' in player_logics: + args.skip_playthrough = True + if not args.skip_playthrough: logger.info(world.fish.translate("cli","cli","calc.playthrough")) create_playthrough(world) From 8b73fee1f235b1edc0e0ebbd99af4072493651b0 Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Thu, 12 Dec 2024 20:56:59 +0100 Subject: [PATCH 6/7] SP Bosstest for MP --- test/suite/hmg/moon_pearl_locs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml index 13a37f92..b2d418b1 100644 --- a/test/suite/hmg/moon_pearl_locs.yaml +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -41,6 +41,7 @@ advanced_placements: C-Shaped House: True Pyramid Fairy - Left: True Swamp Palace - Entrance: False + Swamp Palace - Boss: False Thieves' Town - Map Chest: False From af1892c7d51e485591de34bc583e6489ef2ce71c Mon Sep 17 00:00:00 2001 From: KrisDavie Date: Mon, 16 Dec 2024 00:06:54 +0100 Subject: [PATCH 7/7] Revamp HMG logic --- Main.py | 32 +++-- OverworldGlitchRules.py | 14 ++- OverworldShuffle.py | 3 - Regions.py | 15 +-- UnderworldGlitchRules.py | 111 +++--------------- .../hmg/inverted_inaccessible_desert.yaml | 4 + test/suite/hmg/pod_mp.yaml | 13 ++ 7 files changed, 71 insertions(+), 121 deletions(-) create mode 100644 test/suite/hmg/pod_mp.yaml diff --git a/Main.py b/Main.py index 4d806e96..fcb50142 100644 --- a/Main.py +++ b/Main.py @@ -27,7 +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, get_hybridmajor_connector_entrances +from UnderworldGlitchRules import create_hybridmajor_connections, get_hybridmajor_connection_entrances from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config, verify_item_pool_config @@ -222,8 +222,6 @@ 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(): @@ -264,8 +262,6 @@ def main(args, seed=None, fish=None): link_overworld(world, player) create_dynamic_exits(world, player) link_entrances_new(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): @@ -285,6 +281,8 @@ def main(args, seed=None, fish=None): logger.info(world.fish.translate("cli", "cli", "generating.itempool")) for player in range(1, world.players + 1): + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(world, player) generate_itempool(world, player) verify_item_pool_config(world) @@ -533,8 +531,6 @@ def copy_world(world): create_dungeons(ret, player) 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 @@ -561,7 +557,10 @@ def copy_world(world): # connect copied world copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations - hmg_entrances = get_hybridmajor_connector_entrances() + + # We have to skip these for now. They require both the rest of the entrances _and_ the dungeon portals to be copied first + # We will connect them later + hmg_entrances = get_hybridmajor_connection_entrances() for region in world.regions: copied_region = ret.get_region(region.name, region.player) @@ -621,7 +620,22 @@ def copy_world(world): for player in range(1, world.players + 1): if world.logic[player] in ('nologic', 'hybridglitches'): - create_hybridmajor_connectors(ret, player) + create_hybridmajor_connections(ret, player) + + for region in world.regions: + copied_region = ret.get_region(region.name, region.player) + copied_region.is_light_world = region.is_light_world + copied_region.is_dark_world = region.is_dark_world + copied_region.dungeon = region.dungeon + copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations] + for location in copied_region.locations: + location.parent_region = copied_region + for entrance in region.entrances: + if entrance.name not in hmg_entrances: + continue + ret.get_entrance(entrance.name, entrance.player).connect(copied_region) + + for player in range(1, world.players + 1): set_rules(ret, player) return ret diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 97883dc6..55e9d896 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -2,7 +2,7 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. """ -from BaseClasses import Entrance +from BaseClasses import Entrance, Region # Cave regions that superbunny can get through - but only with a sword. sword_required_superbunny_mirror_regions = ["Spiral Cave (Top)"] @@ -283,10 +283,18 @@ def add_alternate_rule(entrance, rule): entrance.access_rule = lambda state: old_rule(state) or rule(state) -def create_no_logic_connections(player, world, connections): +def create_no_logic_connections(player, world, connections, connect_external=False): for entrance, parent_region, target_region, *rule_override in connections: parent = world.get_region(parent_region, player) - target = world.get_region(target_region, player) + + if isinstance(target_region, Region): + target_region = target_region.name + + if connect_external and target_region.endswith(" Portal"): + target = world.get_portal(target_region[:-7], player).find_portal_entrance().parent_region + else: + target = world.get_region(target_region, player) + connection = Entrance(player, entrance, parent) parent.exits.append(connection) connection.connect(target) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 768588d3..303aeed2 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -15,9 +15,6 @@ def link_overworld(world, player): for exitname, regionname in inverted_mandatory_connections: connect_simple(world, exitname, regionname, player) - if world.logic[player] in ['hybridglitches', 'nologic'] and not world.fix_palaceofdarkness_exit[player]: - connect_two_way(world, 'Palace of Darkness HMG Exit', 'Palace of Darkness HMG Entrance', player) - for forward_edge, back_edge in default_connections: connect_two_way(world, forward_edge, back_edge, player) diff --git a/Regions.py b/Regions.py index e82dd74b..6cb44847 100644 --- a/Regions.py +++ b/Regions.py @@ -191,19 +191,8 @@ def create_regions(world, player): create_dw_region(player, 'Broken Bridge Area', None, ['Broken Bridge Hammer Rock (South)', 'Broken Bridge Water Drop', 'Broken Bridge SW']), create_dw_region(player, 'Broken Bridge Northeast', None, ['Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', 'Broken Bridge Northeast Water Drop', 'Broken Bridge NE']), create_dw_region(player, 'Broken Bridge West', None, ['Broken Bridge West Water Drop', 'Broken Bridge NW']), - create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], 'Dark World', Terrain.Water) - ] - if world.logic[player] in ['hybridglitches', 'nologic'] and not world.fix_palaceofdarkness_exit[player]: - world.regions += [ - create_dw_region(player, 'Palace of Darkness HMG Area', None, ['Palace of Darkness', 'Palace of Darkness HMG Exit']), - create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness SW', 'Palace of Darkness SE', 'Palace of Darkness HMG Entrance']) - ] - else: - world.regions += [ - create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Palace of Darkness SW', 'Palace of Darkness SE']) - ] - - world.regions += [ + create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], 'Dark World', Terrain.Water), + create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Palace of Darkness SW', 'Palace of Darkness SE']), create_dw_region(player, 'Hammer Pegs Area', ['Dark Blacksmith Ruins'], ['Hammer Peg Cave', 'Peg Area Rocks (East)']), create_dw_region(player, 'Hammer Pegs Entry', None, ['Peg Area Rocks (West)', 'Hammer Pegs WS']), create_dw_region(player, 'Dark Dunes Area', None, ['Dark Dunes NW', 'Dark Dunes WN', 'Dark Dunes SC']), diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py index 0f4389df..beb95ed9 100644 --- a/UnderworldGlitchRules.py +++ b/UnderworldGlitchRules.py @@ -29,42 +29,10 @@ paradox_spots = [ ("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area") ] - -# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions -kikiskip_connectors = [ - ("Kiki Skip Connector", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit") -] -mirehera_connectors = [ - ("Mire to Hera Connector", "Mire Torches Top", "Tower of Hera Exit") -] -heraswamp_connectors = [ - ("Mire to Swamp Connector", "Mire Torches Top", "Swamp Palace Exit") -] -thievesdesert_connectors = [ - ("Thieves to Desert West Connector", "Thieves Attic", "Desert Palace Exit (West)"), - ( - "Thieves to Desert South Connector", - "Thieves Attic", - "Desert Palace Exit (South)", - ), - ("Thieves to Desert East Connector", "Thieves Attic", "Desert Palace Exit (East)"), -] -specrock_connectors = [ - ( - "Spec Rock Top Connector", - "Spectacle Rock Cave (Peak)", - "Spectacle Rock Cave Exit (Top)", - ), - ( - "Spec Rock Exit Connector", - "Spectacle Rock Cave (Peak)", - "Spectacle Rock Cave Exit", - ), -] - - # Create connections between dungeons/locations def create_hybridmajor_connections(world, player): + fix_fake_worlds = world.fix_fake_world[player] + for spots in [ kikiskip_spots, mirehera_spots, @@ -74,7 +42,7 @@ def create_hybridmajor_connections(world, player): specrock_spots, paradox_spots, ]: - create_no_logic_connections(player, world, spots) + create_no_logic_connections(player, world, spots, connect_external=fix_fake_worlds) # Add the new Ice path (back of bomb drop to front) to the world and model it properly clip_door = create_door(player, "Ice Bomb Drop Clip", DoorType.Logical) @@ -87,39 +55,20 @@ def create_hybridmajor_connections(world, player): ) connect_simple_door(world, "Ice Bomb Drop Clip", "Ice Bomb Drop", player) - -# Turn dungeons into connectors -def create_hybridmajor_connectors(world, player): - for connectors in [ - kikiskip_connectors, - mirehera_connectors, - heraswamp_connectors, - thievesdesert_connectors, - specrock_connectors, - ]: - new_connectors = [ - ( - connector[0], - connector[1], - world.get_entrance(connector[2], player).connected_region, - ) - for connector in connectors - ] - create_no_logic_connections(player, world, new_connectors) - - -def get_hybridmajor_connector_entrances(): - connectors = [] +def get_hybridmajor_connection_entrances(): + connections = [] for connector in ( - kikiskip_connectors - + mirehera_connectors - + heraswamp_connectors - + thievesdesert_connectors - + specrock_connectors - ): - connectors.append(connector[0]) - return set(connectors) - + kikiskip_spots + + mirehera_spots + + heraswamp_spots + + icepalace_spots + + thievesdesert_spots + + specrock_spots + + paradox_spots + ): + connections.append(connector[0]) + connections.append('Ice Bomb Drop Clip') + return set(connections) # 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. @@ -151,11 +100,6 @@ def dungeon_reentry_rules( + thievesdesert_spots + specrock_spots + paradox_spots - + kikiskip_connectors - + mirehera_connectors - + heraswamp_connectors - + thievesdesert_connectors - + specrock_connectors ] dungeon_entrance = [ @@ -239,23 +183,12 @@ def underworld_glitches_rules(world, player): def dash_clip(state, region, player): return state.can_dash_clip(region, player) - - if not world.fix_palaceofdarkness_exit[player]: - Rules.set_rule( - world.get_entrance("Palace of Darkness HMG Exit", player), - lambda state: state.can_reach(world.get_entrance("Palace of Darkness", player), player), - ) - - # Bomb clips for clip in ( kikiskip_spots + icepalace_spots + thievesdesert_spots + specrock_spots - + kikiskip_connectors - + thievesdesert_connectors - + specrock_connectors ): region = world.get_region(clip[1], player) Rules.set_rule( @@ -279,15 +212,7 @@ def underworld_glitches_rules(world, player): spot[2], ) - for connector in kikiskip_connectors + thievesdesert_connectors: - dungeon_reentry_rules( - world, - player, - world.get_entrance(connector[0], player), - world.get_entrance(connector[2], player).parent_region.name, - ) - - for clip in mirehera_spots + mirehera_connectors: + for clip in mirehera_spots: Rules.set_rule( world.get_entrance(clip[0], player), lambda state: mire_clip(state), @@ -395,7 +320,7 @@ def underworld_glitches_rules(world, player): for desert_exit in desert_exits: Rules.add_rule( - world.get_entrance(f"Thieves to Desert {desert_exit} Connector", player), + world.get_entrance(f"Thieves to Desert {desert_exit} Clip", player), lambda state: state.can_dash_clip( world.get_region("Thieves Attic", player), player ), diff --git a/test/suite/hmg/inverted_inaccessible_desert.yaml b/test/suite/hmg/inverted_inaccessible_desert.yaml index 5e59ca63..2a55777d 100644 --- a/test/suite/hmg/inverted_inaccessible_desert.yaml +++ b/test/suite/hmg/inverted_inaccessible_desert.yaml @@ -14,6 +14,8 @@ start_inventory: placements: 1: Desert Palace - Boss: Moon Pearl + Desert Palace - Prize: Green Pendant + Sahasrahla: Magic Mirror entrances: 1: two-way: @@ -22,4 +24,6 @@ entrances: Thieves Town: Thieves Town Exit Hyrule Castle Entrance (East): Desert Palace Exit (South) Hyrule Castle Entrance (West): Desert Palace Exit (North) + entrances: + Agahnims Tower: Pyramid Fairy diff --git a/test/suite/hmg/pod_mp.yaml b/test/suite/hmg/pod_mp.yaml new file mode 100644 index 00000000..19de4b63 --- /dev/null +++ b/test/suite/hmg/pod_mp.yaml @@ -0,0 +1,13 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Pegasus Boots +placements: + 1: + Palace of Darkness - Shooter Room: Moon Pearl \ No newline at end of file