From f442cff06119c12b18cbac68f9c18529ded2da09 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 3 Aug 2023 15:06:54 -0600 Subject: [PATCH] Logic added for openable trap doors --- BaseClasses.py | 3 +- DoorShuffle.py | 52 ++++++++++++--- Main.py | 2 +- RELEASENOTES.md | 3 + Rules.py | 101 +++++++++++++++++++++++++++- test/dungeons/trap_test.yaml | 125 +++++++++++++++++++++++++++++++++++ 6 files changed, 271 insertions(+), 15 deletions(-) create mode 100644 test/dungeons/trap_test.yaml diff --git a/BaseClasses.py b/BaseClasses.py index 3d97414b..a62ca101 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1760,6 +1760,7 @@ class Door(object): self.dest = None self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed) self.blocked_orig = False + self.trapped = False self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue) self.smallKey = False # There's a small key door on this side self.bigKey = False # There's a big key door on this side @@ -1870,7 +1871,7 @@ class Door(object): return self def no_exit(self): - self.blocked = self.blocked_orig = True + self.blocked = self.blocked_orig = self.trapped = True return self def no_entrance(self): diff --git a/DoorShuffle.py b/DoorShuffle.py index abf1def8..7a78246b 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -88,8 +88,7 @@ def link_doors_prep(world, player): find_inaccessible_regions(world, player) - if world.doorShuffle[player] != 'vanilla': - create_dungeon_pool(world, player) + create_dungeon_pool(world, player) if world.intensity[player] >= 3 and world.doorShuffle[player] != 'vanilla': choose_portals(world, player) else: @@ -1844,12 +1843,12 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, wo builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon]) remaining -= len(custom_trap_doors[dungeon]) ttl += len(builder.candidates.trap) - if ttl == 0: + if ttl == 0 and all(len(custom_trap_doors[dungeon]) == 0 for dungeon in pool): continue for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] proportion = len(builder.candidates.trap) - calc = int(round(proportion * door_type_pool.traps/ttl)) + calc = 0 if ttl == 0 else int(round(proportion * door_type_pool.traps/ttl)) suggested = min(proportion, calc) remaining -= suggested suggestion_map[dungeon] = suggested @@ -1981,7 +1980,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_ remaining = max(0, remaining) for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] - calculated = int(round(builder.key_doors_num*total_keys/ttl)) + if ttl == 0: + calculated = 0 + else: + calculated = int(round(builder.key_doors_num*total_keys/ttl)) max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player)) cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt) limit = min(max_keys, cand_len, max_computation) @@ -2211,9 +2213,10 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, sample_list = build_sample_list(combinations, 1000) proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed) proposal.extend(custom_trap_doors) + filtered_proposal = [x for x in proposal if x.name not in trap_door_exceptions] start_regions, event_starts = filter_start_regions(builder, start_regions, world, player) - while not validate_trap_layout(proposal, builder, start_regions, paths, world, player): + while not validate_trap_layout(filtered_proposal, builder, start_regions, paths, world, player): itr += 1 if itr >= len(sample_list): if not drop: @@ -2248,6 +2251,12 @@ def filter_start_regions(builder, start_regions, world, player): portal_entrance_region = portal.door.entrance.parent_region.name if portal_entrance_region not in builder.path_entrances: excluded[region] = None + if not portal: + drop_region = next((x.parent_region for x in region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] + or x.parent_region.name == 'Sewer Drop'), None) + if drop_region and drop_region.name in world.inaccessible_regions[player]: + excluded[region] = None if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'): excluded[region] = None if portal is None: @@ -2343,10 +2352,12 @@ def reassign_trap_doors(trap_map, world, player): elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]: room.change(d.doorListPos, DoorKind.Normal) d.blocked = False + d.trapped = False # connect_one_way(world, d.name, d.dest.name, player) elif d.type is DoorType.Normal and d not in traps: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.blocked = False + d.trapped = False for d in traps: change_door_to_trap(d, world, player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player) @@ -2384,24 +2395,45 @@ def change_door_to_trap(d, world, player): elif d.direction in [Direction.North, Direction.West]: new_kind = DoorKind.TrapTriggerable if new_kind: - d.blocked = True + d.blocked = is_trap_door_blocked(d) + d.trapped = True pos = 3 if d.type == DoorType.Normal else 4 verify_door_list_pos(d, room, world, player, pos) d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1, 3: 0x8}[d.doorListPos] room.change(d.doorListPos, new_kind) - if d.entrance.connected_region is not None: + if d.entrance.connected_region is not None and d.blocked: d.entrance.connected_region.entrances.remove(d.entrance) d.entrance.connected_region = None elif d.type is DoorType.Normal: - d.blocked = True + d.blocked = is_trap_door_blocked(d) + d.trapped = True verify_door_list_pos(d, room, world, player, pos=3) d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos] room.change(d.doorListPos, DoorKind.Trap) - if d.entrance.connected_region is not None: + if d.entrance.connected_region is not None and d.blocked: d.entrance.connected_region.entrances.remove(d.entrance) d.entrance.connected_region = None +trap_door_exceptions = { + 'PoD Mimics 2 SW', 'TR Twin Pokeys NW', 'Thieves Blocked Entry SW', 'Hyrule Dungeon Armory Interior Key Door N', + 'Desert Compass Key Door WN', 'TR Tile Room SE', 'Mire Cross SW', 'Tower Circle of Pots ES', + 'Eastern Single Eyegore ES', 'Eastern Duo Eyegores SE', 'Swamp Push Statue S', + 'Skull 2 East Lobby WS', 'GT Hope Room WN', 'Eastern Courtyard Ledge S', 'Ice Lobby SE', 'GT Speed Torch WN', + 'Ice Switch Room ES', 'Ice Switch Room NE', 'Skull Torch Room WS', 'GT Speed Torch NE', 'GT Speed Torch WS', + 'GT Torch Cross WN', 'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Torches WN', 'PoD Lobby N', 'PoD Middle Cage S', + 'Ice Bomb Jump NW', 'GT Hidden Spikes SE', 'Ice Tall Hint EN', 'GT Conveyor Cross EN', 'Eastern Pot Switch WN', + 'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Eastern Dark Square Key Door WN', 'Eastern Lobby NW', + 'Eastern Lobby NE', 'Ice Cross Bottom SE', 'Desert Back Lobby S', 'Desert West S', + 'Desert West Lobby ES', 'Mire Hidden Shooters SE', 'Mire Hidden Shooters ES', 'Mire Hidden Shooters WS', + 'Tower Dark Pits EN', 'Tower Dark Maze ES', 'TR Tongue Pull WS', +} + + +def is_trap_door_blocked(door): + return door.name not in trap_door_exceptions + + def find_big_key_candidates(builder, start_regions, used, world, player): if world.door_type_mode[player] != 'original': # big, all, chaos # traverse dungeon and find candidates diff --git a/Main.py b/Main.py index 7c124808..be6266e2 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.19' +version_number = '1.2.0.20' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3231157f..59f1e778 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,9 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.20u + * Added logic for trap doors that could be opened using existing room triggers + * Added a notes field for user added notes either via CLI or Customizer * 1.2.0.19u * Added min/max for triforce pool, goal, and difference for CLI and Customizer. (Thanks Catobat) * Fixed a bug with dungeon generation diff --git a/Rules.py b/Rules.py index 26e70f5f..0b390f3d 100644 --- a/Rules.py +++ b/Rules.py @@ -276,11 +276,18 @@ def global_rules(world, player): # Start of door rando rules # TODO: Do these need to flag off when door rando is off? - some of them, yes + def is_trapped(entrance): + return world.get_entrance(entrance, player).door.trapped + # Eastern Palace # Eyegore room needs a bow set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player)) + if is_trapped('Eastern Single Eyegore ES'): + set_rule(world.get_entrance('Eastern Single Eyegore ES', player), lambda state: state.can_shoot_arrows(player)) + if is_trapped('Eastern Duo Eyegores SE'): + set_rule(world.get_entrance('Eastern Duo Eyegores SE', player), lambda state: state.can_shoot_arrows(player)) # Boss rules. Same as below but no BK or arrow requirement. set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player)) @@ -305,13 +312,18 @@ def global_rules(world, player): set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player)) + set_rule(world.get_entrance('Tower Circle of Pots NW', player), lambda state: state.can_kill_most_things(player)) + if is_trapped('Tower Circle of Pots ES'): + set_rule(world.get_entrance('Tower Circle of Pots ES', player), + lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) - set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player)) + if is_trapped('PoD Mimics 2 SW'): + set_rule(world.get_entrance('PoD Mimics 2 SW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player)) @@ -360,6 +372,8 @@ def global_rules(world, player): set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('Skull Torch Room WS'): + set_rule(world.get_entrance('Skull Torch Room WS', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) hidden_pits_door = world.get_door('Skull Small Hall WS', player) @@ -397,6 +411,8 @@ def global_rules(world, player): set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state)) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player)) + if is_trapped('Ice Lobby SE'): + set_rule(world.get_entrance('Ice Lobby SE', player), lambda state: state.can_melt_things(player)) set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) @@ -411,6 +427,12 @@ def global_rules(world, player): set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player)) if not world.get_door('Ice Switch Room SE', player).entranceFlag: set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) + if is_trapped('Ice Switch Room ES'): + set_rule(world.get_entrance('Ice Switch Room ES', player), + lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) + if is_trapped('Ice Switch Room NE'): + set_rule(world.get_entrance('Ice Switch Room NE', player), + lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player)) @@ -431,8 +453,15 @@ def global_rules(world, player): or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player)) + if is_trapped('Mire Tile Room SW'): + set_rule(world.get_entrance('Mire Tile Room SW', player), lambda state: state.has_fire_source(player)) + if is_trapped('Mire Tile Room ES'): + set_rule(world.get_entrance('Mire Tile Room ES', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player)) + if is_trapped('Mire Dark Shooters SE'): + set_rule(world.get_entrance('Mire Dark Shooters SE', player), + lambda state: state.has('Cane of Somaria', player)) set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player)) @@ -448,6 +477,9 @@ def global_rules(world, player): set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + if is_trapped('TR Torches WN'): + set_rule(world.get_entrance('TR Torches WN', player), + lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player)) set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player)) @@ -467,10 +499,20 @@ def global_rules(world, player): set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) + if is_trapped('GT Hope Room WN'): + set_rule(world.get_entrance('GT Hope Room WN', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player)) + if is_trapped('GT Conveyor Cross EN'): + set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hammer', player)) if not world.get_door('GT Speed Torch SE', player).entranceFlag: set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('GT Speed Torch NE'): + set_rule(world.get_entrance('GT Speed Torch NE', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('GT Speed Torch WS'): + set_rule(world.get_entrance('GT Speed Torch WS', player), lambda state: state.has('Fire Rod', player)) + if is_trapped('GT Speed Torch WN'): + set_rule(world.get_entrance('GT Speed Torch WN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) @@ -505,6 +547,8 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state)) set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player)) + if is_trapped('GT Torch Cross WN'): + set_rule(world.get_entrance('GT Torch Cross WN', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player)) # todo: the following only applies to crystal state propagation from this supertile # you can also reset the supertile, but I'm not sure how to model that @@ -760,13 +804,29 @@ def bomb_rules(world, player): ('GT Petting Zoo SE', False), # Dont make anyone do this room with bombs and/or pots. ('GT DMs Room SW', False) # Four red stalfos ] + conditional_kill_traps = [ + ('Hyrule Dungeon Armory Interior Key Door N', True), + ('Desert Compass Key Door WN', True), + ('Thieves Blocked Entry SW', True), + ('TR Tongue Pull WS', True), + ('TR Twin Pokeys NW', False), + ] for killdoor,bombable in easy_kill_rooms: if bombable: add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player))) else: add_rule(world.get_entrance(killdoor, player), lambda state: state.can_kill_most_things(player)) + for kill_door, bombable in conditional_kill_traps: + if world.get_entrance(kill_door, player).door.trapped: + if bombable: + add_rule(world.get_entrance(kill_door, player), + lambda state: (state.can_use_bombs(player) or state.can_kill_most_things(player))) + else: + add_rule(world.get_entrance(kill_door, player), lambda state: state.can_kill_most_things(player)) add_rule(world.get_entrance('Ice Stalfos Hint SE', player), lambda state: state.can_use_bombs(player)) # Need bombs for big stalfos knights - add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player) + add_rule(world.get_entrance('Mire Cross ES', player), lambda state: state.can_kill_most_things(player)) # 4 Sluggulas. Bombs don't work // or (state.can_use_bombs(player) and state.has('Magic Powder'), player) + if world.get_entrance('Mire Cross SW', player).door.trapped: + add_rule(world.get_entrance('Mire Cross SW', player), lambda state: state.can_kill_most_things(player)) enemy_kill_drops = [ # Location, bool-bombable ('Hyrule Castle - Map Guard Key Drop', True), @@ -1143,6 +1203,9 @@ def swordless_rules(world, player): set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) + if world.get_entrance('Ice Lobby SE', player).door.trapped: + set_rule(world.get_entrance('Ice Lobby SE', player), + lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) @@ -1156,7 +1219,7 @@ def swordless_rules(world, player): if world.mode[player] != 'inverted': set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player)) - +# todo: new traps std_kill_rooms = { 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard @@ -1187,6 +1250,18 @@ std_kill_rooms = { 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work } # all trap rooms? +std_kill_doors_if_trapped = { + 'Hyrule Dungeon Armory Main': 'Hyrule Dungeon Armory Interior Key Door N', + # 'Eastern Single Eyegore ES', # arrow rule is sufficient + # 'Eastern Duo Eyegores S', # arrow rule is sufficient + 'TR Twin Pokeys': 'TR Twin Pokeys NW', + 'Thieves Basement Block': 'Thieves Blocked Entry SW', + 'Desert Compass Room': 'Desert Compass Key Door WN', + 'Mire Cross': 'Mire Cross SW', + 'Tower Circle of Pots': 'Tower Circle of Pots ES', + # 'Ice Lobby S' # can melt rule is sufficient +} + def add_connection(parent_name, target_name, entrance_name, world, player): parent = world.get_region(parent_name, player) target = world.get_region(target_name, player) @@ -1241,6 +1316,10 @@ def standard_rules(world, player): if region.name in std_kill_rooms: for ent in std_kill_rooms[region.name]: add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state)) + if region.name in std_kill_doors_if_trapped: + ent = world.get_entrance(std_kill_doors_if_trapped[region.name], player) + if ent.door.trapped: + add_rule(ent, lambda state: standard_escape_rule(state)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player)) @@ -1866,6 +1945,11 @@ def set_bunny_rules(world, player, inverted): if is_bunny(bunny_exit.parent_region): add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region)) + for ent_name in bunny_impassible_if_trapped: + bunny_exit = world.get_entrance(ent_name, player) + if bunny_exit.door.trapped and is_bunny(bunny_exit.parent_region): + add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region)) + doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors] doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked] for door in doors_to_check: @@ -1997,6 +2081,17 @@ bunny_impassible_doors = { 'GT Validation Block Path' } +bunny_impassible_if_trapped = { + 'Hyrule Dungeon Armory Interior Key Door N', 'Eastern Pot Switch WN', 'Eastern Lobby NW', + 'Eastern Lobby NE', 'Desert Compass Key Door WN', 'Tower Circle of Pots ES', 'PoD Mimics 2 SW', + 'PoD Middle Cage S', 'Swamp Push Statue S', 'Skull 2 East Lobby WS', 'Skull Torch Room WS', + 'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW', 'Thieves Blocked Entry SW', 'Ice Bomb Jump NW', + 'Ice Tall Hint EN', 'Ice Switch Room ES', 'Ice Switch Room NE', 'Mire Cross SW', + 'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Twin Pokeys NW', 'TR Torches WN', 'GT Hope Room WN', + 'GT Speed Torch NE', 'GT Speed Torch WS', 'GT Torch Cross WN', 'GT Hidden Spikes SE', 'GT Conveyor Cross EN', + 'GT Speed Torch WN', 'Ice Lobby SE' +} + def add_key_logic_rules(world, player): key_logic = world.key_logic[player] diff --git a/test/dungeons/trap_test.yaml b/test/dungeons/trap_test.yaml new file mode 100644 index 00000000..d82a8ade --- /dev/null +++ b/test/dungeons/trap_test.yaml @@ -0,0 +1,125 @@ +meta: + players: 1 +settings: + 1: + door_shuffle: basic + intensity: 3 + door_type_mode: all +doors: + 1: + doors: + PoD Mimics 2 SW: + type: Trap Door +# TR Twin Pokeys NW: # not possible due to trap flags +# type: Trap Door + Thieves Blocked Entry SW: + type: Trap Door + Hyrule Dungeon Armory Interior Key Door N: + type: Trap Door + Desert Compass Key Door WN: + type: Trap Door + TR Tile Room SE: + type: Trap Door +# Mire Cross SW: # not possible due to trap flags +# type: Trap Door + Tower Circle of Pots ES: + type: Trap Door + Eastern Single Eyegore ES: + type: Trap Door + Eastern Duo Eyegores SE: + type: Trap Door + Swamp Push Statue S: + type: Trap Door +# Skull 2 East Lobby WS: # currently not possible due to trap flags +# type: Trap Door + GT Hope Room WN : + type: Trap Door + +# Eastern Courtyard Ledge S: # currently not possible due to trap flags +# type: Trap Door + Ice Switch Room ES : + type: Trap Door + Ice Switch Room NE : + type: Trap Door + Skull Torch Room WS : + type: Trap Door + GT Speed Torch NE : + type: Trap Door + GT Speed Torch WS : + type: Trap Door + GT Torch Cross WN : + type: Trap Door + Mire Tile Room SW : + type: Trap Door + Mire Tile Room ES : + type: Trap Door + TR Torches WN : + type: Trap Door + PoD Lobby N: + type: Trap Door + PoD Middle Cage S: + type: Trap Door + Ice Bomb Jump NW: + type: Trap Door + GT Hidden Spikes SE: + type: Trap Door + Ice Tall Hint EN: + type: Trap Door + GT Conveyor Cross EN: + type: Trap Door + Eastern Pot Switch WN: + type: Trap Door + Thieves Conveyor Maze WN: + type: Trap Door +# Thieves Conveyor Maze SW: #not possible due to 4 door limit +# type: Trap Door + Eastern Dark Square Key Door WN: + type: Trap Door + Eastern Lobby NW: + type: Trap Door + Eastern Lobby NE: + type: Trap Door +# Ice Cross Bottom SE: # not possible due to trap flags +# type: Trap Door + Desert Back Lobby S: + type: Trap Door +# Desert West S: need enough lobbies for basic, should otherwise work +# type: Trap Door + Desert West Lobby ES: + type: Trap Door +# Mire Hidden Shooters SE: # not possible due to trap flags +# type: Trap Door +# Mire Hidden Shooters ES: # not possible due to trap flags +# type: Trap Door + Mire Hidden Shooters WS: + type: Trap Door + Tower Dark Pits EN: + type: Trap Door + Tower Dark Maze ES: + type: Trap Door + TR Tongue Pull WS: + type: Trap Door + +# Lower layer: not valid + # Sewers Pull Switch N: + # type: Trap Door + # PoD Sexy Statue W: # not possible due to trap flags and low layer too, so likely not an exception + # type: Trap Door + +# Not valid due to disappearing somaria block +# Mire Dark Shooters SE: +# type: Trap Door + + # These triggers don't open doors +# Ice Compass Room NE: +# type: Trap Door +# Hera Torches NE: +# type: Trap Door +# Mire Spikes WS: +# type: Trap Door +# Mire Spikes SW: +# type: Trap Door +# Mire Spikes NW: +# type: Trap Door +# Tower Room 03 WN: +# type: Trap Door