From 861195eb8baa2397316cdac16f18ebc6ef69bac2 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sat, 7 Sep 2019 16:39:39 -0700 Subject: [PATCH 01/12] Add internal key data Adds forced keys (enemy and pot drops) to regions, adds key doors within supertiles. --- BaseClasses.py | 4 +++- DoorShuffle.py | 41 +++++++++++++++++++++++++++-------------- Doors.py | 4 ++++ Regions.py | 22 +++++++++++++--------- Rules.py | 2 +- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f54ea07d..15479f24 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -655,9 +655,10 @@ class RegionType(Enum): class Region(object): - def __init__(self, name, type, hint, player): + def __init__(self, name, type, forced_keys, hint, player): self.name = name self.type = type + self.forced_keys = forced_keys self.entrances = [] self.exits = [] self.locations = [] @@ -809,6 +810,7 @@ class DoorType(Enum): Open = 5 Hole = 6 Warp = 7 + Interior = 8 @unique diff --git a/DoorShuffle.py b/DoorShuffle.py index d09bf76f..29cb9696 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -143,24 +143,27 @@ def connect_one_way(world, entrancename, exitname, player, skipSpoiler=False): def within_dungeon(world, player): # TODO: Add dungeon names to Regions so we can just look these lists up - dungeon_region_names_es = ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall', 'Hyrule Castle West Hall', 'Hyrule Castle Back Hall', 'Hyrule Castle Throne Room', 'Hyrule Dungeon Map Room', 'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk', 'Hyrule Dungeon South Abyss', 'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom', 'Hyrule Dungeon Armory', 'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Secret Room', 'Sewers Secret Room Blocked Path', 'Sewers Pull Switch', 'Sanctuary'] + # TODO: The "starts" regions need access logic + dungeon_region_starts_es = ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Secret Room'] + dungeon_region_names_es = ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall', 'Hyrule Castle West Hall', 'Hyrule Castle Back Hall', 'Hyrule Castle Throne Room', 'Hyrule Dungeon Map Room', 'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk', 'Hyrule Dungeon South Abyss', 'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom', 'Hyrule Dungeon Armory Main', 'Hyrule Dungeon Armory North Branch', 'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Secret Room', 'Sewers Secret Room Blocked Path', 'Sewers Pull Switch', 'Sanctuary'] + dungeon_region_starts_ep = ['Eastern Lobby'] dungeon_region_names_ep = ['Eastern Lobby', 'Eastern Cannonball', 'Eastern Cannonball Ledge', 'Eastern Courtyard Ledge', 'Eastern Map Area', 'Eastern Compass Area', 'Eastern Courtyard', 'Eastern Fairies', 'Eastern Map Valley', 'Eastern Dark Square', 'Eastern Big Key', 'Eastern Darkness', 'Eastern Attic Start', 'Eastern Attic Switches', 'Eastern Eyegores', 'Eastern Boss'] - dungeon_region_lists = [dungeon_region_names_es, dungeon_region_names_ep] - for region_list in dungeon_region_lists: - shuffle_dungeon(world, player, region_list) + dungeon_region_lists = [(dungeon_region_starts_es, dungeon_region_names_es), (dungeon_region_starts_ep, dungeon_region_names_ep)] + for start_list, region_list in dungeon_region_lists: + shuffle_dungeon(world, player, start_list, region_list) -def shuffle_dungeon(world, player, dungeon_region_names): +def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): logger = logging.getLogger('') + # Part one - generate a random layout available_regions = [] for name in dungeon_region_names: available_regions.append(world.get_region(name, player)) random.shuffle(available_regions) # Pick a random region and make its doors the open set - # TODO: It would make sense to start with the entrance but I'm not sure it's needed. available_doors = [] region = available_regions.pop() - print("Starting in " + region.name) + logger.info("Starting in %s", region.name) available_doors.extend(get_doors(world, region, player)) # Loop until all available doors are used @@ -188,16 +191,24 @@ def shuffle_dungeon(world, player, dungeon_region_names): available_doors.remove(connect_door) # Check that we used everything, and retry if we failed if len(available_regions) > 0 or len(available_doors) > 0: - logger.info('Failed to add all regions to dungeon, trying again.') - shuffle_dungeon(world, player, dungeon_region_names) + logger.info('Failed to add all regions to dungeon, trying again.') + shuffle_dungeon(world, player, start_region_names, dungeon_region_names) + return + # Connects a and b. Or don't if they're an unsupported connection type. # TODO: This is gross, don't do it this way def maybe_connect_two_way(world, a, b, player): - if a.type == DoorType.Open or a.type == DoorType.StraightStairs or a.type == DoorType.Hole or a.type == DoorType.Warp: + # Return on unsupported types. + if a.type == DoorType.Open or a.type == DoorType.StraightStairs or a.type == DoorType.Hole or a.type == DoorType.Warp or a.type == DoorType.Interior: return - connect_two_way(world, a.name, b.name, player) - + # Connect supported types + if a.type == DoorType.Normal or a.type == DoorType.SpiralStairs: + connect_two_way(world, a.name, b.name, player) + return + # If we failed to account for a type, panic + raise RuntimeError('Unknown door type ' + a.type) + # Finds a compatible door in regions, returns the region and door def find_compatible_door_in_regions(world, door, regions, player): for region in regions: @@ -230,6 +241,8 @@ def doors_compatible(a, b): return doors_fit_mandatory_pair(falldown_pits_as_doors, a, b) if a.type == DoorType.Warp: return doors_fit_mandatory_pair(dungeon_warps_as_doors, a, b) + if a.type == DoorType.Interior: + return doors_fit_mandatory_pair(interior_doors, a, b) return a.direction == switch_dir(b.direction) def doors_fit_mandatory_pair(pair_list, a, b): @@ -664,8 +677,6 @@ def experiment(world, player): # DATA GOES DOWN HERE mandatory_connections = [('Hyrule Dungeon North Abyss Catwalk Dropdown', 'Hyrule Dungeon North Abyss'), - ('Hyrule Dungeon Key Door S', 'Hyrule Dungeon North Abyss'), - ('Hyrule Dungeon Key Door N', 'Hyrule Dungeon Map Room'), ('Sewers Secret Room Push Block', 'Sewers Secret Room Blocked Path') ] @@ -714,6 +725,8 @@ falldown_pits_as_doors = [('Eastern Courtyard Potholes', 'Eastern Fairy Landing' dungeon_warps = [('Eastern Fairies\' Warp', 'Eastern Courtyard')] dungeon_warps_as_doors = [('Eastern Fairies\' Warp', 'Eastern Courtyard Warp End')] +interior_doors = [('Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Interior Key Door N'), ('Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon North Abyss Key Door N')] + default_door_connections = [('Hyrule Castle Lobby W', 'Hyrule Castle West Lobby E'), ('Hyrule Castle Lobby E', 'Hyrule Castle East Lobby W'), ('Hyrule Castle Lobby WN', 'Hyrule Castle West Lobby EN'), diff --git a/Doors.py b/Doors.py index 5c66b199..e9e40f9f 100644 --- a/Doors.py +++ b/Doors.py @@ -44,6 +44,8 @@ def create_doors(world, player): # hyrule dungeon level create_spiral_stairs(player, 'Hyrule Dungeon Map Room Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x72, 0, High, A, 0x4f, 0xf8), + create_small_key_door(player, 'Hyrule Dungeon Map Room Key Door S', DoorType.Interior, Direction.South, 0x71, Right, High), + create_small_key_door(player, 'Hyrule Dungeon North Abyss Key Door N', DoorType.Interior, Direction.North, 0x71, Right, High), create_dir_door(player, 'Hyrule Dungeon North Abyss South Edge', DoorType.Open, Direction.South, 0x72, None, Low), create_dir_door(player, 'Hyrule Dungeon North Abyss Catwalk Edge', DoorType.Open, Direction.South, 0x72, None, High), create_dir_door(player, 'Hyrule Dungeon South Abyss North Edge', DoorType.Open, Direction.North, 0x82, None, Low), @@ -54,6 +56,8 @@ def create_doors(world, player): create_dir_door(player, 'Hyrule Dungeon Guardroom Abyss Edge', DoorType.Open, Direction.West, 0x81, None, High), create_dir_door(player, 'Hyrule Dungeon Guardroom N', DoorType.Normal, Direction.North, 0x81, Left, Low), # todo: is this a toggle door? create_dir_door(player, 'Hyrule Dungeon Armory S', DoorType.Normal, Direction.South, 0x71, Left, Low), # not sure what the layer should be here + create_small_key_door(player, 'Hyrule Dungeon Armory Interior Key Door N', DoorType.Interior, Direction.North, 0x71, Left, High), + create_small_key_door(player, 'Hyrule Dungeon Armory Interior Key Door S', DoorType.Interior, Direction.South, 0x71, Left, High), create_spiral_stairs(player, 'Hyrule Dungeon Armory Down Stairs', DoorType.SpiralStairs, Direction.Down, 0x71, 0, Low, A, 0x1e, 0xa0, True), create_spiral_stairs(player, 'Hyrule Dungeon Staircase Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x70, 2, High, A, 0x36, 0xa0, True), create_spiral_stairs(player, 'Hyrule Dungeon Staircase Down Stairs', DoorType.SpiralStairs, Direction.Down, 0x70, 1, High, A, 0x1f, 0x50), diff --git a/Regions.py b/Regions.py index 7bf297c0..5a527d30 100644 --- a/Regions.py +++ b/Regions.py @@ -296,13 +296,14 @@ def create_regions(world, player): create_dungeon_region(player, 'Hyrule Castle Back Hall', 'A dungeon', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']), create_dungeon_region(player, 'Hyrule Castle Throne Room', 'A dungeon', None, ['Hyrule Castle Throne Room N', 'Hyrule Castle Throne Room South Stairs']), - create_dungeon_region(player, 'Hyrule Dungeon Map Room', 'A dungeon', ['Hyrule Castle - Map Chest'], ['Hyrule Dungeon Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), - create_dungeon_region(player, 'Hyrule Dungeon North Abyss', 'A dungeon', None, ['Hyrule Dungeon North Abyss South Edge', 'Hyrule Dungeon Key Door N']), + create_forced_key_region(player, 'Hyrule Dungeon Map Room', 'A dungeon', 1, ['Hyrule Castle - Map Chest'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), + create_dungeon_region(player, 'Hyrule Dungeon North Abyss', 'A dungeon', None, ['Hyrule Dungeon North Abyss South Edge', 'Hyrule Dungeon North Abyss Key Door N']), create_dungeon_region(player, 'Hyrule Dungeon North Abyss Catwalk', 'A dungeon', None, ['Hyrule Dungeon North Abyss Catwalk Edge', 'Hyrule Dungeon North Abyss Catwalk Dropdown']), create_dungeon_region(player, 'Hyrule Dungeon South Abyss', 'A dungeon', None, ['Hyrule Dungeon South Abyss North Edge', 'Hyrule Dungeon South Abyss West Edge']), create_dungeon_region(player, 'Hyrule Dungeon South Abyss Catwalk', 'A dungeon', None, ['Hyrule Dungeon South Abyss Catwalk North Edge', 'Hyrule Dungeon South Abyss Catwalk West Edge']), create_dungeon_region(player, 'Hyrule Dungeon Guardroom', 'A dungeon', None, ['Hyrule Dungeon Guardroom Catwalk Edge', 'Hyrule Dungeon Guardroom Abyss Edge', 'Hyrule Dungeon Guardroom N']), - create_dungeon_region(player, 'Hyrule Dungeon Armory', 'A dungeon', ['Hyrule Castle - Boomerang Chest'], ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory Down Stairs']), + create_forced_key_region(player, 'Hyrule Dungeon Armory Main', 'A dungeon', 1, ['Hyrule Castle - Boomerang Chest'], ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory Interior Key Door N']), + create_dungeon_region(player, 'Hyrule Dungeon Armory North Branch', 'A dungeon', None, ['Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Down Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Staircase', 'A dungeon', None, ['Hyrule Dungeon Staircase Up Stairs', 'Hyrule Dungeon Staircase Down Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'A dungeon', ['Hyrule Castle - Zelda\'s Chest'], ['Hyrule Dungeon Cellblock Up Stairs']), @@ -367,19 +368,22 @@ def create_regions(world, player): world.intialize_regions() def create_lw_region(player, name, locations=None, exits=None): - return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits) + return _create_region(player, name, RegionType.LightWorld, 'Light World', 0, locations, exits) def create_dw_region(player, name, locations=None, exits=None): - return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits) + return _create_region(player, name, RegionType.DarkWorld, 'Dark World', 0, locations, exits) def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None): - return _create_region(player, name, RegionType.Cave, hint, locations, exits) + return _create_region(player, name, RegionType.Cave, hint, 0, locations, exits) def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None): - return _create_region(player, name, RegionType.Dungeon, hint, locations, exits) + return _create_region(player, name, RegionType.Dungeon, hint, 0, locations, exits) -def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None): - ret = Region(name, type, hint, player) +def create_forced_key_region(player, name, hint='Hyrule', keys=0, locations=None, exits=None): + return _create_region(player, name, RegionType.Dungeon, hint, keys, locations, exits) + +def _create_region(player, name, type, hint='Hyrule', keys=0, locations=None, exits=None): + ret = Region(name, type, keys, hint, player) if locations is None: locations = [] if exits is None: diff --git a/Rules.py b/Rules.py index 772b2def..7fdc536a 100644 --- a/Rules.py +++ b/Rules.py @@ -951,7 +951,7 @@ def swordless_rules(world, player): set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) def standard_rules(world, player): - add_rule(world.get_entrance('Sewers Door', player), lambda state: state.can_kill_most_things(player)) +# add_rule(world.get_entrance('Sewers Door', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) From 8513dff63688d6b648e1556553291debb4181577 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sat, 14 Sep 2019 22:08:32 -0700 Subject: [PATCH 02/12] Fix some incorrect door keyness --- Doors.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Doors.py b/Doors.py index 53fda537..d62c284f 100644 --- a/Doors.py +++ b/Doors.py @@ -99,7 +99,7 @@ def create_doors(world, player): create_dir_door(player, 'Eastern Map Area W', DoorType.Normal, Direction.West, 0xaa, Mid, High), create_dir_door(player, 'Eastern Compass Area E', DoorType.Normal, Direction.East, 0xa8, Mid, High), create_dir_door(player, 'Eastern Compass Area EN', DoorType.Normal, Direction.East, 0xa8, Top, Low), - create_blocked_door(player, 'Eastern Compass Area SW', DoorType.Normal, Direction.South, 0xa8, Right, High), + create_blocked_key_door(player, 'Eastern Compass Area SW', DoorType.Normal, Direction.South, 0xa8, Right, High), create_dir_door(player, 'Eastern Courtyard WN', DoorType.Normal, Direction.West, 0xa9, Top, Low), create_dir_door(player, 'Eastern Courtyard EN', DoorType.Normal, Direction.East, 0xa9, Top, Low), create_big_key_door(player, 'Eastern Courtyard N', DoorType.Normal, Direction.North, 0xa9, Mid, High), @@ -109,10 +109,10 @@ def create_doors(world, player): create_door(player, 'Eastern Courtyard Warp End', DoorType.Warp), create_dir_door(player, 'Eastern Map Valley WN', DoorType.Normal, Direction.West, 0xaa, Top, Low), create_dir_door(player, 'Eastern Map Valley SW', DoorType.Normal, Direction.South, 0xaa, Left, High), - create_small_key_door(player, 'Eastern Dark Square NW', DoorType.Normal, Direction.North, 0xba, Left, High), + create_dir_door(player, 'Eastern Dark Square NW', DoorType.Normal, Direction.North, 0xba, Left, High), create_small_key_door(player, 'Eastern Dark Square Key Door WN', DoorType.Normal, Direction.West, 0xba, Top, High), - create_small_key_door(player, 'Eastern Big Key EN', DoorType.Normal, Direction.East, 0xb8, Top, High), - create_dir_door(player, 'Eastern Big Key NE', DoorType.Normal, Direction.North, 0xb8, Right, High), + create_dir_door(player, 'Eastern Big Key EN', DoorType.Normal, Direction.East, 0xb8, Top, High), + create_big_key_door(player, 'Eastern Big Key NE', DoorType.Normal, Direction.North, 0xb8, Right, High), create_small_key_door(player, 'Eastern Darkness S', DoorType.Normal, Direction.South, 0x99, Mid, High), create_spiral_stairs(player, 'Eastern Darkness Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x99, 0, HTH, Z, 0x1a, 0x6c, False, True), create_spiral_stairs(player, 'Eastern Attic Start Down Stairs', DoorType.SpiralStairs, Direction.Down, 0xda, 0, HTH, Z, 0x11, 0x80, False, True), @@ -146,6 +146,11 @@ def create_blocked_door(player, name, type, direction, room, doorIndex, layer, t d.blocked = True return d +def create_blocked_key_door(player, name, type, direction, room, doorIndex, layer, toggle=False): + d = Door(player, name, type, direction, room, doorIndex, layer, toggle) + d.blocked = True + d.smallKey = True + return d def create_dir_door(player, name, type, direction, room, doorIndex, layer): return Door(player, name, type, direction, room, doorIndex, layer) From fcd810b4f39b581c33cd598b7fc3c83e8c49102e Mon Sep 17 00:00:00 2001 From: tolmar Date: Sat, 14 Sep 2019 22:09:21 -0700 Subject: [PATCH 03/12] Force pair keydoors --- DoorShuffle.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 29cb9696..d837d053 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -193,7 +193,8 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): if len(available_regions) > 0 or len(available_doors) > 0: logger.info('Failed to add all regions to dungeon, trying again.') shuffle_dungeon(world, player, start_region_names, dungeon_region_names) - return + return + # Connects a and b. Or don't if they're an unsupported connection type. @@ -207,7 +208,7 @@ def maybe_connect_two_way(world, a, b, player): connect_two_way(world, a.name, b.name, player) return # If we failed to account for a type, panic - raise RuntimeError('Unknown door type ' + a.type) + raise RuntimeError('Unknown door type ' + a.type.name) # Finds a compatible door in regions, returns the region and door def find_compatible_door_in_regions(world, door, regions, player): @@ -243,6 +244,8 @@ def doors_compatible(a, b): return doors_fit_mandatory_pair(dungeon_warps_as_doors, a, b) if a.type == DoorType.Interior: return doors_fit_mandatory_pair(interior_doors, a, b) + if a.type == DoorType.Normal and (a.smallKey or b.smallKey or a.bigKey or b.bigKey): + return doors_fit_mandatory_pair(key_doors, a, b) return a.direction == switch_dir(b.direction) def doors_fit_mandatory_pair(pair_list, a, b): @@ -725,7 +728,13 @@ falldown_pits_as_doors = [('Eastern Courtyard Potholes', 'Eastern Fairy Landing' dungeon_warps = [('Eastern Fairies\' Warp', 'Eastern Courtyard')] dungeon_warps_as_doors = [('Eastern Fairies\' Warp', 'Eastern Courtyard Warp End')] -interior_doors = [('Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Interior Key Door N'), ('Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon North Abyss Key Door N')] +interior_doors = [('Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Interior Key Door N'), + ('Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon North Abyss Key Door N')] + +key_doors = [('Sewers Key Rat Key Door N', 'Sewers Secret Room Key Door S'), + ('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'), + ('Eastern Big Key NE', 'Eastern Compass Area SW'), + ('Eastern Darkness S', 'Eastern Courtyard N')] default_door_connections = [('Hyrule Castle Lobby W', 'Hyrule Castle West Lobby E'), ('Hyrule Castle Lobby E', 'Hyrule Castle East Lobby W'), From c8bd17042e5875a1ac8fb6fd2c24ca6ea495d7e0 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 00:08:30 -0700 Subject: [PATCH 04/12] More door fixes --- DoorShuffle.py | 1 + Doors.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index d837d053..430f876a 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -732,6 +732,7 @@ interior_doors = [('Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon ('Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon North Abyss Key Door N')] key_doors = [('Sewers Key Rat Key Door N', 'Sewers Secret Room Key Door S'), + ('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S'), ('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'), ('Eastern Big Key NE', 'Eastern Compass Area SW'), ('Eastern Darkness S', 'Eastern Courtyard N')] diff --git a/Doors.py b/Doors.py index 312f2712..72476c6b 100644 --- a/Doors.py +++ b/Doors.py @@ -69,13 +69,13 @@ def create_doors(world, player): create_spiral_stairs(player, 'Hyrule Dungeon Cellblock Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x80, 0, HTH, A, 0x1a, 0x44), # sewers - create_blocked_door(player, 'Sewers Behind Tapestry S', DoorType.Normal, Direction.South, 0x41, Mid, High, False, 0x2), + create_blocked_door(player, 'Sewers Behind Tapestry S', DoorType.Normal, Direction.South, 0x41, Mid, High, False, False, 0x2), create_spiral_stairs(player, 'Sewers Behind Tapestry Down Stairs', DoorType.SpiralStairs, Direction.Down, 0x41, 0, HTH, S, 0x12, 0xb0), create_spiral_stairs(player, 'Sewers Rope Room Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x42, 0, HTH, S, 0x1b, 0x9c), create_dir_door(player, 'Sewers Rope Room North Stairs', DoorType.StraightStairs, Direction.North, 0x42, Mid, High), create_dir_door(player, 'Sewers Dark Cross South Stairs', DoorType.StraightStairs, Direction.South, 0x32, Mid, High), - create_dir_door(player, 'Sewers Dark Cross Key Door N', DoorType.Normal, Direction.North, 0x32, Mid, High), - create_dir_door(player, 'Sewers Dark Cross Key Door S', DoorType.Normal, Direction.South, 0x22, Mid, High), + create_small_key_door(player, 'Sewers Dark Cross Key Door N', DoorType.Normal, Direction.North, 0x32, Mid, High), + create_small_key_door(player, 'Sewers Dark Cross Key Door S', DoorType.Normal, Direction.South, 0x22, Mid, High), create_dir_door(player, 'Sewers Water W', DoorType.Normal, Direction.West, 0x22, Bot, High), create_dir_door(player, 'Sewers Key Rat E', DoorType.Normal, Direction.East, 0x21, Bot, High), create_small_key_door(player, 'Sewers Key Rat Key Door N', DoorType.Normal, Direction.North, 0x21, Right, High), From cce2ef12fa9f96d3b2089ff784956fe443d25a04 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 00:08:56 -0700 Subject: [PATCH 05/12] Add lamp rules to door entrances --- Rules.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Rules.py b/Rules.py index 7fdc536a..78ddbc10 100644 --- a/Rules.py +++ b/Rules.py @@ -258,6 +258,25 @@ def global_rules(world, player): set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) + # Start of door rando rules + # TODO: Do these need to flag off when door rando is off? + # TODO: Can these replace other rules? + + # Sewers: All sorts of things need lamps + # TODO: Need to play nice with other complicated lamp rules + set_rule(world.get_entrance('Sewers Behind Tapestry S', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Behind Tapestry Down Stairs', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Rope Room Up Stairs', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Rope Room North Stairs', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Dark Cross South Stairs', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Dark Cross Key Door N', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Dark Cross Key Door S', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Water W', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Key Rat E', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_entrance('Sewers Key Rat Key Door N', player), lambda state: state.has('Lamp', player)) + + # End of door rando rules. + # set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player)) # set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player)) From b1e20c9c9452d05906c553826e8694b76aa7e6eb Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 00:27:51 -0700 Subject: [PATCH 06/12] Revert forced keys change I think I found a better way to do it --- BaseClasses.py | 3 +-- Regions.py | 19 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 8161b12c..5c5f9ab1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -655,10 +655,9 @@ class RegionType(Enum): class Region(object): - def __init__(self, name, type, forced_keys, hint, player): + def __init__(self, name, type, hint, player): self.name = name self.type = type - self.forced_keys = forced_keys self.entrances = [] self.exits = [] self.locations = [] diff --git a/Regions.py b/Regions.py index 5a527d30..2835fd07 100644 --- a/Regions.py +++ b/Regions.py @@ -296,13 +296,13 @@ def create_regions(world, player): create_dungeon_region(player, 'Hyrule Castle Back Hall', 'A dungeon', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']), create_dungeon_region(player, 'Hyrule Castle Throne Room', 'A dungeon', None, ['Hyrule Castle Throne Room N', 'Hyrule Castle Throne Room South Stairs']), - create_forced_key_region(player, 'Hyrule Dungeon Map Room', 'A dungeon', 1, ['Hyrule Castle - Map Chest'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), + create_dungeon_region(player, 'Hyrule Dungeon Map Room', 'A dungeon', ['Hyrule Castle - Map Chest'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), create_dungeon_region(player, 'Hyrule Dungeon North Abyss', 'A dungeon', None, ['Hyrule Dungeon North Abyss South Edge', 'Hyrule Dungeon North Abyss Key Door N']), create_dungeon_region(player, 'Hyrule Dungeon North Abyss Catwalk', 'A dungeon', None, ['Hyrule Dungeon North Abyss Catwalk Edge', 'Hyrule Dungeon North Abyss Catwalk Dropdown']), create_dungeon_region(player, 'Hyrule Dungeon South Abyss', 'A dungeon', None, ['Hyrule Dungeon South Abyss North Edge', 'Hyrule Dungeon South Abyss West Edge']), create_dungeon_region(player, 'Hyrule Dungeon South Abyss Catwalk', 'A dungeon', None, ['Hyrule Dungeon South Abyss Catwalk North Edge', 'Hyrule Dungeon South Abyss Catwalk West Edge']), create_dungeon_region(player, 'Hyrule Dungeon Guardroom', 'A dungeon', None, ['Hyrule Dungeon Guardroom Catwalk Edge', 'Hyrule Dungeon Guardroom Abyss Edge', 'Hyrule Dungeon Guardroom N']), - create_forced_key_region(player, 'Hyrule Dungeon Armory Main', 'A dungeon', 1, ['Hyrule Castle - Boomerang Chest'], ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory Interior Key Door N']), + create_dungeon_region(player, 'Hyrule Dungeon Armory Main', 'A dungeon', ['Hyrule Castle - Boomerang Chest'], ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory Interior Key Door N']), create_dungeon_region(player, 'Hyrule Dungeon Armory North Branch', 'A dungeon', None, ['Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Down Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Staircase', 'A dungeon', None, ['Hyrule Dungeon Staircase Up Stairs', 'Hyrule Dungeon Staircase Down Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'A dungeon', ['Hyrule Castle - Zelda\'s Chest'], ['Hyrule Dungeon Cellblock Up Stairs']), @@ -368,22 +368,19 @@ def create_regions(world, player): world.intialize_regions() def create_lw_region(player, name, locations=None, exits=None): - return _create_region(player, name, RegionType.LightWorld, 'Light World', 0, locations, exits) + return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits) def create_dw_region(player, name, locations=None, exits=None): - return _create_region(player, name, RegionType.DarkWorld, 'Dark World', 0, locations, exits) + return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits) def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None): - return _create_region(player, name, RegionType.Cave, hint, 0, locations, exits) + return _create_region(player, name, RegionType.Cave, hint, locations, exits) def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None): - return _create_region(player, name, RegionType.Dungeon, hint, 0, locations, exits) - -def create_forced_key_region(player, name, hint='Hyrule', keys=0, locations=None, exits=None): - return _create_region(player, name, RegionType.Dungeon, hint, keys, locations, exits) + return _create_region(player, name, RegionType.Dungeon, hint, locations, exits) -def _create_region(player, name, type, hint='Hyrule', keys=0, locations=None, exits=None): - ret = Region(name, type, keys, hint, player) +def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None): + ret = Region(name, type, hint, player) if locations is None: locations = [] if exits is None: From 3a872723b03193b322d06beacd1e06c134fabc7d Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 02:24:48 -0700 Subject: [PATCH 07/12] Use connect_one_way when doors are blocked Shuffler doesn't respect blocked doors yet so it may create an unbeatable layout, and the item placer will panic. But that's better than silently giving the player something impossible --- DoorShuffle.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 430f876a..736a93fa 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -124,7 +124,7 @@ def connect_one_way(world, entrancename, exitname, player, skipSpoiler=False): # if these were already connected somewhere, remove the backreference if entrance.connected_region is not None: - entrance.connected_region.entrances.remove(entrance, player) + entrance.connected_region.entrances.remove(entrance) if ext.connected_region is not None: ext.connected_region.entrances.remove(ext) @@ -205,7 +205,12 @@ def maybe_connect_two_way(world, a, b, player): return # Connect supported types if a.type == DoorType.Normal or a.type == DoorType.SpiralStairs: - connect_two_way(world, a.name, b.name, player) + if a.blocked: + connect_one_way(world, b.name, a.name, player) + elif b.blocked: + connect_one_way(world, a.name, b.name, player) + else: + connect_two_way(world, a.name, b.name, player) return # If we failed to account for a type, panic raise RuntimeError('Unknown door type ' + a.type.name) From 40f345df1393f266c7554240b6fd98e2f38168bb Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 02:28:04 -0700 Subject: [PATCH 08/12] Simple key logic for Escape Adds ability to make a location for required keys (enemy drops and pots), so the filler knows about them. Uses the most pessimistic possible rule to make sure you can't keylock yourself in Escape. --- BaseClasses.py | 13 ++++++++++--- Regions.py | 16 ++++++++++++---- Rules.py | 11 +++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 5c5f9ab1..552d39e0 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -898,17 +898,24 @@ class Boss(object): return self.defeat_rule(state, self.player) class Location(object): - def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None): + def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, forced_item=None): self.name = name self.parent_region = parent - self.item = None + if forced_item is not None: + from Items import ItemFactory + self.forced_item = ItemFactory([forced_item], player)[0] + self.item = self.forced_item + self.event = True + else: + self.forced_item = None + self.item = None + self.event = False self.crystal = crystal self.address = address self.spot_type = 'Location' self.hint_text = hint_text if hint_text is not None else 'Hyrule' self.recursion_count = 0 self.staleness_count = 0 - self.event = False self.locked = True self.always_allow = lambda item, state: False self.access_rule = lambda state: True diff --git a/Regions.py b/Regions.py index 2835fd07..9ea5eaab 100644 --- a/Regions.py +++ b/Regions.py @@ -296,13 +296,13 @@ def create_regions(world, player): create_dungeon_region(player, 'Hyrule Castle Back Hall', 'A dungeon', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']), create_dungeon_region(player, 'Hyrule Castle Throne Room', 'A dungeon', None, ['Hyrule Castle Throne Room N', 'Hyrule Castle Throne Room South Stairs']), - create_dungeon_region(player, 'Hyrule Dungeon Map Room', 'A dungeon', ['Hyrule Castle - Map Chest'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), + create_dungeon_region(player, 'Hyrule Dungeon Map Room', 'A dungeon', ['Hyrule Castle - Map Chest', 'Hyrule Castle - Map Guard Key Drop'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), create_dungeon_region(player, 'Hyrule Dungeon North Abyss', 'A dungeon', None, ['Hyrule Dungeon North Abyss South Edge', 'Hyrule Dungeon North Abyss Key Door N']), create_dungeon_region(player, 'Hyrule Dungeon North Abyss Catwalk', 'A dungeon', None, ['Hyrule Dungeon North Abyss Catwalk Edge', 'Hyrule Dungeon North Abyss Catwalk Dropdown']), create_dungeon_region(player, 'Hyrule Dungeon South Abyss', 'A dungeon', None, ['Hyrule Dungeon South Abyss North Edge', 'Hyrule Dungeon South Abyss West Edge']), create_dungeon_region(player, 'Hyrule Dungeon South Abyss Catwalk', 'A dungeon', None, ['Hyrule Dungeon South Abyss Catwalk North Edge', 'Hyrule Dungeon South Abyss Catwalk West Edge']), create_dungeon_region(player, 'Hyrule Dungeon Guardroom', 'A dungeon', None, ['Hyrule Dungeon Guardroom Catwalk Edge', 'Hyrule Dungeon Guardroom Abyss Edge', 'Hyrule Dungeon Guardroom N']), - create_dungeon_region(player, 'Hyrule Dungeon Armory Main', 'A dungeon', ['Hyrule Castle - Boomerang Chest'], ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory Interior Key Door N']), + create_dungeon_region(player, 'Hyrule Dungeon Armory Main', 'A dungeon', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Boomerang Guard Key Drop'], ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory Interior Key Door N']), create_dungeon_region(player, 'Hyrule Dungeon Armory North Branch', 'A dungeon', None, ['Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Down Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Staircase', 'A dungeon', None, ['Hyrule Dungeon Staircase Up Stairs', 'Hyrule Dungeon Staircase Down Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'A dungeon', ['Hyrule Castle - Zelda\'s Chest'], ['Hyrule Dungeon Cellblock Up Stairs']), @@ -389,8 +389,11 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None for exit in exits: ret.exits.append(Entrance(player, exit, ret)) for location in locations: - address, crystal, hint_text = location_table[location] - ret.locations.append(Location(player, location, address, crystal, hint_text, ret)) + if location in key_only_locations: + ret.locations.append(Location(player, location, None, False, None, ret, key_only_locations[location])) + else: + address, crystal, hint_text = location_table[location] + ret.locations.append(Location(player, location, address, crystal, hint_text, ret)) return ret def mark_light_world_regions(world): @@ -456,6 +459,11 @@ default_shop_contents = { 'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], } +key_only_locations = { + 'Hyrule Castle - Map Guard Key Drop': 'Small Key (Escape)', + 'Hyrule Castle - Boomerang Guard Key Drop': 'Small Key (Escape)' +} + location_table = {'Mushroom': (0x180013, False, 'in the woods'), 'Bottle Merchant': (0x2eb18, False, 'with a merchant'), 'Flute Spot': (0x18014a, False, 'underground'), diff --git a/Rules.py b/Rules.py index 78ddbc10..05827283 100644 --- a/Rules.py +++ b/Rules.py @@ -262,6 +262,17 @@ def global_rules(world, player): # TODO: Do these need to flag off when door rando is off? # TODO: Can these replace other rules? + # Hyrule Castle: Can't get keys from guards unless you can kill said guards. + add_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), lambda state: state.can_kill_most_things(player)) + add_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state.can_kill_most_things(player)) + + # Hyrule Castle: There are three keys and we don't know how we shuffled, so we + # need three keys to be accessible before you use any of these doors. + # TODO: Generate key rules in the shuffler. (But make sure this way works first.) + for door in ['Sewers Key Rat Key Door N', 'Sewers Secret Room Key Door S', + 'Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S', 'Hyrule Dungeon Armory Interior Key Door N', 'Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon North Abyss Key Door N']: + set_rule(world.get_entrance(door, player), lambda state: state.has_key('Small Key (Escape)', player, 3)) + # Sewers: All sorts of things need lamps # TODO: Need to play nice with other complicated lamp rules set_rule(world.get_entrance('Sewers Behind Tapestry S', player), lambda state: state.has('Lamp', player)) From 6ac00c296808e4ad8cb854197f96b75b4d033f12 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 11:35:35 -0700 Subject: [PATCH 09/12] Eastern Palace Logic Works sometimes. Either needs a south-facing big key door hack, or a way to avoid placing big key doors in places where you can see their back. --- DoorShuffle.py | 1 + Doors.py | 1 + Regions.py | 8 +++++--- Rules.py | 26 ++++++++++++++++++++++---- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 736a93fa..c7ba6b22 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -739,6 +739,7 @@ interior_doors = [('Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon key_doors = [('Sewers Key Rat Key Door N', 'Sewers Secret Room Key Door S'), ('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S'), ('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'), + ('Eastern Darkness Up Stairs', 'Eastern Attic Start Down Stairs'), ('Eastern Big Key NE', 'Eastern Compass Area SW'), ('Eastern Darkness S', 'Eastern Courtyard N')] diff --git a/Doors.py b/Doors.py index 72476c6b..76a00c15 100644 --- a/Doors.py +++ b/Doors.py @@ -113,6 +113,7 @@ def create_doors(world, player): create_dir_door(player, 'Eastern Big Key EN', DoorType.Normal, Direction.East, 0xb8, Top, High), create_big_key_door(player, 'Eastern Big Key NE', DoorType.Normal, Direction.North, 0xb8, Right, High), create_small_key_door(player, 'Eastern Darkness S', DoorType.Normal, Direction.South, 0x99, Mid, High), + # TODO: Up is a keydoor and down is not. Are they both spiralkeys or what? create_spiral_stairs(player, 'Eastern Darkness Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x99, 0, HTH, Z, 0x1a, 0x6c, False, True), create_spiral_stairs(player, 'Eastern Attic Start Down Stairs', DoorType.SpiralStairs, Direction.Down, 0xda, 0, HTH, Z, 0x11, 0x80, False, True), create_dir_door(player, 'Eastern Attic Start WS', DoorType.Normal, Direction.West, 0xda, Bot, High), diff --git a/Regions.py b/Regions.py index 9ea5eaab..ea5fe587 100644 --- a/Regions.py +++ b/Regions.py @@ -329,9 +329,9 @@ def create_regions(world, player): create_dungeon_region(player, 'Eastern Courtyard', 'A dungeon', ['Eastern Palace - Big Chest'], ['Eastern Courtyard WN', 'Eastern Courtyard EN', 'Eastern Courtyard N', 'Eastern Courtyard Potholes', 'Eastern Courtyard Warp End']), create_dungeon_region(player, 'Eastern Fairies', 'A dungeon', None, ['Eastern Fairies\' Warp', 'Eastern Fairy Landing']), create_dungeon_region(player, 'Eastern Map Valley', 'A dungeon', None, ['Eastern Map Valley WN', 'Eastern Map Valley SW']), - create_dungeon_region(player, 'Eastern Dark Square', 'A dungeon', None, ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN']), + create_dungeon_region(player, 'Eastern Dark Square', 'A dungeon', ['Eastern Palace - Dark Square Pot Key'], ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN']), create_dungeon_region(player, 'Eastern Big Key', 'A dungeon', ['Eastern Palace - Big Key Chest'], ['Eastern Big Key EN', 'Eastern Big Key NE']), - create_dungeon_region(player, 'Eastern Darkness', 'A dungeon', None, ['Eastern Darkness S', 'Eastern Darkness Up Stairs']), + create_dungeon_region(player, 'Eastern Darkness', 'A dungeon', ['Eastern Palace - Dark Eyegore Key Drop'], ['Eastern Darkness S', 'Eastern Darkness Up Stairs']), create_dungeon_region(player, 'Eastern Attic Start', 'A dungeon', None, ['Eastern Attic Start Down Stairs', 'Eastern Attic Start WS']), create_dungeon_region(player, 'Eastern Attic Switches', 'A dungeon', None, ['Eastern Attic Switches ES', 'Eastern Attic Switches WS']), create_dungeon_region(player, 'Eastern Eyegores', 'A dungeon', None, ['Eastern Eyegores ES', 'Eastern Eyegores NE']), @@ -461,7 +461,9 @@ default_shop_contents = { key_only_locations = { 'Hyrule Castle - Map Guard Key Drop': 'Small Key (Escape)', - 'Hyrule Castle - Boomerang Guard Key Drop': 'Small Key (Escape)' + 'Hyrule Castle - Boomerang Guard Key Drop': 'Small Key (Escape)', + 'Eastern Palace - Dark Square Pot Key': 'Small Key (Eastern Palace)', + 'Eastern Palace - Dark Eyegore Key Drop': 'Small Key (Eastern Palace)', } location_table = {'Mushroom': (0x180013, False, 'in the woods'), diff --git a/Rules.py b/Rules.py index 05827283..08e90ab0 100644 --- a/Rules.py +++ b/Rules.py @@ -282,20 +282,38 @@ def global_rules(world, player): set_rule(world.get_entrance('Sewers Dark Cross South Stairs', player), lambda state: state.has('Lamp', player)) set_rule(world.get_entrance('Sewers Dark Cross Key Door N', player), lambda state: state.has('Lamp', player)) set_rule(world.get_entrance('Sewers Dark Cross Key Door S', player), lambda state: state.has('Lamp', player)) + set_rule(world.get_location('Sewers - Dark Cross', player), lambda state: state.has('Lamp', player)) set_rule(world.get_entrance('Sewers Water W', player), lambda state: state.has('Lamp', player)) set_rule(world.get_entrance('Sewers Key Rat E', player), lambda state: state.has('Lamp', player)) set_rule(world.get_entrance('Sewers Key Rat Key Door N', player), lambda state: state.has('Lamp', player)) - # End of door rando rules. + # Eastern Palace + # The stalfos room and eyegore with a key can be killed with pots. + # Eastern Palace has dark rooms. + for location in ['Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop']: + add_rule(world.get_location(location, player), lambda state: state.has('Lamp', player)) + for door in ['Eastern Darkness S', 'Eastern Darkness Up Stairs', 'Eastern Dark Square NW', 'Eastern Dark Square Key Door WN']: + add_rule(world.get_entrance(door, player), lambda state: state.has('Lamp', player)) + # Eyegore room needs a bow + set_rule(world.get_entrance('Eastern Eyegores NE', player), lambda state: state.can_shoot_arrows(player)) + # Big key rules + set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) + set_rule(world.get_entrance('Eastern Big Key NE', player), lambda state: state.has('Big Key (Eastern Palace)', player)) + set_rule(world.get_entrance('Eastern Courtyard N', player), lambda state: state.has('Big Key (Eastern Palace)', player)) + # There are two keys and we don't know how we shuffled, so careful with key doors. + # TODO: Generate key rules in the shuffler. (But make sure this way works first.) + for door in ['Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN', 'Eastern Darkness Up Stairs', 'Eastern Attic Start Down Stairs']: + set_rule(world.get_entrance(door, player), lambda state: state.has_key('Small Key (Eastern Palace)', player, 2)) + + # End of door rando rules. # set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player)) # set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player)) - set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) set_rule(world.get_location('Eastern Palace - Boss', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) set_rule(world.get_location('Eastern Palace - Prize', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) - for location in ['Eastern Palace - Boss', 'Eastern Palace - Big Chest']: - forbid_item(world.get_location(location, player), 'Big Key (Eastern Palace)', player) +# for location in ['Eastern Palace - Boss', 'Eastern Palace - Big Chest']: +# forbid_item(world.get_location(location, player), 'Big Key (Eastern Palace)', player) set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player)) From 9fa26ef067f9b515963adf99ffb9f286a3112311 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 21:14:51 -0700 Subject: [PATCH 10/12] Handle multiple startpoints and some blocked doors Better blocked door handling: - Start dungeon layout at the entrance - Hold off on linking from blocked doors until the end of the dungeon, so they either point deeper in, or get added as a loop. - Now that we care what the start is, properly handle the fact that dungeons have multiple starts. --- DoorShuffle.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index c7ba6b22..11574520 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -156,26 +156,31 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): logger = logging.getLogger('') # Part one - generate a random layout available_regions = [] - for name in dungeon_region_names: + for name in [r for r in dungeon_region_names if r not in start_region_names]: available_regions.append(world.get_region(name, player)) random.shuffle(available_regions) - # Pick a random region and make its doors the open set + # Add all start regions to the open set. available_doors = [] - region = available_regions.pop() - logger.info("Starting in %s", region.name) - available_doors.extend(get_doors(world, region, player)) + for name in start_region_names: + logger.info("Starting in %s", name) + available_doors.extend(get_doors(world, world.get_region(name, player), player)) # Loop until all available doors are used while len(available_doors) > 0: - # Pick a random available door to connect - # TODO: Is there an existing "remove random from list" in this codebase? + # Pick a random available door to connect, prioritizing ones that aren't blocked. + # This makes them either get picked up through another door (so they head deeper + # into the dungeon), or puts them late in the dungeon (so they probably are part + # of a loop). Panic if neither of these happens. random.shuffle(available_doors) + available_doors.sort(key=lambda door: 1 if door.blocked else 0) door = available_doors.pop() logger.info('Linking %s', door.name) # Find an available region that has a compatible door connect_region, connect_door = find_compatible_door_in_regions(world, door, available_regions, player) - if connect_region is not None: + # Also ignore compatible doors if they're blocked; these should only be used to + # create loops. + if connect_region is not None and not door.blocked: logger.info(' Found new region %s via %s', connect_region.name, connect_door.name) # Apply connection and add the new region's doors to the available list maybe_connect_two_way(world, door, connect_door, player) @@ -186,6 +191,11 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): else: # If there's no available region with a door, use an internal connection connect_door = find_compatible_door_in_list(world, door, available_doors, player) + # If we don't have a door at this point, it's time to panic and retry. + if connect_door is None: + logger.info('Failed because of blocked door, trying again.') + shuffle_dungeon(world, player, start_region_names, dungeon_region_names) + return logger.info(' Adding loop via %s', connect_door.name) maybe_connect_two_way(world, door, connect_door, player) available_doors.remove(connect_door) From dda7dbaad4c0b3ce06a994a02deac5d479dbe96a Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 23:40:06 -0700 Subject: [PATCH 11/12] "Ugly door" support Ugly doors are doors that we don't want to see from the front: - south-facing side of a BK door - other side of one of those keyed staircases that don't actually have a matching key on the other side This rejects layouts where one would appear. --- BaseClasses.py | 1 + DoorShuffle.py | 34 +++++++++++++++++++++++++++++----- Doors.py | 8 ++++++-- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 552d39e0..03378f4e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -850,6 +850,7 @@ class Door(object): self.blocked = False # Indicates if the door is normally blocked off. (Sanc door or always closed) self.smallKey = False # There's a small key door on this side self.bigKey = False # There's a big key door on this side + self.ugly = False # Indicates that it can't be seen from the front (e.g. back of a big key door) def getAddress(self): if self.type == DoorType.Normal: diff --git a/DoorShuffle.py b/DoorShuffle.py index 11574520..94f4b89e 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -159,12 +159,20 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): for name in [r for r in dungeon_region_names if r not in start_region_names]: available_regions.append(world.get_region(name, player)) random.shuffle(available_regions) + + # "Ugly" doors are doors that we don't want to see from the front, because of some + # sort of unsupported key door. To handle them, make a map of "ugly regions" and + # never link across them. + ugly_regions = {} + next_ugly_region = 1 # Add all start regions to the open set. available_doors = [] for name in start_region_names: logger.info("Starting in %s", name) - available_doors.extend(get_doors(world, world.get_region(name, player), player)) + for door in get_doors(world, world.get_region(name, player), player): + ugly_regions[door.name] = 0 + available_doors.append(door) # Loop until all available doors are used while len(available_doors) > 0: @@ -173,7 +181,7 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): # into the dungeon), or puts them late in the dungeon (so they probably are part # of a loop). Panic if neither of these happens. random.shuffle(available_doors) - available_doors.sort(key=lambda door: 1 if door.blocked else 0) + available_doors.sort(key=lambda door: 1 if door.blocked else 2 if door.ugly else 0) door = available_doors.pop() logger.info('Linking %s', door.name) # Find an available region that has a compatible door @@ -184,13 +192,27 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names): logger.info(' Found new region %s via %s', connect_region.name, connect_door.name) # Apply connection and add the new region's doors to the available list maybe_connect_two_way(world, door, connect_door, player) - available_doors.extend(get_doors(world, connect_region, player)) + # Figure out the new room's ugliness region + new_room_ugly_region = ugly_regions[door.name] + if connect_door.ugly: + next_ugly_region += 1 + new_room_ugly_region = next_ugly_region + # Add the doors + for door in get_doors(world, connect_region, player): + ugly_regions[door.name] = new_room_ugly_region + available_doors.append(door) + # If an ugly door is anything but the connect door, panic and die + if door != connect_door and door.ugly: + logger.info('Failed because of ugly door, trying again.') + shuffle_dungeon(world, player, start_region_names, dungeon_region_names) + return + # We've used this region and door, so don't use them again available_regions.remove(connect_region) available_doors.remove(connect_door) else: # If there's no available region with a door, use an internal connection - connect_door = find_compatible_door_in_list(world, door, available_doors, player) + connect_door = find_compatible_door_in_list(ugly_regions, world, door, available_doors, player) # If we don't have a door at this point, it's time to panic and retry. if connect_door is None: logger.info('Failed because of blocked door, trying again.') @@ -233,8 +255,10 @@ def find_compatible_door_in_regions(world, door, regions, player): return region, proposed_door return None, None -def find_compatible_door_in_list(world, door, doors, player): +def find_compatible_door_in_list(ugly_regions, world, door, doors, player): for proposed_door in doors: + if ugly_regions[door.name] != ugly_regions[proposed_door.name]: + continue if doors_compatible(door, proposed_door): return proposed_door diff --git a/Doors.py b/Doors.py index 76a00c15..d6130398 100644 --- a/Doors.py +++ b/Doors.py @@ -112,10 +112,10 @@ def create_doors(world, player): create_small_key_door(player, 'Eastern Dark Square Key Door WN', DoorType.Normal, Direction.West, 0xba, Top, High), create_dir_door(player, 'Eastern Big Key EN', DoorType.Normal, Direction.East, 0xb8, Top, High), create_big_key_door(player, 'Eastern Big Key NE', DoorType.Normal, Direction.North, 0xb8, Right, High), - create_small_key_door(player, 'Eastern Darkness S', DoorType.Normal, Direction.South, 0x99, Mid, High), + ugly_door(create_small_key_door(player, 'Eastern Darkness S', DoorType.Normal, Direction.South, 0x99, Mid, High)), # TODO: Up is a keydoor and down is not. Are they both spiralkeys or what? create_spiral_stairs(player, 'Eastern Darkness Up Stairs', DoorType.SpiralStairs, Direction.Up, 0x99, 0, HTH, Z, 0x1a, 0x6c, False, True), - create_spiral_stairs(player, 'Eastern Attic Start Down Stairs', DoorType.SpiralStairs, Direction.Down, 0xda, 0, HTH, Z, 0x11, 0x80, False, True), + ugly_door(create_spiral_stairs(player, 'Eastern Attic Start Down Stairs', DoorType.SpiralStairs, Direction.Down, 0xda, 0, HTH, Z, 0x11, 0x80, False, True)), create_dir_door(player, 'Eastern Attic Start WS', DoorType.Normal, Direction.West, 0xda, Bot, High), create_dir_door(player, 'Eastern Attic Switches ES', DoorType.Normal, Direction.East, 0xd9, Bot, High), create_dir_door(player, 'Eastern Attic Switches WS', DoorType.Normal, Direction.West, 0xd9, Bot, High), @@ -169,3 +169,7 @@ def create_spiral_stairs(player, name, type, direction, room, d.zeroHzCam = zero_hz_cam d.zeroVtCam = zero_vt_cam return d + +def ugly_door(door): + door.ugly = True + return door From 64bdedf1e3a86145cb4dcd7a65e80ba3757e21e5 Mon Sep 17 00:00:00 2001 From: tolmar Date: Sun, 15 Sep 2019 23:41:08 -0700 Subject: [PATCH 12/12] Loosen EP rules EP boss rules no longer need BK+bow, since those were actually requirements of the rooms leading up to the boss. --- Rules.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Rules.py b/Rules.py index 08e90ab0..eb3c9b67 100644 --- a/Rules.py +++ b/Rules.py @@ -305,13 +305,17 @@ def global_rules(world, player): for door in ['Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN', 'Eastern Darkness Up Stairs', 'Eastern Attic Start Down Stairs']: set_rule(world.get_entrance(door, player), lambda state: state.has_key('Small Key (Eastern Palace)', player, 2)) - # End of door rando rules. + # Boss rules. Same as below but no BK or arrow requirement. + set_rule(world.get_location('Eastern Palace - Boss', player), lambda state: world.get_location('Eastern Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Eastern Palace - Prize', player), lambda state: world.get_location('Eastern Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + + # End of door rando rules. # set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player)) # set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player)) - set_rule(world.get_location('Eastern Palace - Boss', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) - set_rule(world.get_location('Eastern Palace - Prize', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) +# set_rule(world.get_location('Eastern Palace - Boss', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) +# set_rule(world.get_location('Eastern Palace - Prize', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) # for location in ['Eastern Palace - Boss', 'Eastern Palace - Big Chest']: # forbid_item(world.get_location(location, player), 'Big Key (Eastern Palace)', player)