From 5614dea2b51703f9bdd844930ed9638e6e494e25 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 26 Aug 2021 15:21:10 -0600 Subject: [PATCH 01/10] Fix can_beat_game error Add start_region awareness to door finder combinations Added dungeon table --- BaseClasses.py | 2 -- DoorShuffle.py | 8 ++++++++ Dungeons.py | 44 ++++++++++++++++++++++++++++++++------------ KeyDoorShuffle.py | 13 +++++++------ 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b9b3dd1c..f92203d9 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -438,8 +438,6 @@ class World(object): return True state = starting_state.copy() else: - if self.has_beaten_game(self.state): - return True state = CollectionState(self) if self.has_beaten_game(state): diff --git a/DoorShuffle.py b/DoorShuffle.py index 3ed21895..ca377c65 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1456,6 +1456,14 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True random.shuffle(sample_list) proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num) + # eliminate start region if portal marked as destination + excluded = {} + for region in start_regions: + portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None) + if portal and portal.destination: + excluded[region] = None + start_regions = [x for x in start_regions if x not in excluded.keys()] + key_layout = build_key_layout(builder, start_regions, proposal, world, player) while not validate_key_layout(key_layout, world, player): itr += 1 diff --git a/Dungeons.py b/Dungeons.py index 73f53794..6fe38cfb 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -375,6 +375,38 @@ flexible_starts = { 'Skull Woods': ['Skull Left Drop', 'Skull Pinball'] } + +class DungeonInfo: + + def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize=None): + # todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name): + self.free_items = free + self.key_num = keys + self.bk_present = bk + self.map_present = map + self.compass_present = compass + self.bk_drops = bk_drop + self.key_drops = drops + self.prize = prize + + +dungeon_table = { + 'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None), + 'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize'), + 'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize'), + 'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize'), + 'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None), + 'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize'), + 'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize'), + 'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize'), + 'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize"), + 'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize'), + 'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize'), + 'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize'), + 'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None), +} + + dungeon_keys = { 'Hyrule Castle': 'Small Key (Escape)', 'Eastern Palace': 'Small Key (Eastern Palace)', @@ -407,18 +439,6 @@ dungeon_bigs = { 'Ganons Tower': 'Big Key (Ganons Tower)' } -dungeon_prize = { - 'Eastern Palace': 'Eastern Palace - Prize', - 'Desert Palace': 'Desert Palace - Prize', - 'Tower of Hera': 'Tower of Hera - Prize', - 'Palace of Darkness': 'Palace of Darkness - Prize', - 'Swamp Palace': 'Swamp Palace - Prize', - 'Skull Woods': 'Skull Woods - Prize', - 'Thieves Town': 'Thieves Town - Prize', - 'Ice Palace': 'Ice Palace - Prize', - 'Misery Mire': 'Misery Mire - Prize', - 'Turtle Rock': 'Turtle Rock - Prize', -} dungeon_hints = { 'Hyrule Castle': 'in Hyrule Castle', diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index cf18f4f4..c5aab0fc 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -4,8 +4,9 @@ from collections import defaultdict, deque from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType from Regions import dungeon_events -from Dungeons import dungeon_keys, dungeon_bigs, dungeon_prize -from DungeonGenerator import ExplorationState, special_big_key_doors +from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table +from DungeonGenerator import ExplorationState, special_big_key_doors, count_locations_exclude_big_chest, prize_or_event +from DungeonGenerator import reserved_location, blind_boss_unavail class KeyLayout(object): @@ -1387,7 +1388,7 @@ def validate_key_layout(key_layout, world, player): dungeon_entrance, portal_door = find_outside_connection(region) if (len(key_layout.start_regions) > 1 and dungeon_entrance and dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower', 'Pyramid Fairy'] - and key_layout.key_logic.dungeon in dungeon_prize): + and dungeon_table[key_layout.key_logic.dungeon].prize): state.append_door_to_list(portal_door, state.prize_doors) state.prize_door_set[portal_door] = dungeon_entrance key_layout.prize_relevant = True @@ -1550,7 +1551,7 @@ def create_key_counters(key_layout, world, player): dungeon_entrance, portal_door = find_outside_connection(region) if (len(key_layout.start_regions) > 1 and dungeon_entrance and dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower', 'Pyramid Fairy'] - and key_layout.key_logic.dungeon in dungeon_prize): + and dungeon_table[key_layout.key_logic.dungeon].prize): state.append_door_to_list(portal_door, state.prize_doors) state.prize_door_set[portal_door] = dungeon_entrance key_layout.prize_relevant = True @@ -1975,8 +1976,8 @@ def validate_key_placement(key_layout, world, player): len(counter.key_only_locations) + keys_outside if key_layout.prize_relevant: found_prize = any(x for x in counter.important_locations if '- Prize' in x.name) - if not found_prize and key_layout.sector.name in dungeon_prize: - prize_loc = world.get_location(dungeon_prize[key_layout.sector.name], player) + if not found_prize and dungeon_table[key_layout.sector.name].prize: + prize_loc = world.get_location(dungeon_table[key_layout.sector.name].prize, player) # todo: pyramid fairy only care about crystals 5 & 6 found_prize = 'Crystal' not in prize_loc.item.name else: From 1f884649d1dd9bcf1d735f74e639f91e202645f3 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 26 Aug 2021 15:32:35 -0600 Subject: [PATCH 02/10] Remove unnecessary imports --- KeyDoorShuffle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index c5aab0fc..be4f03a2 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -5,8 +5,7 @@ from collections import defaultdict, deque from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType from Regions import dungeon_events from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table -from DungeonGenerator import ExplorationState, special_big_key_doors, count_locations_exclude_big_chest, prize_or_event -from DungeonGenerator import reserved_location, blind_boss_unavail +from DungeonGenerator import ExplorationState, special_big_key_doors class KeyLayout(object): From 759e9979da18e5038a63ffe7d3f5e70013709aa2 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 26 Aug 2021 15:51:12 -0600 Subject: [PATCH 03/10] Rule fix for bigkey shuffle, use the always_allow for the big key, but require it for every other item --- Rules.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Rules.py b/Rules.py index 28d627da..3114bde0 100644 --- a/Rules.py +++ b/Rules.py @@ -1950,9 +1950,11 @@ def add_key_logic_rules(world, player): forbid_item(location, d_logic.small_key_name, player) for door in d_logic.bk_doors: add_rule(world.get_entrance(door.name, player), create_rule(d_logic.bk_name, player)) - if len(d_logic.bk_doors) > 0 or len(d_logic.bk_chests) > 1: - for chest in d_logic.bk_chests: - add_rule(world.get_location(chest.name, player), create_rule(d_logic.bk_name, player)) + for chest in d_logic.bk_chests: + big_chest = world.get_location(chest.name, player) + add_rule(big_chest, create_rule(d_logic.bk_name, player)) + if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1: + set_always_allow(big_chest, lambda state, item: item.name == d_logic.bk_name and item.player == player) if world.retro[player]: for d_name, layout in world.key_layout[player].items(): for door in layout.flat_prop: From 9e7223795f7205edede0354e9dba754e3424aade Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 27 Aug 2021 15:03:27 -0600 Subject: [PATCH 04/10] Fix for path checking. should get pinball more often --- DoorShuffle.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index ca377c65..39d6283f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1915,14 +1915,18 @@ def check_required_paths(paths, world, player): if dungeon_name in world.dungeon_layouts[player].keys(): builder = world.dungeon_layouts[player][dungeon_name] if len(paths[dungeon_name]) > 0: - states_to_explore = defaultdict(list) + states_to_explore = {} for path in paths[dungeon_name]: if type(path) is tuple: - states_to_explore[tuple([path[0]])] = path[1] + states_to_explore[tuple([path[0]])] = (path[1], 'any') else: - states_to_explore[tuple(builder.path_entrances)].append(path) + common_starts = tuple(builder.path_entrances) + if common_starts not in states_to_explore: + states_to_explore[common_starts] = ([], 'all') + states_to_explore[common_starts][0].append(path) cached_initial_state = None - for start_regs, dest_regs in states_to_explore.items(): + for start_regs, info in states_to_explore.items(): + dest_regs, path_type = info if type(dest_regs) is not list: dest_regs = [dest_regs] check_paths = convert_regions(dest_regs, world, player) @@ -1939,11 +1943,17 @@ def check_required_paths(paths, world, player): cached_initial_state = state else: state = cached_initial_state - valid, bad_region = check_if_regions_visited(state, check_paths) + if path_type == 'any': + valid, bad_region = check_if_any_regions_visited(state, check_paths) + else: + valid, bad_region = check_if_all_regions_visited(state, check_paths) if not valid: if check_for_pinball_fix(state, bad_region, world, player): explore_state(state, world, player) - valid, bad_region = check_if_regions_visited(state, check_paths) + if path_type == 'any': + valid, bad_region = check_if_any_regions_visited(state, check_paths) + else: + valid, bad_region = check_if_all_regions_visited(state, check_paths) if not valid: raise Exception('%s cannot reach %s' % (dungeon_name, bad_region.name)) @@ -1983,7 +1993,7 @@ def explore_state_not_inaccessible(state, world, player): state.add_all_doors_check_unattached(connect_region, world, player) -def check_if_regions_visited(state, check_paths): +def check_if_any_regions_visited(state, check_paths): valid = False breaking_region = None for region_target in check_paths: @@ -1995,6 +2005,13 @@ def check_if_regions_visited(state, check_paths): return valid, breaking_region +def check_if_all_regions_visited(state, check_paths): + for region_target in check_paths: + if not state.visited_at_all(region_target): + return False, region_target + return True, None + + def check_for_pinball_fix(state, bad_region, world, player): pinball_region = world.get_region('Skull Pinball', player) # todo: lobby shuffle From 91fbcd38dad5f700fc73740a03cfa49bd86f7e4e Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 27 Aug 2021 15:13:37 -0600 Subject: [PATCH 05/10] Fix shop hints in ER modes where shops are not located vanilla --- ItemList.py | 10 ++++++++++ Regions.py | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ItemList.py b/ItemList.py index 5f5b5e7e..eda7291f 100644 --- a/ItemList.py +++ b/ItemList.py @@ -604,6 +604,7 @@ def customize_shops(world, player): upgrade.location = loc change_shop_items_to_rupees(world, player, shops_to_customize) balance_prices(world, player) + check_hints(world, player) def randomize_price(price): @@ -707,6 +708,15 @@ def balance_prices(world, player): # loc.parent_region.shop.inventory[slot]['price'] = new_price +def check_hints(world, player): + if world.shuffle[player] in ['simple', 'restricted', 'full', 'crossed', 'insanity']: + for shop, location_list in shop_to_location_table.items(): + if shop in ['Capacity Upgrade', 'Light World Death Mountain Shop', 'Potion Shop']: + continue # near the queen, near potions, and near 7 chests are fine + for loc_name in location_list: # other shops are indistinguishable in ER + world.get_location(loc_name, player).hint_text = f'for sale' + + repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart', 'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion'] diff --git a/Regions.py b/Regions.py index 35a7eda3..546cd49b 100644 --- a/Regions.py +++ b/Regions.py @@ -1374,9 +1374,9 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), 'Red Shield Shop - Left': (None, None, False, 'for sale as a curiosity'), 'Red Shield Shop - Middle': (None, None, False, 'for sale as a curiosity'), 'Red Shield Shop - Right': (None, None, False, 'for sale as a curiosity'), - 'Potion Shop - Left': (None, None, False, 'for sale near the witch'), - 'Potion Shop - Middle': (None, None, False, 'for sale near the witch'), - 'Potion Shop - Right': (None, None, False, 'for sale near the witch'), + 'Potion Shop - Left': (None, None, False, 'for sale near potions'), + 'Potion Shop - Middle': (None, None, False, 'for sale near potions'), + 'Potion Shop - Right': (None, None, False, 'for sale near potions'), } lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int} From 67c4fee636b4fafb661aa612e5d8454e2b70db60 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 30 Aug 2021 15:20:27 -0600 Subject: [PATCH 06/10] Boss shuffle fix - fixes some bias discovered by krebel --- Bosses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Bosses.py b/Bosses.py index 2718431e..90acafcf 100644 --- a/Bosses.py +++ b/Bosses.py @@ -181,11 +181,11 @@ def place_bosses(world, player): logging.getLogger('').debug('Bosses chosen %s', bosses) - random.shuffle(bosses) for [loc, level] in boss_locations: loc_text = loc + (' ('+level+')' if level else '') - boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None) - if not boss: + try: + boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)]) + except IndexError: raise FillError('Could not place boss for location %s' % loc_text) bosses.remove(boss) From 07287d85a765440e539f0e741ba937de6f4b8fe4 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 30 Aug 2021 15:21:25 -0600 Subject: [PATCH 07/10] Improve exclusion calculation --- BaseClasses.py | 1 + DoorShuffle.py | 2 +- DungeonGenerator.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f92203d9..820b2fed 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -77,6 +77,7 @@ class World(object): self._room_cache = {} self.dungeon_layouts = {} self.inaccessible_regions = {} + self.enabled_entrances = {} self.key_logic = {} self.pool_adjustment = {} self.key_layout = defaultdict(dict) diff --git a/DoorShuffle.py b/DoorShuffle.py index 39d6283f..e89a7bb4 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -753,7 +753,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player): entrances_map, potentials, connections = connections_tuple - enabled_entrances = {} + enabled_entrances = world.enabled_entrances[player] = {} sector_queue = deque(dungeon_builders.values()) last_key, loops = None, 0 logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon")) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 948115f3..f579f285 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -109,7 +109,8 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon p_region = portal.door.entrance.connected_region access_region = next(x.parent_region for x in p_region.entrances if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]) - if access_region.name in world.inaccessible_regions[player]: + if (access_region.name in world.inaccessible_regions[player] and + region.name not in world.enabled_entrances[player]): excluded[region] = None entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] doors_to_connect = {} From dad6016498ea22ae9f5591c24498a8caa8e5e39d Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 30 Aug 2021 15:57:45 -0600 Subject: [PATCH 08/10] Rom features: update cutofftable Add a multiworld ram slot for item that just got grabbed --- Rom.py | 2 +- asm/doortables.asm | 2 ++ data/base2current.bps | Bin 136271 -> 136284 bytes 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index d39675db..e2eeb5f1 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '988f1546b14d8f2e6ee30b9de44882da' +RANDOMIZERBASEHASH = 'f0947723c296ce13c7f4c909d8318042' class JsonRom(object): diff --git a/asm/doortables.asm b/asm/doortables.asm index 8ee0dd60..eae16430 100644 --- a/asm/doortables.asm +++ b/asm/doortables.asm @@ -582,6 +582,8 @@ dw $00bc, $00a2, $00a3, $00c2, $001a, $0049, $0014, $008c dw $009f, $0066, $005d, $00a8, $00a9, $00aa, $00b9, $0052 ; HC West Hall, TR Dash Bridge, TR Hub, Pod Arena, GT Petting Zoo, Ice Spike Cross dw $0050, $00c5, $00c6, $0009, $0003, $002a, $007d, $005e +; Sewer Drop, Mire Cross, GT Crystal Circles +dw $0011, $00b2, $003d dw $ffff ; dungeon tables diff --git a/data/base2current.bps b/data/base2current.bps index 5f41171067dccc266f98a3c0b2daee5a07a03f06..ddbb10748c14ecc4470df7ce1e0920cd009f2c01 100644 GIT binary patch delta 668 zcmV;N0%QHps0iGs2(Ur{1PKWC_p?R;Dn0>ClW{*T1dp4*ev_X+H8!z_$xhG?uY2=; z@CB()n*<>CiC)lFlOuq!6t7wHe$SAAFpvtbk)H;DlS^&yk^j&LsY#nOlL2mjwY)lPpeIHaPx?{m>kfAAo@qqgnZZ z36Fx4Ncowm1Z&Cu&^((OfS)J+fenv>lP3O~CH|EIsY7eY0GDEuyG|Smb^EKiffbK} zmtK?MP8b|B`+*OSf|oG*pD+5Y1F0!%sVZx021G?h6M-g^6;CfA6_bG#qd5JWjFGgb zplfKCu7$yBbx;cfQ=bAfK|W3 zlR^xDkw1Vu$zZ=@3xJabvwKhh0}T%ufSCc00Z@N{2AdkQiBkp@2TK)!fq;_}vzBNL z1OYdbq--w%7PHiB5(*aySAo6o1jz!h323PdpaEPN;st0ofDE(!cGEWnaU+kHHj~w^ z@BvAa#;-2{9JBVX6etlXF@dQVZzc48k2g?%uO^>Xvm4k8B>_>BC;cuG0A2tQ0I~o* zFNP`;m3#I9Pl1zN{UQQiKC^-SgMtl#_nRJDmWB8Kn=g2lw-bN?G?WP6-~iF^d{Tlm Cr4)St delta 632 zcmWO3Jxmi}90qXT_IegdOQ00WmnD2D2v3 z4Fi_)*Hm&b#W2WO@i1pX90;J(#eqv)l<4AM;$#zu;lrMG{XNh0Jl~eITT9v%H=@>b z?p@J~>K*6|j5`@vbhflCKazT7gK&2zoYL?a5r4HkwJC z>#PogLBxKf))y7yc@#zdDNJ*f;^RKrbhOVz2nL Date: Tue, 31 Aug 2021 14:10:47 -0600 Subject: [PATCH 09/10] Minor rom fix --- Rom.py | 2 +- data/base2current.bps | Bin 136284 -> 136284 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index e2eeb5f1..53d6666e 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'f0947723c296ce13c7f4c909d8318042' +RANDOMIZERBASEHASH = '091671c7f555331ff2f411b304141c29' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index ddbb10748c14ecc4470df7ce1e0920cd009f2c01..4571d623111492b36542f9572a826fd4ecbbef1d 100644 GIT binary patch delta 57 zcmV-90LK5^s0iGs2(U5D0acSS%_JAEJAg34An*#WX!CwQslBA|7K)b}I{Sld&9`mM P0p~^tEZfI>QeH Date: Tue, 31 Aug 2021 15:36:38 -0600 Subject: [PATCH 10/10] Couple of big key logic fixes - one with bk forced Another with bk assumed to be found early in dungeon Catobat map check fix from rom --- BaseClasses.py | 1 + KeyDoorShuffle.py | 15 +++++++++++++-- Main.py | 2 +- RELEASENOTES.md | 11 +++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 820b2fed..b5462eb4 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -142,6 +142,7 @@ class World(object): set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}) set_player_attr('exp_cache', defaultdict(dict)) + set_player_attr('enabled_entrances', {}) def get_name_string_for_object(self, obj): return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index be4f03a2..23dd24be 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -282,6 +282,12 @@ def analyze_dungeon(key_layout, world, player): if not big_avail: if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs: key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations)) + # note to self: this is due to the enough_small_locations function in validate_key_layout_sub_loop + # I don't like this exception here or there + elif available <= possible_smalls and avail_bigs and non_big_locs > 0: + max_ctr = find_max_counter(key_layout) + bk_lockdown = [x for x in max_ctr.free_locations if x not in key_counter.free_locations] + key_logic.bk_restricted.update(filter_big_chest(bk_lockdown)) # try to relax the rules here? - smallest requirement that doesn't force a softlock child_queue = deque() for child in key_counter.child_doors.keys(): @@ -1619,8 +1625,13 @@ def can_open_door_by_counter(door, counter: KeyCounter, layout, world, player): # ttl_small_key_only = len(counter.key_only_locations) return cnt_avail_small_locations_by_ctr(ttl_locations, counter, layout, world, player) > 0 elif door.bigKey: - available_big_locations = cnt_avail_big_locations_by_ctr(ttl_locations, counter, layout, world, player) - return not counter.big_key_opened and available_big_locations > 0 and not layout.big_key_special + if counter.big_key_opened: + return False + if layout.big_key_special: + return any(x for x in counter.other_locations.keys() if x.forced_item and x.forced_item.bigkey) + else: + available_big_locations = cnt_avail_big_locations_by_ctr(ttl_locations, counter, layout, world, player) + return available_big_locations > 0 else: return True diff --git a/Main.py b/Main.py index ee9e83fb..be2cb2e6 100644 --- a/Main.py +++ b/Main.py @@ -28,7 +28,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.5.1.0-u' +__version__ = '0.5.1.1-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 37877c89..4d212275 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,17 @@ CLI: ```--bombbag``` # Bug Fixes and Notes. +* 0.5.1.1 + * Shop hints in ER are now more generic instead of using "near X" because they aren't near that anymore + * Added memory location for mutliworld scripts to read what item was just obtain (longer than one frame) + * Fix for bias in boss shuffle "full" + * Fix for certain lone big chests in keysanity (allowed you to get contents without big key) + * Fix for pinball checking + * Fix for multi-entrance dungeons + * 2 fixes for big key placement logic + * ensure big key is placed early if the validator assumes it) + * Open big key doors appropriately when generating rules and big key is forced somewhere + * Updated cutoff entrances for intensity 3 * 0.5.1.0 * Large logic refactor introducing a new method of key logic * Some performance optimization