From ea53f97289f0ad2c3f219ea086c53fd41a242b8e Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 19 Dec 2019 16:18:59 -0700 Subject: [PATCH 1/9] Removed the sewer dungeon for now to enable Escape Big Key to work. --- Rom.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Rom.py b/Rom.py index 2a5416f1..b7672adc 100644 --- a/Rom.py +++ b/Rom.py @@ -534,6 +534,10 @@ def patch_rom(world, player, rom): patch_shuffled_dark_sanc(world, rom, player) # patch doors + if world.doorShuffle == 'crossed': + rom.write_byte(0x151f1, 2) + rom.write_byte(0x15270, 2) + rom.write_byte(0x1597b, 2) for door in world.doors: if door.dest is not None and door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs]: rom.write_bytes(door.getAddress(), door.dest.getTarget(door.toggle)) From d0129256df09bd74d4350ddba32d421d5889637d Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 23 Dec 2019 11:15:55 -0700 Subject: [PATCH 2/9] logic fixes --- DoorShuffle.py | 1 + Doors.py | 1 + Regions.py | 2 +- Rules.py | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 68a418a1..8866d5b0 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1292,6 +1292,7 @@ logical_connections = [ ('Eastern Map Balcony Hook Path', 'Eastern Map Room'), ('Eastern Map Room Drop Down', 'Eastern Map Balcony'), ('Hera Big Chest Landing Exit', 'Hera 4F'), + ('PoD Arena Bonk Path', 'PoD Arena Bridge'), ('PoD Arena Main Crystal Path', 'PoD Arena Crystal'), ('PoD Arena Crystal Path', 'PoD Arena Main'), ('PoD Arena Main Orange Barrier', 'PoD Arena North'), diff --git a/Doors.py b/Doors.py index cb8c53b1..6e475ae6 100644 --- a/Doors.py +++ b/Doors.py @@ -333,6 +333,7 @@ def create_doors(world, player): create_door(player, 'PoD Arena Main Crystal Path', Lgcl), create_door(player, 'PoD Arena Main Orange Barrier', Lgcl), create_door(player, 'PoD Arena North Drop Down', Lgcl), + create_door(player, 'PoD Arena Bonk Path', Lgcl), create_door(player, 'PoD Arena Crystals E', Nrml).dir(Ea, 0x2a, Mid, High).pos(3), create_door(player, 'PoD Arena Crystal Path', Lgcl), create_door(player, 'PoD Arena Bridge Drop Down', Lgcl), diff --git a/Regions.py b/Regions.py index 90bb3f43..134d97db 100644 --- a/Regions.py +++ b/Regions.py @@ -336,7 +336,7 @@ def create_regions(world, player): create_dungeon_region(player, 'PoD Middle Cage', 'Palace of Darkness', None, ['PoD Middle Cage S', 'PoD Middle Cage SE', 'PoD Middle Cage N', 'PoD Middle Cage Down Stairs']), create_dungeon_region(player, 'PoD Shooter Room', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['PoD Shooter Room Up Stairs']), create_dungeon_region(player, 'PoD Pit Room', 'Palace of Darkness', None, ['PoD Pit Room S', 'PoD Pit Room NW', 'PoD Pit Room NE', 'PoD Pit Room Freefall', 'PoD Pit Room Bomb Hole']), - create_dungeon_region(player, 'PoD Arena Main', 'Palace of Darkness', None, ['PoD Arena Main SW', 'PoD Arena Main Crystal Path', 'PoD Arena Main Orange Barrier']), + create_dungeon_region(player, 'PoD Arena Main', 'Palace of Darkness', None, ['PoD Arena Main SW', 'PoD Arena Main Crystal Path', 'PoD Arena Main Orange Barrier', 'PoD Arena Bonk Path']), create_dungeon_region(player, 'PoD Arena North', 'Palace of Darkness', None, ['PoD Arena Main NW', 'PoD Arena Main NE', 'PoD Arena North Drop Down']), create_dungeon_region(player, 'PoD Arena Crystal', 'Palace of Darkness', None, ['PoD Arena Crystals E', 'PoD Arena Crystal Path']), create_dungeon_region(player, 'PoD Arena Bridge', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge'], ['PoD Arena Bridge SE', 'PoD Arena Bridge Drop Down']), diff --git a/Rules.py b/Rules.py index e7baebf4..0d291c2b 100644 --- a/Rules.py +++ b/Rules.py @@ -271,6 +271,7 @@ def global_rules(world, player): # Desert set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player)) + add_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has('Big Key (Desert Palace)', player)) set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Boss', player)) @@ -281,6 +282,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) + set_rule(world.get_entrance('PoD Arena 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)) set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player)) @@ -394,6 +396,7 @@ 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)) set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot East-North Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot South-East Path', player), lambda state: state.has('Hookshot', player)) From f0215303925d7ebf4cc119eb3fb2e726b19b07da Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 19 Dec 2019 15:10:03 -0700 Subject: [PATCH 3/9] Potential stair key door fix --- DoorShuffle.py | 5 ++++- RoomData.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 8866d5b0..77fe8ed5 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1033,7 +1033,10 @@ def reassign_key_doors(builder, proposal, world, player): if room.doorList[d.doorListPos][1] == DoorKind.StairKeyLow: room.delete(d.doorListPos) else: - room.change(d.doorListPos, DoorKind.Waterfall) + if len(room.doorList) > 1: + room.mirror(d.doorListPos) + else: + room.delete(d.doorListPos) d.smallKey = False elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) diff --git a/RoomData.py b/RoomData.py index 31001ea5..f1f89fc4 100644 --- a/RoomData.py +++ b/RoomData.py @@ -257,6 +257,16 @@ class Room(object): self.doorList[list_idx] = (prev[0], kind) self.modified = True + def mirror(self, list_idx): + prev = self.doorList[list_idx] + mirror_door = None + for door in self.doorList: + if door != prev: + mirror_door = door + break + self.doorList[list_idx] = (mirror_door[0], mirror_door[1]) + self.modified = True + def swap(self, idx1, idx2): item1 = self.doorList[idx1] item2 = self.doorList[idx2] From bdc722eaa3f1a82bb0a0ed0fb0320a4b981e2abb Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 23 Dec 2019 13:56:47 -0700 Subject: [PATCH 4/9] readme updates --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 8e86e87e..9b40f7e6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ This is a door randomizer for _The Legend of Zelda: A Link to the Past_ for the based on the Entrance Randomizer found at [KevinCathcart's Github Project.](https://github.com/KevinCathcart/ALttPEntranceRandomizer) See https://alttpr.com/ for more details on the normal randomizer. +# Known Issues + +[List of Known Issues and Their Status](https://docs.google.com/document/d/1Bk-m-QRvH5iF60ndptKYgyaV7P93D3TiG8xmdxp_bdQ/edit?usp=sharing) + +# Feedback and Bug Reports + +Please just DM me on discord for now. I (Aerinon) can be found at the [ALTTP Randomizer discord](https://discordapp.com/invite/alttprandomizer). + # Installation Clone this repository and then run ```DungeonRandomizer.py``` (requires Python 3). From 3a5a918012083e19e4a76645d7584aaad8632d1b Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 24 Dec 2019 07:15:34 -0700 Subject: [PATCH 5/9] Mark regions as light/dark world after dungeons are connected up. --- Main.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index e095db6f..b6aed083 100644 --- a/Main.py +++ b/Main.py @@ -63,20 +63,19 @@ def main(args, seed=None): if world.mode != 'inverted': for player in range(1, world.players + 1): link_entrances(world, player) - - mark_light_world_regions(world) else: for player in range(1, world.players + 1): link_inverted_entrances(world, player) - mark_dark_world_regions(world) - logger.info('Shuffling dungeons') for player in range(1, world.players + 1): link_doors(world, player) - # todo: mark regions after linking doors - for bunny logic? + if world.mode != 'inverted': + mark_light_world_regions(world) + else: + mark_dark_world_regions(world) logger.info('Generating Item Pool.') From 34352b35fc915cc6ce9d4d9b2172d480af9a8188 Mon Sep 17 00:00:00 2001 From: aerinon Date: Sat, 28 Dec 2019 20:54:48 -0700 Subject: [PATCH 6/9] PoD Key door - wrong position --- Doors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doors.py b/Doors.py index 6e475ae6..ed6cd4ba 100644 --- a/Doors.py +++ b/Doors.py @@ -304,11 +304,11 @@ def create_doors(world, player): create_door(player, 'Tower Agahnim 1 SW', Nrml).dir(So, 0x20, Left, High).no_exit().trap(0x4).pos(0), # Palace of Darkness - create_door(player, 'PoD Lobby N', Intr).dir(No, 0x4a, Mid, High).pos(2), + create_door(player, 'PoD Lobby N', Intr).dir(No, 0x4a, Mid, High).pos(3), create_door(player, 'PoD Lobby NW', Intr).dir(No, 0x4a, Left, High).pos(0), create_door(player, 'PoD Lobby NE', Intr).dir(No, 0x4a, Right, High).pos(1), create_door(player, 'PoD Left Cage SW', Intr).dir(No, 0x4a, Left, High).pos(0), - create_door(player, 'PoD Middle Cage S', Intr).dir(No, 0x4a, Mid, High).pos(2), + create_door(player, 'PoD Middle Cage S', Intr).dir(No, 0x4a, Mid, High).pos(3), create_door(player, 'PoD Middle Cage SE', Intr).dir(No, 0x4a, Right, High).pos(1), create_door(player, 'PoD Left Cage Down Stairs', Sprl).dir(Dn, 0x4a, 1, HTH).ss(A, 0x12, 0x80, False, True), create_door(player, 'PoD Middle Cage Down Stairs', Sprl).dir(Dn, 0x4a, 0, HTH).ss(S, 0x12, 0x80, False, True), From dcec274b639d1c2f0bfee70eac8ba604b2b1c84d Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 31 Dec 2019 11:44:30 -0700 Subject: [PATCH 7/9] Fix typo --- Rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index 0d291c2b..2f24ea90 100644 --- a/Rules.py +++ b/Rules.py @@ -396,7 +396,7 @@ 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)) set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot East-North Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot South-East Path', player), lambda state: state.has('Hookshot', player)) From 59f819aebd3643a0c6b68285f0d246c61d190eae Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 31 Dec 2019 21:12:53 -0700 Subject: [PATCH 8/9] Fixed a couple of generation errors --- DungeonGenerator.py | 2 +- DungeonRandomizer.py | 1 + KeyDoorShuffle.py | 14 +++++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index b5feaf74..95eebb40 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1482,7 +1482,7 @@ def valid_polarized_assignment(builder, sector_list): sector_mag = sector.magnitude() for i in range(len(sector_mag)): if sector_mag[i] > 0 and other_mag[i] == 0: - return True + return False # dead_ends = 0 # branches = 0 # for sector in sector_list: diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index 3e97e5ac..a6e5d07c 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -306,6 +306,7 @@ def start(): for _ in range(args.count): main(seed=seed, args=args) seed = random.randint(0, 999999999) + logging.getLogger('').info('Finished run %s', _) else: main(seed=args.seed, args=args) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 2f31f308..7d76a0ab 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -118,8 +118,9 @@ def analyze_dungeon(key_layout, world, player): raw_avail = chest_keys + len(key_counter.key_only_locations) available = raw_avail - key_counter.used_keys possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) + avail_bigs = count_unique_big_doors(key_counter) if not key_counter.big_key_opened: - if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls: + if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and avail_bigs == 0: key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations)) if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations): key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations)) @@ -479,6 +480,17 @@ def count_unique_small_doors(key_counter, proposal): return cnt +def count_unique_big_doors(key_counter): + cnt = 0 + counted = set() + for door in key_counter.child_doors: + if door.bigKey and door not in counted: + cnt += 1 + counted.add(door) + counted.add(door.dest) + return cnt + + def count_locations_big_optional(locations, bk=False): cnt = 0 for loc in locations: From 438d76562752568ed02ab15607d3f4528ff51a6a Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 2 Jan 2020 11:15:27 -0700 Subject: [PATCH 9/9] Directional typos on interior doors fixed. Better batching support for mass testing of seed generation. Generation issues fixed: --Filler now tests with the key in the proposed location to enable alternate key rules --Key rule checker now only considers key locations that the parent sphere did not have - better key rules --- DoorShuffle.py | 12 ++++++------ Doors.py | 30 +++++++++++++++--------------- DungeonRandomizer.py | 15 +++++++++++++-- Fill.py | 10 +++++++++- KeyDoorShuffle.py | 25 ++++++++++++++++++------- Regions.py | 12 ++++++------ Rules.py | 2 +- 7 files changed, 68 insertions(+), 38 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 77fe8ed5..5c7e7a18 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1150,9 +1150,9 @@ def find_inaccessible_regions(world, player): world.inaccessible_regions[player].append('Hyrule Castle Ledge') world.inaccessible_regions[player].append('Sewer Drop') logger = logging.getLogger('') - logger.info('Inaccessible Regions:') + logger.debug('Inaccessible Regions:') for r in world.inaccessible_regions[player]: - logger.info('%s', r) + logger.debug('%s', r) def valid_inaccessible_region(r): @@ -1661,10 +1661,10 @@ interior_doors = [ ('Skull Big Key WN', 'Skull Lone Pot EN'), ('Skull Small Hall WS', 'Skull 2 West Lobby ES'), ('Skull 2 West Lobby NW', 'Skull X Room SW'), - ('Skull 3 Lobby WN', 'Skull East Bridge EN'), - ('Skull East Bridge ES', 'Skull West Bridge Nook WS'), - ('Skull Star Pits WS', 'Skull Torch Room ES'), - ('Skull Torch Room EN', 'Skull Vines WN'), + ('Skull 3 Lobby EN', 'Skull East Bridge WN'), + ('Skull East Bridge WS', 'Skull West Bridge Nook ES'), + ('Skull Star Pits ES', 'Skull Torch Room WS'), + ('Skull Torch Room WN', 'Skull Vines EN'), ('Skull Spike Corner ES', 'Skull Final Drop WS'), ('Thieves Hallway WS', 'Thieves Pot Alcove Mid ES'), ('Thieves Conveyor Maze SW', 'Thieves Pot Alcove Top NW'), diff --git a/Doors.py b/Doors.py index ed6cd4ba..939e96f9 100644 --- a/Doors.py +++ b/Doors.py @@ -307,9 +307,9 @@ def create_doors(world, player): create_door(player, 'PoD Lobby N', Intr).dir(No, 0x4a, Mid, High).pos(3), create_door(player, 'PoD Lobby NW', Intr).dir(No, 0x4a, Left, High).pos(0), create_door(player, 'PoD Lobby NE', Intr).dir(No, 0x4a, Right, High).pos(1), - create_door(player, 'PoD Left Cage SW', Intr).dir(No, 0x4a, Left, High).pos(0), - create_door(player, 'PoD Middle Cage S', Intr).dir(No, 0x4a, Mid, High).pos(3), - create_door(player, 'PoD Middle Cage SE', Intr).dir(No, 0x4a, Right, High).pos(1), + create_door(player, 'PoD Left Cage SW', Intr).dir(So, 0x4a, Left, High).pos(0), + create_door(player, 'PoD Middle Cage S', Intr).dir(So, 0x4a, Mid, High).pos(3), + create_door(player, 'PoD Middle Cage SE', Intr).dir(So, 0x4a, Right, High).pos(1), create_door(player, 'PoD Left Cage Down Stairs', Sprl).dir(Dn, 0x4a, 1, HTH).ss(A, 0x12, 0x80, False, True), create_door(player, 'PoD Middle Cage Down Stairs', Sprl).dir(Dn, 0x4a, 0, HTH).ss(S, 0x12, 0x80, False, True), create_door(player, 'PoD Middle Cage N', Nrml).dir(No, 0x4a, Mid, High).small_key().pos(2), @@ -358,7 +358,7 @@ def create_doors(world, player): create_door(player, 'PoD Dark Maze EN', Nrml).dir(Ea, 0x19, Top, High).small_key().pos(1), create_door(player, 'PoD Dark Maze E', Nrml).dir(Ea, 0x19, Mid, High).pos(0), create_door(player, 'PoD Compass Room WN', Intr).dir(We, 0x1a, Top, High).pos(4), - create_door(player, 'PoD Compass Room SE', Intr).dir(No, 0x1a, Mid, High).small_key().pos(0), + create_door(player, 'PoD Compass Room SE', Intr).dir(So, 0x1a, Mid, High).small_key().pos(0), create_door(player, 'PoD Harmless Hellway NE', Intr).dir(No, 0x1a, Right, High).small_key().pos(0), create_door(player, 'PoD Harmless Hellway SE', Nrml).dir(So, 0x1a, Right, High).pos(5), create_door(player, 'PoD Compass Room W Down Stairs', Sprl).dir(Dn, 0x1a, 0, HTH).ss(S, 0x12, 0x50, True, True), @@ -466,7 +466,7 @@ def create_doors(world, player): create_door(player, 'Swamp Flooded Spot Ladder', Lgcl), create_door(player, 'Swamp Flooded Room Ladder', Lgcl), create_door(player, 'Swamp Basement Shallows NW', Nrml).dir(No, 0x76, Left, High).toggler().pos(2), - create_door(player, 'Swamp Basement Shallows EN', Intr).dir(We, 0x76, Top, High).pos(0), + create_door(player, 'Swamp Basement Shallows EN', Intr).dir(Ea, 0x76, Top, High).pos(0), create_door(player, 'Swamp Basement Shallows ES', Intr).dir(Ea, 0x76, Bot, High).pos(1), create_door(player, 'Swamp Waterfall Room SW', Nrml).dir(So, 0x66, Left, Low).toggler().pos(1), create_door(player, 'Swamp Waterfall Room NW', Intr).dir(No, 0x66, Left, Low).pos(3), @@ -514,15 +514,15 @@ def create_doors(world, player): create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0), create_door(player, 'Skull Back Drop Star Path', Lgcl), create_door(player, 'Skull 3 Lobby NW', Nrml).dir(No, 0x59, Left, High).small_key().pos(0), - create_door(player, 'Skull 3 Lobby WN', Intr).dir(We, 0x59, Top, High).pos(2), - create_door(player, 'Skull East Bridge EN', Intr).dir(Ea, 0x59, Top, High).pos(2), - create_door(player, 'Skull East Bridge ES', Intr).dir(Ea, 0x59, Bot, High).pos(3), - create_door(player, 'Skull West Bridge Nook WS', Intr).dir(We, 0x59, Bot, High).pos(3), + create_door(player, 'Skull 3 Lobby EN', Intr).dir(Ea, 0x59, Top, High).pos(2), + create_door(player, 'Skull East Bridge WN', Intr).dir(We, 0x59, Top, High).pos(2), + create_door(player, 'Skull East Bridge WS', Intr).dir(We, 0x59, Bot, High).pos(3), + create_door(player, 'Skull West Bridge Nook ES', Intr).dir(Ea, 0x59, Bot, High).pos(3), create_door(player, 'Skull Star Pits SW', Nrml).dir(So, 0x49, Left, High).small_key().pos(2), - create_door(player, 'Skull Star Pits WS', Intr).dir(We, 0x49, Bot, High).pos(3), - create_door(player, 'Skull Torch Room ES', Intr).dir(Ea, 0x49, Bot, High).pos(3), - create_door(player, 'Skull Torch Room EN', Intr).dir(Ea, 0x49, Top, High).pos(1), - create_door(player, 'Skull Vines WN', Intr).dir(We, 0x49, Top, High).pos(1), + create_door(player, 'Skull Star Pits ES', Intr).dir(Ea, 0x49, Bot, High).pos(3), + create_door(player, 'Skull Torch Room WS', Intr).dir(We, 0x49, Bot, High).pos(3), + create_door(player, 'Skull Torch Room WN', Intr).dir(We, 0x49, Top, High).pos(1), + create_door(player, 'Skull Vines EN', Intr).dir(Ea, 0x49, Top, High).pos(1), create_door(player, 'Skull Vines NW', Nrml).dir(No, 0x49, Left, High).pos(0), create_door(player, 'Skull Spike Corner SW', Nrml).dir(So, 0x39, Left, High).no_exit().trap(0x4).pos(0), create_door(player, 'Skull Spike Corner ES', Intr).dir(Ea, 0x39, Bot, High).small_key().pos(1), @@ -701,7 +701,7 @@ def create_doors(world, player): create_door(player, 'Mire Lobby Gap', Lgcl), create_door(player, 'Mire Post-Gap Gap', Lgcl), - create_door(player, 'Mire Post-Gap Down Stairs', Sprl).dir(Up, 0x98, 0, HTH).ss(X, 0x11, 0x90, False, True), + create_door(player, 'Mire Post-Gap Down Stairs', Sprl).dir(Dn, 0x98, 0, HTH).ss(X, 0x11, 0x90, False, True), create_door(player, 'Mire 2 Up Stairs', Sprl).dir(Up, 0xd2, 0, HTH).ss(X, 0x1a, 0x7c, False, True), create_door(player, 'Mire 2 NE', Nrml).dir(No, 0xd2, Right, High).trap(0x4).pos(0), create_door(player, 'Mire Hub SE', Nrml).dir(So, 0xc2, Right, High).pos(5), @@ -900,7 +900,7 @@ def create_doors(world, player): create_door(player, 'GT Torch EN', Intr).dir(Ea, 0x8c, Top, High).small_key().pos(2), create_door(player, 'GT Hope Room WN', Intr).dir(We, 0x8c, Top, High).small_key().pos(2), create_door(player, 'GT Torch SW', Intr).dir(So, 0x8c, Left, High).no_exit().pos(1), - create_door(player, 'GT Big Chest NW', Intr).dir(So, 0x8c, Left, High).pos(1), + create_door(player, 'GT Big Chest NW', Intr).dir(No, 0x8c, Left, High).pos(1), create_door(player, 'GT Blocked Stairs Down Stairs', Sprl).dir(Dn, 0x8c, 3, HTH).ss(Z, 0x12, 0x40, True, True).kill(), create_door(player, 'GT Blocked Stairs Block Path', Lgcl), create_door(player, 'GT Big Chest SW', Nrml).dir(So, 0x8c, Left, High).pos(4), diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index a6e5d07c..6f0bfdd7 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -8,6 +8,7 @@ import sys from Main import main from Utils import is_bundled, close_console, output_path +from Fill import FillError class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -303,12 +304,22 @@ def start(): guiMain(args) elif args.count is not None: seed = args.seed + failures = [] + logger = logging.getLogger('') for _ in range(args.count): - main(seed=seed, args=args) + try: + main(seed=seed, args=args) + logger.info('Finished run %s', _+1) + except (FillError, Exception, RuntimeError) as err: + failures.append((err, seed)) + logger.warning('Generation failed: %s', err) seed = random.randint(0, 999999999) - logging.getLogger('').info('Finished run %s', _) + for fail in failures: + logger.info('%s seed failed with: %s', fail[1], fail[0]) + logger.info('Generation fail rate: %f%%', 100*len(failures)/args.count) else: main(seed=args.seed, args=args) + if __name__ == '__main__': start() diff --git a/Fill.py b/Fill.py index bfc1145e..1dd4f6ab 100644 --- a/Fill.py +++ b/Fill.py @@ -190,9 +190,17 @@ def fill_restrictive(world, base_state, locations, itempool): for item_to_place in items_to_place: spot_to_fill = None for location in locations: - if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check): + if item_to_place.key: # a better test to see if a key can go there + location.item = item_to_place + test_state = maximum_exploration_state.copy() + test_state.stale[item_to_place.player] = True + else: + test_state = maximum_exploration_state + if location.can_fill(test_state, item_to_place, perform_access_check): spot_to_fill = location break + elif item_to_place.key: + location.item = None if spot_to_fill is None: # we filled all reachable spots. Maybe the game can be beaten anyway? diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 7d76a0ab..217a6e9f 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -162,6 +162,13 @@ def queue_sorter(queue_item): return 1 if door.bigKey else 0 +def queue_sorter_2(queue_item): + door, counter, key_only = queue_item + if door is None: + return 0 + return 1 if door.bigKey else 0 + + def find_bk_locked_sections(key_layout, world): key_counters = key_layout.key_counters key_logic = key_layout.key_logic @@ -547,13 +554,13 @@ def flatten_pair_list(paired_list): def check_rules(original_counter, key_layout): all_key_only = set() key_only_map = {} - queue = collections.deque([(None, original_counter)]) + queue = collections.deque([(None, original_counter, original_counter.key_only_locations)]) completed = set() completed.add(cid(original_counter, key_layout)) while len(queue) > 0: - queue = collections.deque(sorted(queue, key=queue_sorter)) - access_door, counter = queue.popleft() - for loc in counter.key_only_locations: + queue = collections.deque(sorted(queue, key=queue_sorter_2)) + access_door, counter, key_only_loc = queue.popleft() + for loc in key_only_loc: if loc not in all_key_only: all_key_only.add(loc) access_rules = [] @@ -561,16 +568,20 @@ def check_rules(original_counter, key_layout): else: access_rules = key_only_map[loc] if access_door is None or access_door.name not in key_layout.key_logic.door_rules.keys(): - access_rules.append(DoorRules(0)) + if access_door is None or not access_door.bigKey: + access_rules.append(DoorRules(0)) else: - access_rules.append(key_layout.key_logic.door_rules[access_door.name]) + rule = key_layout.key_logic.door_rules[access_door.name] + if rule not in access_rules: + access_rules.append(rule) for child in counter.child_doors.keys(): if not child.bigKey or not key_layout.big_key_special or counter.big_key_opened: next_counter = find_next_counter(child, counter, key_layout) c_id = cid(next_counter, key_layout) if c_id not in completed: completed.add(c_id) - queue.append((child, next_counter)) + new_key_only = dict_difference(next_counter.key_only_locations, counter.key_only_locations) + queue.append((child, next_counter, new_key_only)) min_rule_bk = defaultdict(list) min_rule_non_bk = defaultdict(list) check_non_bk = False diff --git a/Regions.py b/Regions.py index 134d97db..5d27d312 100644 --- a/Regions.py +++ b/Regions.py @@ -432,12 +432,12 @@ def create_regions(world, player): create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', None, ['Skull Back Drop Star Path', ]), create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull Woods Second Section Exit (West)']), create_dungeon_region(player, 'Skull X Room', 'Skull Woods', None, ['Skull X Room SW']), - create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby WN', 'Skull Woods Final Section Exit']), - create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge EN', 'Skull East Bridge ES']), - create_dungeon_region(player, 'Skull West Bridge Nook', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull West Bridge Nook WS']), - create_dungeon_region(player, 'Skull Star Pits', 'Skull Woods', None, ['Skull Star Pits SW', 'Skull Star Pits WS']), - create_dungeon_region(player, 'Skull Torch Room', 'Skull Woods', None, ['Skull Torch Room ES', 'Skull Torch Room EN']), - create_dungeon_region(player, 'Skull Vines', 'Skull Woods', None, ['Skull Vines WN', 'Skull Vines NW']), + create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby EN', 'Skull Woods Final Section Exit']), + create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge WN', 'Skull East Bridge WS']), + create_dungeon_region(player, 'Skull West Bridge Nook', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull West Bridge Nook ES']), + create_dungeon_region(player, 'Skull Star Pits', 'Skull Woods', None, ['Skull Star Pits SW', 'Skull Star Pits ES']), + create_dungeon_region(player, 'Skull Torch Room', 'Skull Woods', None, ['Skull Torch Room WS', 'Skull Torch Room WN']), + create_dungeon_region(player, 'Skull Vines', 'Skull Woods', None, ['Skull Vines EN', 'Skull Vines NW']), create_dungeon_region(player, 'Skull Spike Corner', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop'], ['Skull Spike Corner SW', 'Skull Spike Corner ES']), create_dungeon_region(player, 'Skull Final Drop', 'Skull Woods', None, ['Skull Final Drop WS', 'Skull Final Drop Hole']), create_dungeon_region(player, 'Skull Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), diff --git a/Rules.py b/Rules.py index 2f24ea90..39647bbb 100644 --- a/Rules.py +++ b/Rules.py @@ -324,7 +324,7 @@ def global_rules(world, player): set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', 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 EN', player), lambda state: state.has('Fire Rod', player)) + set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player))