From 6076441fbfcbc9c143547856bfe181a8139fe81a Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 4 Mar 2022 16:45:51 -0700 Subject: [PATCH 01/83] Fix some interior doors during key door identification as missing one reachable side --- DoorShuffle.py | 15 ++++++++++----- Main.py | 2 +- RELEASENOTES.md | 2 ++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 3d3a6eda..8c0b8e25 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -14,6 +14,7 @@ from RoomData import DoorKind, PairedDoor, reset_rooms from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException +from DungeonGenerator import valid_region_to_explore as valid_region_to_explore_lim from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock from Utils import ncr, kth_combination @@ -1368,6 +1369,8 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map): dungeon_builders[recombine.name] = recombine +# todo: this allows cross-dungeon exploring via HC Ledge or Inaccessible Regions +# todo: @deprecated def valid_region_to_explore(region, world, player): return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player] @@ -1559,7 +1562,7 @@ okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind. def find_key_door_candidates(region, checked, world, player): - dungeon = region.dungeon + dungeon_name = region.dungeon.name candidates = [] checked_doors = list(checked) queue = deque([(region, None, None)]) @@ -1569,14 +1572,16 @@ def find_key_door_candidates(region, checked, world, player): d = ext.door if d and d.controller: d = d.controller - if d and not d.blocked and not d.entranceFlag and d.dest is not last_door and d.dest is not last_region and d not in checked_doors: + if d and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors: valid = False - if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]: + if (0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs] + and not d.entranceFlag): room = world.get_room(d.roomIndex, player) position, kind = room.doorList[d.doorListPos] - if d.type == DoorType.Interior: valid = kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable] + if valid and d.dest not in candidates: # interior doors are not separable yet + candidates.append(d.dest) elif d.type == DoorType.SpiralStairs: valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow] elif d.type == DoorType.Normal: @@ -1595,7 +1600,7 @@ def find_key_door_candidates(region, checked, world, player): if valid and d not in candidates: candidates.append(d) connected = ext.connected_region - if connected and (connected.type != RegionType.Dungeon or connected.dungeon == dungeon): + if valid_region_to_explore_lim(connected, dungeon_name, world, player): queue.append((ext.connected_region, d, current)) if d is not None: checked_doors.append(d) diff --git a/Main.py b/Main.py index 573f935f..7dc012e7 100644 --- a/Main.py +++ b/Main.py @@ -29,7 +29,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__ = '1.0.0-dev' +__version__ = '1.0.1-dev' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a224c7c..b59df407 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,8 @@ CLI: ```--bombbag``` # Bug Fixes and Notes. +* 1.0.1 + * Fixed a bug with key doors not detecting one side of an interior door * 0.5.1.7 * Baserom update * Fix for Inverted Mode: Dark Lake Hylia shop defaults to selling a blue potion From 481516cc4c2af6644560d8a8cef6f7bee568ef80 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 24 Feb 2022 14:39:45 -0700 Subject: [PATCH 02/83] SSL not verified for alttpr.com/sprites --- source/classes/SpriteSelector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/classes/SpriteSelector.py b/source/classes/SpriteSelector.py index cb76e64c..9a18cacd 100644 --- a/source/classes/SpriteSelector.py +++ b/source/classes/SpriteSelector.py @@ -4,6 +4,7 @@ import json import os import random import shutil +import ssl from urllib.parse import urlparse from urllib.request import urlopen import webbrowser @@ -149,7 +150,7 @@ class SpriteSelector(object): try: task.update_status("Downloading official sprites list") - with urlopen('https://alttpr.com/sprites') as response: + with urlopen('https://alttpr.com/sprites', context=ssl._create_unverified_context()) as response: sprites_arr = json.loads(response.read().decode("utf-8")) except Exception as e: resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) From f9fdd6ede92c22b3b672b474f545c392331f0c3b Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 4 Mar 2022 16:46:46 -0700 Subject: [PATCH 03/83] Note for fix --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b59df407..4c3ec552 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,6 +17,7 @@ CLI: ```--bombbag``` * 1.0.1 * Fixed a bug with key doors not detecting one side of an interior door + * Sprite selector fix for systems with SSL issues * 0.5.1.7 * Baserom update * Fix for Inverted Mode: Dark Lake Hylia shop defaults to selling a blue potion From 3df1072f0aa8cb8d70b5580d097a2bc83367f8ff Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 21 Jan 2022 16:15:51 -0700 Subject: [PATCH 04/83] Fix bombbag bug --- RELEASENOTES.md | 2 ++ Rules.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4c3ec552..28e4f5d7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,8 @@ CLI: ```--bombbag``` # Bug Fixes and Notes. +* 1.0.0.1 + * Add Light Hype Fairy to bombbag mode as needing bombs * 1.0.1 * Fixed a bug with key doors not detecting one side of an interior door * Sprite selector fix for systems with SSL issues diff --git a/Rules.py b/Rules.py index 83a2175b..c2bb27ca 100644 --- a/Rules.py +++ b/Rules.py @@ -568,7 +568,7 @@ def bomb_rules(world, player): bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended. bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Mini Moldorm Cave', 'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front','Hookshot Cave Middle to Back', - 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery'] + 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery', 'Light Hype Fairy'] for entrance in bonkable_doors: add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) for entrance in bombable_doors: From 42c6e18655e1076ea1f50a647e459aa1f1713aa0 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 24 May 2022 09:36:52 -0600 Subject: [PATCH 05/83] Mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..d8857508 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ + \ No newline at end of file From 752c063a9abba5aaf3b20d52d266b8160fb5fe45 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 24 May 2022 10:00:40 -0600 Subject: [PATCH 06/83] Mailmap fix --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index d8857508..9f3cc0db 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 3052bd75858428884edc8920e9b1e72daf7498f1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 25 May 2022 10:28:15 -0600 Subject: [PATCH 07/83] Spelling --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 9f3cc0db..1aecc0db 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 4f5a41d0406e8a4d7ba3e17d35485a76c71335e7 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 16 Sep 2022 10:41:18 -0600 Subject: [PATCH 08/83] Change customizer item pool to warn of missing items --- ItemList.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ItemList.py b/ItemList.py index f2500e07..c1deb8db 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1114,37 +1114,35 @@ def make_customizer_pool(world, player): place_item('Master Sword Pedestal', 'Triforce') guaranteed_items = alwaysitems + ['Magic Mirror', 'Moon Pearl'] + missing_items = [] if world.shopsanity[player]: guaranteed_items.extend(['Blue Potion', 'Green Potion', 'Red Potion']) if world.retro[player]: guaranteed_items.append('Small Key (Universal)') for item in guaranteed_items: if item not in pool: - pool.append(item) + missing_items.append(item) glove_count = sum(1 for i in pool if i == 'Progressive Glove') glove_count = 2 if next((i for i in pool if i == 'Titans Glove'), None) is not None else glove_count for i in range(glove_count, 2): - pool.append('Progressive Glove') + missing_items.append('Progressive Glove') if world.bombbag[player]: if 'Bomb Upgrade (+10)' not in pool: - pool.append('Bomb Upgrade (+10)') + missing_items.append('Bomb Upgrade (+10)') if world.swords[player] != 'swordless': beam_swords = {'Master Sword', 'Tempered Sword', 'Golden Sword'} sword_count = sum(1 for i in pool if i in 'Progressive Sword') sword_count = 2 if next((i for i in pool if i in beam_swords), None) is not None else sword_count for i in range(sword_count, 2): - pool.append('Progressive Sword') + missing_items.append('Progressive Sword') bow_found = next((i for i in pool if i in {'Bow', 'Progressive Bow'}), None) if not bow_found: - pool.append('Progressive Bow') - - heart_found = next((i for i in pool if i in {'Boss Heart Container', 'Sanctuary Heart Container'}), None) - if heart_found is None: - pool.append('Boss Heart Container') + missing_items.append('Progressive Bow') + logging.getLogger('').warning(f'The following items are not in the custom item pool {", ".join(missing_items)}') g, t = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], world.treasure_hunt_total[player]) From 513782683742db05b492f320d665f8c7ec40a839 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 16 Sep 2022 13:00:15 -0600 Subject: [PATCH 09/83] Fix github build? --- resources/ci/common/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/ci/common/install.py b/resources/ci/common/install.py index 6830905e..3d0b5965 100644 --- a/resources/ci/common/install.py +++ b/resources/ci/common/install.py @@ -6,7 +6,7 @@ import subprocess # do stuff at the shell level env = common.prepare_env() -pip_requirements = os.path.join("..","resources","app","meta","manifests","pip_requirements.txt") +pip_requirements = os.path.join(".","resources","app","meta","manifests","pip_requirements.txt") if not os.path.isfile(pip_requirements): pip_requirements = os.path.join("..","..","..","resources","app","meta","manifests","pip_requirements.txt") From a9806ec40f52fce69edb631b9cd82471e0311809 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 16 Sep 2022 13:31:26 -0600 Subject: [PATCH 10/83] Minor doc update --- RELEASENOTES.md | 72 +------------------------------------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fab0c559..ba7ac9e0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -179,10 +179,6 @@ Currently bugged, not recommended for use. Same as above but both small keys and bigs keys of the dungeon are not allowed on a boss. (Note: this does not affect universal keys as they are not dungeon-specific) -## Notes and Bug Fixes - -#### Unstable - # Bug Fixes and Notes * 1.1.0 @@ -244,70 +240,4 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Add Light Hype Fairy to bombbag mode as needing bombs * 1.0.1 * Fixed a bug with key doors not detecting one side of an interior door - * Sprite selector fix for systems with SSL issues -* 0.5.1.7 - * Baserom update - * Fix for Inverted Mode: Dark Lake Hylia shop defaults to selling a blue potion - * Fix for Ijwu's enemizer: Boss door in Thieves' Town no longer closes after the maiden hint if Blind is shuffled to Theives' Town in boss shuffle + crossed mode - * No logic now sets the AllowAccidentalMajorGlitches flag in the rom appropriately - * Houlihan room now exits wherever Link's House is shuffled to - * Rom fixes from Catobat and Codemann8. Thanks! -* 0.5.1.6 - * Rules fixes for TT (Boss and Cell) can now have TT Big Key if not otherwise required (boss shuffle + crossed dungeon) - * BUg fix for money balancing - * Add some bomb assumptions for bosses in bombbag mode -* 0.5.1.5 - * Fix for hard pool capacity upgrades missing - * Bonk Fairy (Light) is no longer in logic for ER Standard and is forbidden to be a connector, so rain state isn't exitable - * Bug fix for retro + enemizer and arrows appearing under pots - * Added bombbag and shufflelinks to settings code - * Catobat fixes: - * Fairy refills in spoiler - * Subweights support in mystery - * More defaults for mystery weights - * Less camera jank for straight stair transitions - * Bug with Straight stairs with vanilla doors where Link's walking animation stopped early is fixed -* 0.5.1.4 - * Revert quadrant glitch fix for baserom - * Fix for inverted -* 0.5.1.3 - * Certain lobbies forbidden in standard when rupee bow is enabled - * PoD EG disarmed when mirroring (except in nologic) - * Fixed issue with key logic - * Updated baserom -* 0.5.1.2 - * Allowed Blind's Cell to be shuffled anywhere if Blind is not the boss of Thieves Town - * Remove unique annotation from a FastEnum that was causing problems - * Updated prevent mixed_travel setting to prevent more mixed travel - * Prevent key door loops on the same supertile where you could have spent 2 keys on one logical door - * Promoted dynamic soft-lock prevention on "stonewalls" from experimental to be the primary prevention (Stonewalls are now never pre-opened) - * Fix to money balancing algorithm with small item_pool, thanks Catobat - * Many fixes and refinements to key logic and generation -* 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 - * Some outstanding bug fixes (boss shuffle "full" picks three unique bosses to be duplicated, e.g.) -* 0.5.0.3 - * Fixed a bug in retro+vanilla and big key placement - * Fixed a problem with shops not registering in the Multiclient until you visit one - * Fixed a bug in the Mystery code with sfx -* 0.5.0.2 - * --shuffle_sfx option added -* 0.5.0.1 - * --bombbag option added -* 0.5.0.0 - * Handles headered roms for enemizer (Thanks compiling) - * Warning added for earlier version of python (Thanks compiling) - * Minor logic issue for defeating Aga in standard (Thanks compiling) - * Fix for boss music in non-DR modes (Thanks codemann8) \ No newline at end of file + * Sprite selector fix for systems with SSL issues \ No newline at end of file From 4f2fed48d7be409b9105104ad9886d35704c677b Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 26 Oct 2022 16:46:32 -0600 Subject: [PATCH 11/83] Big key door fix Some generation fixes (need to look at pre-validate some more) Bumped up escape assist values in non-basic shuffles --- DoorShuffle.py | 20 +++++++++++++------- DungeonGenerator.py | 27 +++++++++++++++++---------- Fill.py | 2 +- Rom.py | 8 +++++--- data/base2current.bps | Bin 93438 -> 93445 bytes source/dungeon/DungeonStitcher.py | 22 ++++++++++++---------- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 368aa931..1dea2f99 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1018,7 +1018,8 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ find_standard_origins(builder, recombinant_builders, origin_list) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name) split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player) - if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player): + # todo: figure out pre-validate, ensure all needed origins are enabled? + if len(origin_list) <= 0: # or not pre_validate(builder, origin_list, split_dungeon, world, player): if last_key == builder.name or loops > 1000: origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin' raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}') @@ -1068,14 +1069,14 @@ def determine_entrance_list(world, player): connections = {} for key, portal_list in dungeon_portals.items(): entrance_map[key] = [] - r_names = {} + r_names = [] if key in dungeon_drops.keys(): for drop in dungeon_drops[key]: - r_names[drop] = None + r_names.append((drop, None)) for portal_name in portal_list: portal = world.get_portal(portal_name, player) - r_names[portal.door.entrance.parent_region.name] = portal - for region_name, portal in r_names.items(): + r_names.append((portal.door.entrance.parent_region.name, portal)) + for region_name, portal in r_names: if portal: region = world.get_region(portal.name + ' Portal', player) else: @@ -2160,11 +2161,16 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world, # eliminate start region if portal marked as destination def filter_start_regions(builder, start_regions, world, player): std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' - excluded = {} + excluded = {} # todo: drop lobbies, might be better to white list instead (two entrances per region) 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 + # make sure that a drop is not accessible for this "destination" + drop_region = next((x.parent_region for x in region.entrances + if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] + or x.parent_region.name == 'Sewer Drop'), None) + if not drop_region: + excluded[region] = None if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'): excluded[region] = None return [x for x in start_regions if x not in excluded.keys()] diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f982cb6f..d33db874 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1356,7 +1356,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge sanc_builder = random.choice(lw_builders) assign_sector(sanc, sanc_builder, candidate_sectors, global_pole) - bow_sectors, retro_std_flag = {}, world.bow_mode[player].startswith('retro') and world.mode[player] == 'standard' + retro_std_flag = world.bow_mode[player].startswith('retro') and world.mode[player] == 'standard' + non_hc_sectors = {} free_location_sectors = {} crystal_switches = {} crystal_barriers = {} @@ -1364,7 +1365,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge neutral_sectors = {} for sector in candidate_sectors: if retro_std_flag and 'Bow' in sector.item_logic: # these need to be distributed outside of HC - bow_sectors[sector] = None + non_hc_sectors[sector] = None + elif world.mode[player] == 'standard' and 'Open Floodgate' in sector.item_logic: + non_hc_sectors[sector] = None elif sector.chest_locations > 0: free_location_sectors[sector] = None elif sector.c_switch: @@ -1375,8 +1378,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge neutral_sectors[sector] = None else: polarized_sectors[sector] = None - if bow_sectors: - assign_bow_sectors(dungeon_map, bow_sectors, global_pole) + if non_hc_sectors: + assign_non_hc_sectors(dungeon_map, non_hc_sectors, global_pole) leftover = assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player) free_location_sectors = scatter_extra_location_sectors(dungeon_map, leftover, global_pole) for sector in free_location_sectors: @@ -1420,7 +1423,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge def standard_stair_check(dungeon_map, dungeon, candidate_sectors, global_pole): # this is because there must be at least one non-dead stairway in hc to get out # this check may not be necessary - filtered_sectors = [x for x in candidate_sectors if any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)] + filtered_sectors = [x for x in candidate_sectors if 'Open Floodgate' not in x.item_logic and + any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)] valid = False while not valid: chosen_sector = random.choice(filtered_sectors) @@ -1550,7 +1554,7 @@ def define_sector_features(sectors): sector.bk_required = True for ext in region.exits: door = ext.door - if door is not None: + if door is not None and not door.blocked: if door.crystal == CrystalBarrier.Either: sector.c_switch = True elif door.crystal == CrystalBarrier.Orange: @@ -1562,6 +1566,8 @@ def define_sector_features(sectors): if region.name in ['PoD Mimics 2', 'PoD Bow Statue Right', 'PoD Mimics 1', 'GT Mimics 1', 'GT Mimics 2', 'Eastern Single Eyegore', 'Eastern Duo Eyegores']: sector.item_logic.add('Bow') + if region.name in ['Swamp Lobby', 'Swamp Entrance']: + sector.item_logic.add('Open Floodgate') def assign_sector(sector, dungeon, candidate_sectors, global_pole): @@ -1613,8 +1619,8 @@ def find_sector(r_name, sectors): return None -def assign_bow_sectors(dungeon_map, bow_sectors, global_pole): - sector_list = list(bow_sectors) +def assign_non_hc_sectors(dungeon_map, non_hc_sectors, global_pole): + sector_list = list(non_hc_sectors) random.shuffle(sector_list) population = [] for name in dungeon_map: @@ -1623,7 +1629,7 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole): choices = random.choices(population, k=len(sector_list)) for i, choice in enumerate(choices): builder = dungeon_map[choice] - assign_sector(sector_list[i], builder, bow_sectors, global_pole) + assign_sector(sector_list[i], builder, non_hc_sectors, global_pole) def scatter_extra_location_sectors(dungeon_map, free_location_sectors, global_pole): @@ -3511,7 +3517,8 @@ def check_for_valid_layout(builder, sector_list, builder_info): for portal in world.dungeon_portals[player]: if not portal.destination and portal.name in dungeon_portals[builder.name]: possible_regions.add(portal.door.entrance.parent_region.name) - if builder.name in dungeon_drops.keys(): + if builder.name in dungeon_drops.keys() and (builder.name != 'Hyrule Castle' + or world.mode[player] != 'standard'): possible_regions.update(dungeon_drops[builder.name]) independents = find_independent_entrances(possible_regions, world, player) for name, split_build in builder.split_dungeon_map.items(): diff --git a/Fill.py b/Fill.py index f695bd9a..8487c750 100644 --- a/Fill.py +++ b/Fill.py @@ -289,7 +289,7 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] else: possible_swaps = [x for x in state.locations_checked - if x.item.type not in ['Event', 'Crystal'] and not x.forced_item] + if x.item.type not in ['Event', 'Crystal'] and not x.forced_item and not x.locked] swap_locations = sorted(possible_swaps, key=location_preference) return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, key_pool, single_player_placement) diff --git a/Rom.py b/Rom.py index 18677375..fff0456a 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '61c296effe6180274721d570d2471e1c' +RANDOMIZERBASEHASH = '5639de3bfd500ba238d3f27ea78c19e1' class JsonRom(object): @@ -1492,8 +1492,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.doorShuffle[player] not in ['vanilla', 'basic']: # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) - rom.write_bytes(0x180188, [0x20, 3, 10]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0x20, 3, 10]) # Mantle respawn refills (magic, bombs, arrows) + # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180188, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) + # Mantle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest rom.write_bytes(0x180185, [max(0x00, magic_max), max(3, bomb_max), max(0, bow_max)]) rom.write_bytes(0x180188, [magic_small, 3, bow_small]) # Zelda respawn refills (magic, bombs, arrows) diff --git a/data/base2current.bps b/data/base2current.bps index 03d6471ed59fbc3a0aa2fcb13383c31d53c06f20..3fd78189a58e8bcb70b83f770932c6ebbe1745dc 100644 GIT binary patch delta 867 zcmW-cUu;u#6vyxBy|?YQ(tB_JtwpTAwQg<2kZn$!pk!z@q?9a!MB?8xBsyit!_*gI z#;?WOP{`8WUdq{aL#S*wp(V=}`cOccb(%SdFAK>MTwIhHqYs4iVFWaUdiA`V@A>4L z?>Fb^&%C_G&wm7*^Mm`-l_L;ip{Fv--C<#|Lap^SXjb;Rmfi+`#q$1F+k=x}a|+BB z!2MB%*1*F%UEtywyU@;nozspUU}!~4+xJ3OrM%p`8|t`jf{^v5LU|p?Beb#Lhx@pl zg$_Q7sU)oqu@JY|qKX_SZPGTwM60D%*ujAh@7kgFWQk!$>dl6QJjfSKlqI{=+s%e| zYDmccZD5{c6$AdH5Kpc@;Z6LH%y|Qy$S-{2_t0c}HZ^`oA#wF|L1*RSH|8HYZ`8O5 zU1`xd8OSTA=oX#3_;tEK3OTY>zPRsd<@M_q{xf(DgNkVZ9NyUV)tQ{mk}uK;QpxFr z)N+vlhFEZ30RPV5jU=xf{y6_fxz<>UG?~jfL(-~j(k;eesIt!IIiT8W9qI)WDXTK0 z+weK$MCjc1-gilBHZ{xZ9EmD_)h3tZHuciDEfEp4-T0OWL9-*+PGyS6MF_x6R7B{4 zN7ZX02+(s^rLH=k?V_1qRhqZnn~fRObUjgLm_|5@$snf2pQzM_rzfRzR3LxiV+r<) zJI;-4i0a^ZRoLUEJ=Bhe9B|kiP?CN449+{i*Ah=;H0QC5HimZ`Mr{v1bilBL6WL1` zK28^Bmd9${4TAW&6Y`EhRK=8TVOH4qAeN-?+%c3~K*(|-391Z-&)Bi4!NMyZH>67cHmS}&|Vg%l!lp+3x?zv>&a+b)PdJgitpw6P&a4?vFS=nM@PeMP&TVGXr4?zTyVNd!_~17a zN)}E2zWYtJ?`RT-t3eN>Tj3y<3A`b8Fd603T_eTfobRQa#`RU75HM!A`m_Tc$E@q4 LJ5ygCZ29G1-!Dm} delta 854 zcmW-bZ%i9?7{~AF^=Ly2cm3b)5Bt$>v=uYhUvQd9mxTz{45Ma7GrgHOjkx(jm#Go_ zUD-A5B5Cgq_7pA)h3(wRf=v6OVFtlJ2uplHO!i^~lWoz&#l-YQO+=@7@p@Lq(e`MjSGPN%{;9)u8UFwFdW!uYVTX!sh)59|j z;NTcwJmBX%-rV2WMn0gOIghcS@`&%X=S(dAw(VsC`bMoB^bU0t-#>0NYq- z<`S5a1}ZPGkhD1yiX1N0sKYc3RA%a7w-p+2y%F~I^)GH#ZR9t#XrV0W&~9y3gX7Qg z>V}Cq@`+-?6@_^6onlb@AF-;5&Z!Gxlb=f7Txz&aAw61V)L`Y}`MPg+;7J|=PpWyc zMV>hFMY&_{k~L0OgN9YsoRf03e&RrJlm^KYDN%cH{pj57$-Kdmche%dkv9mrnU8ld z#EKOjnhtKiDRKJo*VSL9=c`NcP3D@xkReUB7&h}T)wIE>R-pR9X6=%NT-9X8aAF7@ z0_@uM{FjnFmzv=WD@iE7&W|n04sB}KDaLvI044>9)Hx#oDi7dU0m3kc1p!*1R#^~$ zhdsA7>Rse=Ei}8XQPqBLrq`^dKZ%PBQ;p?NiePGZL!%+gj5Ur>p8SlPE_hmK9qGR> zXgwD-{;-ekp$>e>4aZ~K6lp*DPG_Y&o|5pRtkiiqHF;=xkamPanJK+n%<2tqXZ82- zj@$gC6(73c4cAB^H-)h?^y@3jgY)fGGJ@}UKy@ck6H Date: Thu, 27 Oct 2022 13:56:37 -0600 Subject: [PATCH 12/83] Bumper cave fix Pottery coloring updated --- EntranceShuffle.py | 12 ++++++++---- InvertedRegions.py | 3 ++- Main.py | 2 +- PotShuffle.py | 18 +++++++++--------- RELEASENOTES.md | 3 +++ Regions.py | 3 ++- Rules.py | 8 ++++---- 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index f67c089b..8ff4fcc8 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -3141,6 +3141,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Bumper Cave Entrance Mirror Spot', 'Death Mountain Entrance'), ('Bumper Cave Ledge Drop', 'West Dark World'), ('Bumper Cave Ledge Mirror Spot', 'Death Mountain Return Ledge'), + ('Bumper Cave Bottom to Top', 'Bumper Cave (top)'), + ('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'), ('Skull Woods Forest', 'Skull Woods Forest'), ('Desert Ledge Mirror Spot', 'Desert Ledge'), ('Desert Ledge (Northeast) Mirror Spot', 'Desert Ledge (Northeast)'), @@ -3269,6 +3271,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Bumper Cave Entrance Rock', 'Bumper Cave Entrance'), ('Bumper Cave Entrance Drop', 'West Dark World'), ('Bumper Cave Ledge Drop', 'West Dark World'), + ('Bumper Cave Bottom to Top', 'Bumper Cave (top)'), + ('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'), ('Skull Woods Forest', 'Skull Woods Forest'), ('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'), ('Paradox Cave Push Block', 'Paradox Cave Front'), @@ -3492,8 +3496,8 @@ default_connections = [('Links House', 'Links House'), ('C-Shaped House', 'C-Shaped House'), ('Chest Game', 'Chest Game'), ('Dark World Hammer Peg Cave', 'Dark World Hammer Peg Cave'), - ('Bumper Cave (Bottom)', 'Bumper Cave'), - ('Bumper Cave (Top)', 'Bumper Cave'), + ('Bumper Cave (Bottom)', 'Bumper Cave (bottom)'), + ('Bumper Cave (Top)', 'Bumper Cave (top)'), ('Red Shield Shop', 'Red Shield Shop'), ('Dark Sanctuary Hint', 'Dark Sanctuary Hint'), ('Fortune Teller (Dark)', 'Fortune Teller (Dark)'), @@ -3655,7 +3659,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Inverted Big Bomb Shop', 'Inverted Big Bomb Shop'), ('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'), ('Inverted Dark Sanctuary Exit', 'West Dark World'), - ('Old Man Cave (West)', 'Bumper Cave'), + ('Old Man Cave (West)', 'Bumper Cave (bottom)'), ('Old Man Cave (East)', 'Death Mountain Return Cave (left)'), ('Old Man Cave Exit (West)', 'West Dark World'), ('Old Man Cave Exit (East)', 'Dark Death Mountain'), @@ -3664,7 +3668,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'), ('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'), ('Bumper Cave Exit (Bottom)', 'Light World'), - ('Death Mountain Return Cave (West)', 'Bumper Cave'), + ('Death Mountain Return Cave (West)', 'Bumper Cave (top)'), ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave (right)'), ('Death Mountain Return Cave Exit (West)', 'Death Mountain'), ('Death Mountain Return Cave Exit (East)', 'Death Mountain'), diff --git a/InvertedRegions.py b/InvertedRegions.py index 175b2b76..a02bfbea 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -184,7 +184,8 @@ def create_inverted_regions(world, player): create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']), create_cave_region(player, 'Red Shield Shop', 'the rare shop', ['Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right']), create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller', None, ['Inverted Dark Sanctuary Exit']), - create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), + create_cave_region(player, 'Bumper Cave (bottom)', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Bottom to Top']), + create_cave_region(player, 'Bumper Cave (top)', 'a connector', None, ['Bumper Cave Exit (Top)', 'Bumper Cave Top To Bottom']), create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']), create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section']), diff --git a/Main.py b/Main.py index d1f4542d..d0174f27 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.1.0-dev' +__version__ = '1.1.1-dev' from source.classes.BabelFish import BabelFish diff --git a/PotShuffle.py b/PotShuffle.py index a2a10c0f..88963ffe 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -745,11 +745,11 @@ vanilla_pots = { 0xE7: [Pot(68, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB389, [0x8B, 0x2B, 0xFA])), Pot(72, 5, PotItem.OneRupee, 'Death Mountain Return Cave (right)', obj=RoomObject(0x0AB38C, [0x93, 0x2B, 0xFA]))], 0xE8: [Pot(96, 4, PotItem.Heart, 'Superbunny Cave (Bottom)', obj=RoomObject(0x0AA98E, [0xC3, 0x23, 0xFA]))], - 0xEB: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADE7, [0x9F, 0x47, 0xFA])), - Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave', obj=RoomObject(0x0AADEA, [0xA7, 0x47, 0xFA])), - Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADED, [0xB3, 0x73, 0xFA])), - Pot(92, 14, PotItem.Heart, 'Bumper Cave', obj=RoomObject(0x0AADF0, [0xBB, 0x73, 0xFA])), - Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave', obj=RoomObject(0x0AADF3, [0xC3, 0x73, 0xFA]))], + 0xEB: [Pot(206, 8, PotItem.FiveRupees, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADE7, [0x9F, 0x47, 0xFA])), + Pot(210, 8, PotItem.FiveRupees, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADEA, [0xA7, 0x47, 0xFA])), + Pot(88, 14, PotItem.SmallMagic, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADED, [0xB3, 0x73, 0xFA])), + Pot(92, 14, PotItem.Heart, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADF0, [0xBB, 0x73, 0xFA])), + Pot(96, 14, PotItem.SmallMagic, 'Bumper Cave (bottom)', obj=RoomObject(0x0AADF3, [0xC3, 0x73, 0xFA]))], 0xF1: [Pot(64, 5, PotItem.Heart, 'Old Man Cave', obj=RoomObject(0x0AA6B2, [0x83, 0x2B, 0xFA]))], 0xF3: [Pot(0x28, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA76F, [0x53, 0xA3, 0xFA])), Pot(0x2C, 0x14, PotItem.Nothing, 'Elder House', obj=RoomObject(0x0AA772, [0x5B, 0xA3, 0xFA])), @@ -851,10 +851,10 @@ vanilla_pots = { Pot(100, 22, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA])), Pot(88, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA])), Pot(100, 28, PotItem.Heart, 'Dark Lake Hylia Ledge Spike Cave', obj=RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]))], - 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801A, [0x33, 0xCB, 0xFA])), - Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A801D, [0x3B, 0xCB, 0xFA])), - Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8020, [0x43, 0xCB, 0xFA])), - Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2A8023, [0x4B, 0xCB, 0xFA]))], + 0x127: [Pot(24, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B801A, [0x33, 0xCB, 0xFA])), + Pot(28, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B801D, [0x3B, 0xCB, 0xFA])), + Pot(32, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B8020, [0x43, 0xCB, 0xFA])), + Pot(36, 25, PotItem.Nothing, 'Dark World Hammer Peg Cave', obj=RoomObject(0x2B8023, [0x4B, 0xCB, 0xFA]))], } diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ba7ac9e0..eff11535 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -181,6 +181,9 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o # Bug Fixes and Notes +* 1.1.1 + * Fixed a logic bug with Bumper Cave where the pots were accessible without Cape or Hookshot from the top entrance + * Fixed a pot coloring issue with hammer peg cave * 1.1.0 * Large features * New pottery modes - see notes above diff --git a/Regions.py b/Regions.py index 31a5ab92..506a8116 100644 --- a/Regions.py +++ b/Regions.py @@ -181,7 +181,8 @@ def create_regions(world, player): create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']), create_cave_region(player, 'Red Shield Shop', 'the rare shop', ['Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right']), create_cave_region(player, 'Dark Sanctuary Hint', 'a storyteller'), - create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), + create_cave_region(player, 'Bumper Cave (bottom)', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Bottom to Top']), + create_cave_region(player, 'Bumper Cave (top)', 'a connector', None, ['Bumper Cave Exit (Top)', 'Bumper Cave Top To Bottom']), create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Ledge Drop', 'Bumper Cave (Top)', 'Bumper Cave Ledge Mirror Spot'], 'a ledge with an item'), create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']), diff --git a/Rules.py b/Rules.py index a553fbfe..68f9e616 100644 --- a/Rules.py +++ b/Rules.py @@ -855,8 +855,8 @@ def default_rules(world, player): set_rule(world.get_entrance('Peg Area Rocks', player), lambda state: state.has_Pearl(player) and state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Village of Outcasts Pegs', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) set_rule(world.get_entrance('Grassy Lawn Pegs', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) - set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player)) - set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) + set_rule(world.get_entrance('Bumper Cave Bottom to Top', player), lambda state: state.has('Cape', player)) + set_rule(world.get_entrance('Bumper Cave Top To Bottom', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player) and state.has_Pearl(player)) # bunny cannot use fire rod set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) @@ -1782,8 +1782,8 @@ def set_bunny_rules(world, player, inverted): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. - bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave (Middle)', - 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] + bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House', + 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins', From 1167f2b1ba3feabf2c0c99fac617c00b6ac30dbe Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 27 Oct 2022 15:23:58 -0600 Subject: [PATCH 13/83] Bumper cave fix for new entrance shuffle --- source/overworld/EntranceShuffle2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index da454795..708e26ac 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -2290,8 +2290,8 @@ default_connections = {'Links House': 'Links House', 'C-Shaped House': 'C-Shaped House', 'Chest Game': 'Chest Game', 'Dark World Hammer Peg Cave': 'Dark World Hammer Peg Cave', - 'Bumper Cave (Bottom)': 'Bumper Cave', - 'Bumper Cave (Top)': 'Bumper Cave', + 'Bumper Cave (Bottom)': 'Bumper Cave (bottom)', + 'Bumper Cave (Top)': 'Bumper Cave (top)', 'Red Shield Shop': 'Red Shield Shop', 'Dark Sanctuary Hint': 'Dark Sanctuary Hint', 'Fortune Teller (Dark)': 'Fortune Teller (Dark)', @@ -2452,7 +2452,7 @@ inverted_default_connections = {'Waterfall of Wishing': 'Waterfall of Wishing', 'Inverted Big Bomb Shop': 'Inverted Big Bomb Shop', 'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary', 'Inverted Dark Sanctuary Exit': 'West Dark World', - 'Old Man Cave (West)': 'Bumper Cave', + 'Old Man Cave (West)': 'Bumper Cave (bottom)', 'Old Man Cave (East)': 'Death Mountain Return Cave (left)', 'Old Man Cave Exit (West)': 'West Dark World', 'Old Man Cave Exit (East)': 'Dark Death Mountain', @@ -2461,7 +2461,7 @@ inverted_default_connections = {'Waterfall of Wishing': 'Waterfall of Wishing', 'Bumper Cave (Top)': 'Dark Death Mountain Healer Fairy', 'Bumper Cave Exit (Top)': 'Death Mountain Return Ledge', 'Bumper Cave Exit (Bottom)': 'Light World', - 'Death Mountain Return Cave (West)': 'Bumper Cave', + 'Death Mountain Return Cave (West)': 'Bumper Cave (top)', 'Death Mountain Return Cave (East)': 'Death Mountain Return Cave (right)', 'Death Mountain Return Cave Exit (West)': 'Death Mountain', 'Death Mountain Return Cave Exit (East)': 'Death Mountain', From bd619ff2ef6847956be40e302cca2682614954a2 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 27 Oct 2022 15:48:04 -0600 Subject: [PATCH 14/83] Bumper cave fixes for ES2 --- source/overworld/EntranceShuffle2.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 708e26ac..ad073162 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -2292,6 +2292,8 @@ default_connections = {'Links House': 'Links House', 'Dark World Hammer Peg Cave': 'Dark World Hammer Peg Cave', 'Bumper Cave (Bottom)': 'Bumper Cave (bottom)', 'Bumper Cave (Top)': 'Bumper Cave (top)', + 'Bumper Cave Top To Bottom': 'Bumper Cave (bottom)', + 'Bumper Cave Bottom to Top': 'Bumper Cave (top)', 'Red Shield Shop': 'Red Shield Shop', 'Dark Sanctuary Hint': 'Dark Sanctuary Hint', 'Fortune Teller (Dark)': 'Fortune Teller (Dark)', @@ -2453,6 +2455,8 @@ inverted_default_connections = {'Waterfall of Wishing': 'Waterfall of Wishing', 'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary', 'Inverted Dark Sanctuary Exit': 'West Dark World', 'Old Man Cave (West)': 'Bumper Cave (bottom)', + 'Bumper Cave Top To Bottom': 'Bumper Cave (bottom)', + 'Bumper Cave Bottom to Top': 'Bumper Cave (top)', 'Old Man Cave (East)': 'Death Mountain Return Cave (left)', 'Old Man Cave Exit (West)': 'West Dark World', 'Old Man Cave Exit (East)': 'Dark Death Mountain', From 82965ac734a7a5f7cebbe442eef60f99f1573043 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 28 Oct 2022 08:56:06 -0600 Subject: [PATCH 15/83] Big Key fix for dungeons with only big chests Door pairing issues Minor gui error --- DoorShuffle.py | 40 ++++++------------- .../app/gui/randomize/dungeon/widgets.json | 2 +- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 1dea2f99..5737a5b8 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -831,7 +831,8 @@ def main_dungeon_pool(dungeon_pool, world, player): for name in pool: builder = world.dungeon_layouts[player][name] region_set = builder.master_sector.region_set() - builder.bk_required = builder.bk_door_proposal or any(x in region_set for x in special_bk_regions) + builder.bk_required = (builder.bk_door_proposal or any(x in region_set for x in special_bk_regions) + or len(world.key_logic[player][name].bk_chests) > 0) dungeon = world.get_dungeon(name, player) if not builder.bk_required or builder.bk_provided: dungeon.big_key = None @@ -2362,21 +2363,16 @@ def find_big_key_door_candidates(region, checked, used, world, player): if valid and d.dest not in candidates: # interior doors are not separable yet candidates.append(d.dest) elif d.type == DoorType.Normal: - if decoupled: - valid = kind in okay_normals - else: + valid = kind in okay_normals + if valid and not decoupled: d2 = d.dest if d2 not in candidates and d2 not in used: if d2.type == DoorType.Normal: room_b = world.get_room(d2.roomIndex, player) pos_b, kind_b = room_b.doorList[d2.doorListPos] - valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2) - else: - valid = kind in okay_normals + valid &= kind_b in okay_normals and valid_key_door_pair(d, d2) if valid and 0 <= d2.doorListPos < 4: candidates.append(d2) - else: - valid = True if valid and d not in candidates: candidates.append(d) connected = ext.connected_region @@ -2628,21 +2624,16 @@ def find_bd_door_candidates(region, checked, used, world, player): if valid and d.dest not in candidates: # interior doors are not separable yet candidates.append(d.dest) elif d.type == DoorType.Normal: - if decoupled: - valid = kind in okay_normals - else: + valid = kind in okay_normals + if valid and not decoupled: d2 = d.dest if d2 not in candidates and d2 not in used: if d2.type == DoorType.Normal: room_b = world.get_room(d2.roomIndex, player) pos_b, kind_b = room_b.doorList[d2.doorListPos] - valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2) - else: - valid = kind in okay_normals + valid &= kind_b in okay_normals and valid_key_door_pair(d, d2) if valid and 0 <= d2.doorListPos < 4: candidates.append(d2) - else: - valid = True if valid and d not in candidates: candidates.append(d) connected = ext.connected_region @@ -2702,8 +2693,8 @@ def reassign_bd_doors(bd_map, world, player): elif d.type is DoorType.Normal and not_in_proposal(d): if not d.entranceFlag: world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) - do_bombable_dashable(flat_bomb_proposal, DoorKind.Bombable, world, player) - do_bombable_dashable(flat_dash_proposal, DoorKind.Dashable, world, player) + do_bombable_dashable(pair[0], DoorKind.Bombable, world, player) + do_bombable_dashable(pair[1], DoorKind.Dashable, world, player) def do_bombable_dashable(proposal, kind, world, player): @@ -2864,21 +2855,16 @@ def find_key_door_candidates(region, checked, used, world, player): elif d.type == DoorType.SpiralStairs: valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow] elif d.type == DoorType.Normal: - if decoupled: - valid = kind in okay_normals - else: + valid = kind in okay_normals + if valid and not decoupled: d2 = d.dest if d2 not in candidates and d2 not in used: if d2.type == DoorType.Normal: room_b = world.get_room(d2.roomIndex, player) pos_b, kind_b = room_b.doorList[d2.doorListPos] - valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2) - else: - valid = kind in okay_normals + valid &= kind_b in okay_normals and valid_key_door_pair(d, d2) if valid and 0 <= d2.doorListPos < 4: candidates.append(d2) - else: - valid = True if valid and d not in candidates: candidates.append(d) connected = ext.connected_region diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json index 19606b23..bdedbfba 100644 --- a/resources/app/gui/randomize/dungeon/widgets.json +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -1,6 +1,6 @@ { "widgets": { - "keyshuffle": { + "smallkeyshuffle": { "type": "selectbox", "options": [ "none", From 9fafa8981e5aadf6ed474ddff9940dba327f3b62 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 28 Oct 2022 12:47:43 -0600 Subject: [PATCH 16/83] Big Key Door fix for when big key double sided is off Fix for basic issues - key total doesn't change Potential key lock logic fix --- DoorShuffle.py | 24 +++++++++++++++++------- Rules.py | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 5737a5b8..5458c020 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1903,9 +1903,11 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl custom_key_doors = world.custom_door_types[player]['Key Door'] else: custom_key_doors = defaultdict(list) - + total_adjustable = len(pool) > 1 for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] + if not total_adjustable: + builder.total_keys = total_keys find_small_key_door_candidates(builder, start_regions_map[dungeon], used_doors, world, player) if custom_key_doors[dungeon]: builder.candidates.small = filter_key_door_pool(builder.candidates.small, custom_key_doors[dungeon]) @@ -1930,11 +1932,15 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl flex_map[dungeon] = (limit - suggested) if suggested < limit else 0 for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] + if total_adjustable: + builder.total_keys = suggestion_map[dungeon] valid_doors, small_number = find_valid_combination(builder, suggestion_map[dungeon], start_regions_map[dungeon], world, player) small_map[dungeon] = valid_doors actual_chest_keys = small_number - builder.key_drop_cnt if actual_chest_keys < suggestion_map[dungeon]: + if total_adjustable: + builder.total_keys = actual_chest_keys flex_map[dungeon] = 0 remaining += suggestion_map[dungeon] - actual_chest_keys suggestion_map[dungeon] = small_number @@ -1953,6 +1959,8 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl remaining -= 1 suggestion_map[dungeon] = increased flex_map[dungeon] -= 1 + if total_adjustable: + builder.total_keys = actual_chest_keys if flex_map[dungeon] > 0: builder.combo_size = ncr(len(builder.candidates.small), builder.key_doors_num) queue.append(builder) @@ -2464,7 +2472,8 @@ def reassign_big_key_doors(bk_map, world, player): d = obj if d.type is DoorType.Interior: change_door_to_big_key(d, world, player) - d.dest.bigKey = True # ensure flag is set + if world.door_type_mode[player] != 'original': + d.dest.bigKey = True # ensure flag is set when bk doors are double sided elif d.type is DoorType.SpiralStairs: pass # we don't have spiral stairs candidates yet that aren't already key doors elif d.type is DoorType.Normal: @@ -2574,7 +2583,6 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k # make changes if player not in world.key_logic.keys(): world.key_logic[player] = {} - builder.total_keys = builder.key_doors_num analyze_dungeon(key_layout, world, player) builder.key_door_proposal = proposal world.key_logic[player][builder.name] = key_layout.key_logic @@ -2620,8 +2628,9 @@ def find_bd_door_candidates(region, checked, used, world, player): room = world.get_room(d.roomIndex, player) position, kind = room.doorList[d.doorListPos] if d.type == DoorType.Interior: - valid = kind in okay_interiors - if valid and d.dest not in candidates: # interior doors are not separable yet + # interior doors are not separable yet + valid = kind in okay_interiors and d.dest not in used + if valid and d.dest not in candidates: candidates.append(d.dest) elif d.type == DoorType.Normal: valid = kind in okay_normals @@ -2849,8 +2858,9 @@ def find_key_door_candidates(region, checked, used, world, player): room = world.get_room(d.roomIndex, player) position, kind = room.doorList[d.doorListPos] if d.type == DoorType.Interior: - valid = kind in okay_interiors - if valid and d.dest not in candidates: # interior doors are not separable yet + valid = kind in okay_interiors and d.dest not in used + # interior doors are not separable yet + if valid and d.dest not in candidates: candidates.append(d.dest) elif d.type == DoorType.SpiralStairs: valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow] diff --git a/Rules.py b/Rules.py index 3867d681..fa07bd5a 100644 --- a/Rules.py +++ b/Rules.py @@ -2093,7 +2093,7 @@ def eval_small_key_door_main(state, door_name, dungeon, player): elif ruleType == KeyRuleType.AllowSmall: if (door_rule.small_location.item and door_rule.small_location.item.name == key_logic.small_key_name and door_rule.small_location.item.player == player): - return True # always okay if allow small is on + door_openable |= state.has_sm_key(key_logic.small_key_name, player, number) elif isinstance(ruleType, tuple): lock, lock_item = ruleType # this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't From cb4cf65c9c9a0be03a1a754a3b3e585d89470fd0 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 1 Nov 2022 15:05:28 -0600 Subject: [PATCH 17/83] More robust sweep_for_events (supports events that later become unreachable) Minor fix for AllowSmall key logic --- BaseClasses.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 60d04d82..06c1855d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -786,7 +786,21 @@ class CollectionState(object): else: door_candidates.append(door.name) return door_candidates - return None + door_candidates, skip = [], set() + if state.world.accessibility[player] != 'locations' and remaining_keys == 0: + key_logic = state.world.key_logic[player][dungeon_name] + for door, paired in key_logic.sm_doors.items(): + if door.name in key_logic.door_rules: + rule = key_logic.door_rules[door.name] + key = KeyRuleType.AllowSmall + if (key in rule.new_rules and key_total >= rule.new_rules[key] and door.name not in skip + and door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]): + if paired: + door_candidates.append((door.name, paired.name)) + skip.add(paired.name) + else: + door_candidates.append(door.name) + return door_candidates if door_candidates else None @staticmethod def print_rrp(rrp): @@ -925,29 +939,30 @@ class CollectionState(object): checked_locations = set([l for l in locations if l in self.locations_checked]) reachable_events = [location for location in locations if location.event and location.can_reach(self)] reachable_events = self._do_not_flood_the_keys(reachable_events) + found_new = False for event in reachable_events: if event not in checked_locations: self.events.append((event.name, event.player)) self.collect(event.item, True, event) - return len(reachable_events) > len(checked_locations) + found_new = True + return found_new def sweep_for_events(self, key_only=False, locations=None): # this may need improvement if locations is None: locations = self.world.get_filled_locations() new_locations = True - checked_locations = 0 while new_locations: reachable_events = [location for location in locations if location.event and (not key_only or (self.world.keyshuffle[location.item.player] == 'none' and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey)) and location.can_reach(self)] reachable_events = self._do_not_flood_the_keys(reachable_events) + new_locations = False for event in reachable_events: if (event.name, event.player) not in self.events: self.events.append((event.name, event.player)) self.collect(event.item, True, event) - new_locations = len(reachable_events) > checked_locations - checked_locations = len(reachable_events) + new_locations = True def can_reach_blue(self, region, player): From fa75d2b4e978c6653262de5dd81129d9cd07907d Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 10 Nov 2022 14:14:54 -0700 Subject: [PATCH 18/83] Possible fix for standing items weirdness Generation fix for Bumper Cave Chaos door type fix Minor fix for universal keys Relaase notes update --- BaseClasses.py | 2 +- DoorShuffle.py | 10 +- RELEASENOTES.md | 239 ++++++++------------------- Rom.py | 2 +- data/base2current.bps | Bin 93445 -> 93456 bytes source/overworld/EntranceShuffle2.py | 8 +- 6 files changed, 79 insertions(+), 182 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 06c1855d..d89147c9 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -787,7 +787,7 @@ class CollectionState(object): door_candidates.append(door.name) return door_candidates door_candidates, skip = [], set() - if state.world.accessibility[player] != 'locations' and remaining_keys == 0: + if state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name != 'Universal': key_logic = state.world.key_logic[player][dungeon_name] for door, paired in key_logic.sm_doors.items(): if door.name in key_logic.door_rules: diff --git a/DoorShuffle.py b/DoorShuffle.py index 5458c020..ad22b1c0 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1744,7 +1744,7 @@ class DoorTypePool: @staticmethod def get_choices(number): - return [max(number-i, 0) for i in range(-1, 5)] + return [max(number+i, 0) for i in range(-1, 5)] class BuilderDoorCandidates: @@ -2208,6 +2208,7 @@ def find_bk_special_location(builder, world, player): return loc return None + def check_required_paths_with_traps(paths, proposal, dungeon_name, start_regions, world, player): cached_initial_state = None if len(paths[dungeon_name]) > 0: @@ -2673,8 +2674,11 @@ def find_valid_bd_combination(builder, suggested, world, player): bd_door_pool = filter_key_door_pool(bd_door_pool, custom_dash_doors) dash_doors_needed -= len(custom_dash_doors) while len(bd_door_pool) < bomb_doors_needed + dash_doors_needed: - bomb_doors_needed = round(len(bd_door_pool) * bomb_doors_needed/ttl_needed) - dash_doors_needed = round(len(bd_door_pool) * dash_doors_needed/ttl_needed) + test = random.choice([True, False]) + if test: + bomb_doors_needed -= 1 + else: + dash_doors_needed -= 1 bomb_proposal = random.sample(bd_door_pool, k=bomb_doors_needed) bomb_proposal.extend(custom_bomb_doors) dash_pool = [x for x in bd_door_pool if x not in bomb_proposal] diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ce89548a..8fc802a6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,215 +1,108 @@ -## New Features +# New Features -## Pottery Lottery and Key Drop Shuffle Changes +One major change with this update is that big key doors and certain trap doors are no longer guaranteed to be vanilla in Dungeon Door Shuffle modes even if you choose not to shuffle those types. A newer algorithm for putting dungeons together has been written and it will remove big key doors and trap doors when necessary to ensure progress can be made. -### Pottery +Please note that retro features are now independently customizable as referenced below. Selecting Retro mode or World State: Retro will change Bow Mode to Retro (Progressive). Take Anys to Random, and Small Keys to Universal. -New pottery option that control which pots (and large blocks) are in the locations pool: +## Flute Mode -* None: No pots are in the pool, like normal randomizer -* Key Pots: The pots that have keys are in the pool. This is about half of the old keydropshuffle option -* Cave Pots: The pots that are not found in dungeons are in the pool. (Includes the large block in Spike Cave). Does -not include key pots. -* CaveKeys: Both non-dungeon pots and pots that used to have keys are in the pool. -* Reduced: Same as CaveKeys but also roughly a quarter of dungeon pots are added to the location pool picked at random. This is a dynamic mode so pots in the pool will be colored. Pots out of the pool will have vanilla contents. -* Clustered: Like reduced but pots are grouped by logical sets and roughly 50% of pots are chosen from those group. This is a dynamic mode like the above. -* Nonempty: All pots that had some sort of objects under them are chosen to be in the location pool. This excludes most large blocks and some pots out of dungeons. -* Dungeon Pots: The pots that are in dungeons are in the pool. (Includes serveral large blocks) -* Lottery: All pots and large blocks are in the pool +Normal mode for flute means you need to activate it at the village statue after finding it like usual. +Activated flute mode mean you can use it immediately upon finding it. the flute SFX plays to let you know this is the case. -By default, switches remain in their vanilla location (unless you turn on the legacy option below) +## Bow Mode -CLI `--pottery