From cf2e001447df40b479aa17b9f8ea00ef6401016e Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 17 Aug 2023 13:42:45 -0600 Subject: [PATCH 1/5] Fix for TFH --- source/item/FillUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 6a055584..595e86c9 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -357,7 +357,7 @@ def determine_major_items(world, player): major_item_set.add('Single Arrow') if world.keyshuffle[player] == 'universal': major_item_set.add('Small Key (Universal)') - if world.goal == 'triforcehunt': + if world.goal[player] == 'triforcehunt': major_item_set.add('Triforce Piece') if world.bombbag[player]: major_item_set.add('Bomb Upgrade (+10)') From e722a1129f3b704787d873d6540db89e6e6190de Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 28 Sep 2023 10:31:02 -0600 Subject: [PATCH 2/5] Rule fix for zelda to throne room --- Rules.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Rules.py b/Rules.py index 0b390f3d..4140469c 100644 --- a/Rules.py +++ b/Rules.py @@ -1322,13 +1322,15 @@ def standard_rules(world, player): add_rule(ent, lambda state: standard_escape_rule(state)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) - set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player)) set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player)) def check_rule_list(state, r_list): return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) rule_list, debug_path = find_rules_for_zelda_delivery(world, player) - set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) + set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), + lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) + set_rule(world.get_location('Zelda Drop Off', player), + lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest']: add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player)) From dc96c53a95bacde8b761307c77be4bc80268cbf1 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 21 Aug 2023 08:25:17 -0600 Subject: [PATCH 3/5] Fix for Ganonhunt and various algorithms --- source/item/FillUtil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 595e86c9..8b5225b0 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -357,7 +357,7 @@ def determine_major_items(world, player): major_item_set.add('Single Arrow') if world.keyshuffle[player] == 'universal': major_item_set.add('Small Key (Universal)') - if world.goal[player] == 'triforcehunt': + if world.goal[player] in {'triforcehunt', 'ganonhunt'}: major_item_set.add('Triforce Piece') if world.bombbag[player]: major_item_set.add('Bomb Upgrade (+10)') From 329367e04a58a3dd2d0f2e8c32d2f4a1dfa7a1a0 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 14 Sep 2023 11:09:58 -0600 Subject: [PATCH 4/5] Account for vanilla BK doors in Stitcher --- source/dungeon/DungeonStitcher.py | 90 ++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 504fc03b..84139157 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -72,11 +72,13 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] doors_to_connect, idx = {}, 0 all_regions = set() + bk_special = False for sector in builder.sectors: for door in sector.outstanding_doors: doors_to_connect[door.name] = door, idx idx += 1 all_regions.update(sector.regions) + bk_special |= check_for_special(sector.regions) finished = False # flag if standard and this is hyrule castle paths = determine_paths_for_dungeon(world, player, all_regions, name) @@ -95,9 +97,9 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon if hash_code not in hash_code_set: hash_code_set.add(hash_code) explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect, - world, player) + bk_special, world, player) if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions, - paths, entrance_regions, world, player): + paths, entrance_regions, bk_special, world, player): finished = True else: proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect, @@ -229,21 +231,24 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se return proposed_map, hash_code -def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player): +def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, bk_special, world, player): start = ExplorationState(dungeon=name) + bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special + start.big_key_special = bk_special original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map, - all_regions, valid_doors, world, player) + all_regions, valid_doors, bk_relevant, world, player) return original_state def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions, - paths, entrance_regions, world, player): + paths, entrance_regions, bk_special, world, player): all_visited = set() all_visited.update(exploration_state.visited_blue) all_visited.update(exploration_state.visited_orange) if len(all_regions.difference(all_visited)) > 0: return False - if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player): + if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, + bk_special, world, player): return False return True @@ -266,7 +271,7 @@ def check_for_special(regions): return False -def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player): +def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, bk_special, world, player): for path in paths: if type(path) is tuple: target = path[1] @@ -278,12 +283,13 @@ def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, propose else: target = path start_regions = entrance_regions - if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player): + if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, + bk_special, world, player): return False return True -def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player): +def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, bk_special, world, player): target_regions = set() if type(target) is not list: for region in all_regions: @@ -296,8 +302,10 @@ def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_re target_regions.add(region) start = ExplorationState(dungeon=name) + bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special + start.big_key_special = bk_special original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions, - valid_doors, world, player) + valid_doors, bk_relevant, world, player) for exp_door in original_state.unattached_doors: if not exp_door.door.blocked or exp_door.door.trapFlag != 0: @@ -531,7 +539,7 @@ class ExplorationState(object): self.crystal = exp_door.crystal return exp_door - def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False): + def visit_region(self, region, key_region=None, key_checks=False, bk_relevant=False): if region.type != RegionType.Dungeon: self.crystal = CrystalBarrier.Orange if self.crystal == CrystalBarrier.Either: @@ -552,8 +560,14 @@ class ExplorationState(object): self.ttl_locations += 1 if location not in self.found_locations: self.found_locations.append(location) - if not bk_flag: - self.bk_found.add(location) + if bk_relevant: + if self.big_key_special: + if special_big_key_found(self): + self.bk_found.add(location) + self.re_add_big_key_doors() + else: + self.bk_found.add(location) + self.re_add_big_key_doors() if location.name in dungeon_events and location.name not in self.events: if self.flooded_key_check(location): self.perform_event(location.name, key_region) @@ -574,6 +588,14 @@ class ExplorationState(object): return True return False + def re_add_big_key_doors(self): + self.big_key_opened = True + queue = collections.deque(self.big_doors) + while len(queue) > 0: + exp_door = queue.popleft() + self.avail_doors.append(exp_door) + self.big_doors.remove(exp_door) + def perform_event(self, location_name, key_region): self.events.add(location_name) queue = collections.deque(self.event_doors) @@ -640,7 +662,7 @@ class ExplorationState(object): self.append_door_to_list(door, self.avail_doors, flag) # same as above but traps are ignored, and flag is not used - def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player): + def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, bk_relevant, world, player): for door in get_doors(world, region, player): if door in proposed_map and door.name in valid_doors: self.visited_doors.add(door) @@ -654,14 +676,18 @@ class ExplorationState(object): other = self.find_door_in_list(door, self.unattached_doors) if self.crystal != other.crystal: other.crystal = CrystalBarrier.Either - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, - self.event_doors): + elif (door.req_event is not None and door.req_event not in self.events + and not self.in_door_list(door, self.event_doors)): self.append_door_to_list(door, self.event_doors) + elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player)) + and not self.big_key_opened): + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) # same as above but traps are checked for - def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player): + def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, bk_relevant, world, player): for door in get_doors(world, region, player): if door in proposed_map and door.name in valid_doors: self.visited_doors.add(door) @@ -675,9 +701,13 @@ class ExplorationState(object): other = self.find_door_in_list(door, self.unattached_doors) if self.crystal != other.crystal: other.crystal = CrystalBarrier.Either - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, - self.event_doors): + elif (door.req_event is not None and door.req_event not in self.events + and not self.in_door_list(door, self.event_doors)): self.append_door_to_list(door, self.event_doors) + elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player)) + and not self.big_key_opened): + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) @@ -863,16 +893,22 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg return local_state -def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player): +# bk_relevant means the big key doors need to be checks +def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, bk_relevant, + world, player): local_state = state.copy() for region in search_regions: - local_state.visit_region(region) + local_state.visit_region(region, bk_relevant=bk_relevant) if world.trap_door_mode[player] == 'vanilla': - local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, bk_relevant, world, player) else: - local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, bk_relevant, world, player) while len(local_state.avail_doors) > 0: explorable_door = local_state.next_avail_door() + if explorable_door.door.bigKey: + if bk_relevant and (not special_big_key_found(local_state) if local_state.big_key_special + else local_state.count_locations_exclude_specials(world, player) == 0): + continue if explorable_door.door in proposed_map: connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region else: @@ -880,11 +916,13 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi if connect_region is not None: if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player) and not local_state.visited(connect_region)): - local_state.visit_region(connect_region) + local_state.visit_region(connect_region, bk_relevant=bk_relevant) if world.trap_door_mode[player] == 'vanilla': - local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, + bk_relevant, world, player) else: - local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, + bk_relevant, world, player) return local_state From 7a16ea4fe11a0afa36b52ba160ed246cbf513d89 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 29 Sep 2023 09:33:33 -0600 Subject: [PATCH 5/5] Rule fix for zelda to throne room Minor fix to standard generation and spoilers --- DoorShuffle.py | 10 +++++++++- Main.py | 2 +- RELEASENOTES.md | 4 ++++ Rom.py | 2 +- data/base2current.bps | Bin 94175 -> 94199 bytes test/customizer/zelda_escape.yaml | 14 ++++++++++++++ 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 test/customizer/zelda_escape.yaml diff --git a/DoorShuffle.py b/DoorShuffle.py index aa988351..b8c78f9f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -177,6 +177,7 @@ def create_door_spoiler(world, player): queue = deque(world.dungeon_layouts[player].values()) while len(queue) > 0: builder = queue.popleft() + std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' and world.shuffle[player] == 'vanilla' done = set() start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic reg_queue = deque(start_regions) @@ -205,11 +206,15 @@ def create_door_spoiler(world, player): logger.warning('This is a bug during door spoiler') elif not isinstance(door_b, Region): logger.warning('Door not connected: %s', door_a.name) - if connect and connect.type == RegionType.Dungeon and connect not in visited: + if valid_connection(connect, std_flag, world, player) and connect not in visited: visited.add(connect) reg_queue.append(connect) +def valid_connection(region, std_flag, world, player): + return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player] or + (std_flag and region.name == 'Hyrule Castle Ledge')) + def vanilla_key_logic(world, player): builders = [] world.dungeon_layouts[player] = {} @@ -3328,6 +3333,9 @@ def find_inaccessible_regions(world, player): ledge = world.get_region('Hyrule Castle Ledge', player) if any(x for x in ledge.exits if x.connected_region.name == 'Agahnims Tower Portal'): world.inaccessible_regions[player].append('Hyrule Castle Ledge') + # this should be considered as part of the inaccessible regions, dungeonssimple? + if world.mode[player] == 'standard' and world.shuffle[player] == 'vanilla': + world.inaccessible_regions[player].append('Hyrule Castle Ledge') logger = logging.getLogger('') logger.debug('Inaccessible Regions:') for r in world.inaccessible_regions[player]: diff --git a/Main.py b/Main.py index 259d7a4b..24fa0080 100644 --- a/Main.py +++ b/Main.py @@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.20' +version_number = '1.2.0.21' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 336c604e..cae2bc56 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,10 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.2.0.21u + * Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory + * Small fix for Tavern Shuffle (thanks Catobat) + * Several small generation fixes * 1.2.0.20u * New generation feature that allows Spiral Stair to link to themselves (thank Catobat) * Added logic for trap doors that could be opened using existing room triggers diff --git a/Rom.py b/Rom.py index 5dc3652a..9351970c 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '61662913cc0cb12fb870d794937d88d9' +RANDOMIZERBASEHASH = '9b6e57f6e9d92934ce14276afd715849' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 23872f82d3be21e2fabd52ac809761a819691b9f..643e68f74b79b1aa1e7712e5e48e71831c6eedc5 100644 GIT binary patch delta 5989 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 delta 5929 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 E50@W(RR910 diff --git a/test/customizer/zelda_escape.yaml b/test/customizer/zelda_escape.yaml new file mode 100644 index 00000000..e779bbac --- /dev/null +++ b/test/customizer/zelda_escape.yaml @@ -0,0 +1,14 @@ +meta: + players: 1 +settings: + 1: + door_shuffle: crossed + intensity: 3 + mode: standard + pottery: keys + dropshuffle: 'on' +doors: + 1: + doors: + Hyrule Dungeon Cellblock Up Stairs: + dest: Ice Hammer Block Down Stairs