From 579c3bfd2311322def538a6986325a3189516d20 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Fri, 6 Aug 2021 14:17:24 +0200 Subject: [PATCH 01/24] Fix money balancing with reduced item pool --- BaseClasses.py | 2 ++ Fill.py | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b7a80af7..19cc8def 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -808,6 +808,8 @@ class CollectionState(object): def collect(self, item, event=False, location=None): if location: self.locations_checked.add(location) + if not item: + return changed = False if item.name.startswith('Progressive '): if 'Sword' in item.name: diff --git a/Fill.py b/Fill.py index 7572b350..6436a7ee 100644 --- a/Fill.py +++ b/Fill.py @@ -553,7 +553,7 @@ def balance_money_progression(world): base_value = sum(rupee_rooms.values()) available_money = {player: base_value for player in range(1, world.players+1)} for loc in world.get_locations(): - if loc.item.name in rupee_chart: + if loc.item and loc.item.name in rupee_chart: available_money[loc.item.player] += rupee_chart[loc.item.name] total_price = {player: 0 for player in range(1, world.players+1)} @@ -618,7 +618,7 @@ def balance_money_progression(world): slot = shop_to_location_table[location.parent_region.name].index(location.name) shop = location.parent_region.shop shop_item = shop.inventory[slot] - if interesting_item(location, location.item, world, location.item.player): + if location.item and interesting_item(location, location.item, world, location.item.player): if location.item.name.startswith('Rupee') and loc_player == location.item.player: if shop_item['price'] < rupee_chart[location.item.name]: wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block @@ -638,14 +638,15 @@ def balance_money_progression(world): if location_free: state.collect(location.item, True, location) unchecked_locations.remove(location) - if location.item.name.startswith('Rupee'): - wallet[location.item.player] += rupee_chart[location.item.name] - if location.item.name != 'Rupees (300)': + if location.item: + if location.item.name.startswith('Rupee'): + wallet[location.item.player] += rupee_chart[location.item.name] + if location.item.name != 'Rupees (300)': + balance_locations[location.item.player].add(location) + if interesting_item(location, location.item, world, location.item.player): + checked_locations.append(location) + elif location.item.name in acceptable_balancers: balance_locations[location.item.player].add(location) - if interesting_item(location, location.item, world, location.item.player): - checked_locations.append(location) - elif location.item.name in acceptable_balancers: - balance_locations[location.item.player].add(location) for room, income in rupee_rooms.items(): for player in range(1, world.players+1): if room not in rooms_visited[player] and world.get_region(room, player) in state.reachable_regions[player]: @@ -710,5 +711,5 @@ def balance_money_progression(world): else: state.collect(location.item, True, location) unchecked_locations.remove(location) - if location.item.name.startswith('Rupee'): + if location.item and location.item.name.startswith('Rupee'): wallet[location.item.player] += rupee_chart[location.item.name] From 2760841836d793543cdbf097bd6611b3248548bc Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 2 Sep 2021 17:00:36 -0600 Subject: [PATCH 02/24] Prevent keys doors on door pairs that loop on themselves in the same supertile. (Excludes dead ends) Thus, there's not chance for a keys to be wasted. --- DoorShuffle.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index e89a7bb4..2574e4f5 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1582,7 +1582,7 @@ def find_key_door_candidates(region, checked, world, player): 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 + valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2) else: valid = kind in okay_normals if valid and 0 <= d2.doorListPos < 4: @@ -1599,6 +1599,12 @@ def find_key_door_candidates(region, checked, world, player): return candidates, checked_doors +def valid_key_door_pair(door1, door2): + if door1.roomIndex != door2.roomIndex: + return True + return len(door1.entrance.parent_region.exits) <= 1 or len(door2.entrance.parent_region.exits) <= 1 + + def reassign_key_doors(builder, world, player): logger = logging.getLogger('') logger.debug('Key doors for %s', builder.name) From e48dbe3d27c4f0417293f2fb8f8df2b925dc15a5 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 2 Sep 2021 18:10:49 -0600 Subject: [PATCH 03/24] Prize relevance refinement Subtle change on bk restriction - only restrict if bk was determined to be the way forward --- KeyDoorShuffle.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 23dd24be..1ff840f6 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -26,7 +26,7 @@ class KeyLayout(object): self.item_locations = set() self.found_doors = set() - self.prize_relevant = False + self.prize_relevant = None # bk special? # bk required? True if big chests or big doors exists @@ -37,7 +37,7 @@ class KeyLayout(object): self.max_chests = calc_max_chests(builder, self, world, player) self.all_locations = set() self.item_locations = set() - self.prize_relevant = False + self.prize_relevant = None class KeyLogic(object): @@ -284,7 +284,7 @@ def analyze_dungeon(key_layout, world, player): 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: + 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)) @@ -1380,6 +1380,15 @@ def forced_big_key_avail(locations): return None +def prize_relevance(key_layout, dungeon_entrance): + if len(key_layout.start_regions) > 1 and dungeon_entrance and dungeon_table[key_layout.key_logic.dungeon].prize: + if dungeon_entrance.name in ['Ganons Tower', 'Inverted Ganons Tower']: + return 'GT' + elif dungeon_entrance.name == 'Pyramid Fairy': + return 'BigBomb' + return None + + # Soft lock stuff def validate_key_layout(key_layout, world, player): # retro is all good - except for hyrule castle in standard mode @@ -1391,12 +1400,11 @@ def validate_key_layout(key_layout, world, player): state.big_key_special = check_bk_special(key_layout.sector.regions, world, player) for region in key_layout.start_regions: 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 dungeon_table[key_layout.key_logic.dungeon].prize): + prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance) + if prize_relevant_flag: state.append_door_to_list(portal_door, state.prize_doors) state.prize_door_set[portal_door] = dungeon_entrance - key_layout.prize_relevant = True + key_layout.prize_relevant = prize_relevant_flag else: state.visit_region(region, key_checks=True) state.add_all_doors_check_keys(region, flat_proposal, world, player) @@ -1554,12 +1562,11 @@ def create_key_counters(key_layout, world, player): state.big_key_special = True for region in key_layout.start_regions: 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 dungeon_table[key_layout.key_logic.dungeon].prize): + prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance) + if prize_relevant_flag: state.append_door_to_list(portal_door, state.prize_doors) state.prize_door_set[portal_door] = dungeon_entrance - key_layout.prize_relevant = True + key_layout.prize_relevant = prize_relevant_flag else: state.visit_region(region, key_checks=True) state.add_all_doors_check_keys(region, flat_proposal, world, player) @@ -1988,8 +1995,10 @@ def validate_key_placement(key_layout, world, player): found_prize = any(x for x in counter.important_locations if '- Prize' in x.name) 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 + if key_layout.prize_relevant == 'BigBomb': + found_prize = prize_loc.item.name not in ['Crystal 5', 'Crystal 6'] + elif key_layout.prize_relevant == 'GT': + found_prize = 'Crystal' not in prize_loc.item.name or world.crystals_needed_for_gt[player] < 7 else: found_prize = False can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) or \ From ec7c1489c788ef4cb59df59509cbfc89c2491d15 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 7 Sep 2021 16:25:42 -0600 Subject: [PATCH 04/24] Removed rails flag and just edited the object table for prevent mode in mixed travel Updated rails in PoD Arena to prevent hovering better --- DoorShuffle.py | 2 +- Rom.py | 16 ++++++++++++++-- data/base2current.bps | Bin 136284 -> 135945 bytes 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 2574e4f5..9d22893c 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2041,7 +2041,7 @@ class DROptions(Flag): Town_Portal = 0x02 # If on, Players will start with mirror scroll Map_Info = 0x04 Debug = 0x08 - Rails = 0x10 # If on, draws rails + # Rails = 0x10 # Unused bit now OriginalPalettes = 0x20 Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required diff --git a/Rom.py b/Rom.py index 53d6666e..d6248488 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '091671c7f555331ff2f411b304141c29' +RANDOMIZERBASEHASH = 'ef6e3e1aa59838c01dbd5b1b2387e70c' class JsonRom(object): @@ -671,7 +671,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): dr_flags |= DROptions.Debug if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\ and world.mixed_travel[player] == 'prevent': - dr_flags |= DROptions.Rails + # PoD Falling Bridge or Hammjump + # 1FA607: db $2D, $79, $69 ; 0x0069: Vertical Rail ↕ | { 0B, 1E } | Size: 05 + # 1FA60A: db $14, $99, $5D ; 0x005D: Large Horizontal Rail ↔ | { 05, 26 } | Size: 01 + rom.write_bytes(0xfa607, [0x2d, 0x79, 0x69, 0x14, 0x99, 0x5d]) + # PoD Arena + # 1FA573: db $D4, $B2, $22 ; 0x0022: Horizontal Rail ↔ | { 35, 2C } | Size: 02 + # 1FA576: db $D4, $CE, $22 ; 0x0022: Horizontal Rail ↔ | { 35, 33 } | Size: 01 + # 1FA579: db $D9, $AE, $69 ; 0x0069: Vertical Rail ↕ | { 36, 2B } | Size: 06 + rom.write_bytes(0xfa573, [0xd4, 0xb2, 0x22, 0xd4, 0xce, 0x22, 0xd9, 0xae, 0x69]) + # Mire BK Pond + # 1FB1FC: db $C8, $9D, $69 ; 0x0069: Vertical Rail ↕ | { 32, 27 } | Size: 01 + # 1FB1FF: db $B4, $AC, $5D ; 0x005D: Large Horizontal Rail ↔ | { 2D, 2B } | Size: 00 + rom.write_bytes(0xfb1fc, [0xc8, 0x9d, 0x69, 0xb4, 0xac, 0x5d]) if world.standardize_palettes[player] == 'original': dr_flags |= DROptions.OriginalPalettes if world.experimental[player]: diff --git a/data/base2current.bps b/data/base2current.bps index 4571d623111492b36542f9572a826fd4ecbbef1d..17b3e4976a8e05b8f7e0a167166c55236e3a9806 100644 GIT binary patch delta 7248 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 Date: Fri, 10 Sep 2021 09:13:20 -0600 Subject: [PATCH 05/24] More refinement for prize locked parts of a dungeon and key rule interaction --- Fill.py | 6 +++++- KeyDoorShuffle.py | 27 ++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Fill.py b/Fill.py index 779e1ec4..2cf1f25e 100644 --- a/Fill.py +++ b/Fill.py @@ -245,7 +245,11 @@ def valid_key_placement(item, location, itempool, world): return True key_logic = world.key_logic[item.player][dungeon.name] unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player]) - return key_logic.check_placement(unplaced_keys, location if item.bigkey else None) + prize_loc = None + if key_logic.prize_location: + prize_loc = world.get_location(key_logic.prize_location, location.player) + cr_count = world.crystals_needed_for_gt[location.player] + return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count) else: inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player]) or (item.bigkey and not world.bigkeyshuffle[item.player])) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 1ff840f6..1b74ae80 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -58,10 +58,11 @@ class KeyLogic(object): self.outside_keys = 0 self.dungeon = dungeon_name self.sm_doors = {} + self.prize_location = None - def check_placement(self, unplaced_keys, big_key_loc=None): + def check_placement(self, unplaced_keys, big_key_loc=None, prize_loc=None, cr_count=7): for rule in self.placement_rules: - if not rule.is_satisfiable(self.outside_keys, unplaced_keys, big_key_loc): + if not rule.is_satisfiable(self.outside_keys, unplaced_keys, big_key_loc, prize_loc, cr_count): return False if big_key_loc: for rule_a, rule_b in itertools.combinations(self.placement_rules, 2): @@ -120,6 +121,7 @@ class PlacementRule(object): self.check_locations_wo_bk = None self.bk_relevant = True self.key_reduced = False + self.prize_relevance = None def contradicts(self, rule, unplaced_keys, big_key_loc): bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False @@ -154,7 +156,14 @@ class PlacementRule(object): left -= rule_needed return False - def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc): + def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc, prize_location, cr_count): + if self.prize_relevance and prize_location: + if self.prize_relevance == 'BigBomb': + if prize_location.item.name not in ['Crystal 5', 'Crystal 6']: + return True + elif self.prize_relevance == 'GT': + if 'Crystal' not in prize_location.item.name or cr_count < 7: + return True bk_blocked = False if self.bk_conditional_set: for loc in self.bk_conditional_set: @@ -258,6 +267,7 @@ def analyze_dungeon(key_layout, world, player): find_bk_locked_sections(key_layout, world, player) key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations)) + key_logic.prize_location = dungeon_table[key_layout.sector.name].prize if world.retro[player] and world.mode[player] != 'standard': return @@ -361,6 +371,7 @@ def create_exhaustive_placement_rules(key_layout, world, player): rule.bk_conditional_set = blocked_loc rule.needed_keys_wo_bk = min_keys rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc)) + rule.prize_relevance = key_layout.prize_relevant if rule_prize_relevant(key_counter) else None if valid_rule: key_logic.placement_rules.append(rule) adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr) @@ -368,6 +379,10 @@ def create_exhaustive_placement_rules(key_layout, world, player): refine_location_rules(key_layout) +def rule_prize_relevant(key_counter): + return not key_counter.prize_doors_opened and not key_counter.prize_received + + def skip_key_counter_due_to_prize(key_layout, key_counter): return key_layout.prize_relevant and key_counter.prize_received and not key_counter.prize_doors_opened @@ -467,7 +482,7 @@ def refine_placement_rules(key_layout, max_ctr): if rule.needed_keys_wo_bk == 0: rules_to_remove.append(rule) if len(rule.check_locations_wo_bk) < rule.needed_keys_wo_bk or rule.needed_keys_wo_bk > key_layout.max_chests: - if len(rule.bk_conditional_set) > 0: + if not rule.prize_relevance and len(rule.bk_conditional_set) > 0: key_logic.bk_restricted.update(rule.bk_conditional_set) rules_to_remove.append(rule) changed = True # impossible for bk to be here, I think @@ -1432,7 +1447,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa found_forced_bk = state.found_forced_bk() smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations) bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk) - if smalls_done and bk_done: + prize_done = not key_layout.prize_relevant or state.prize_doors_opened + if smalls_done and bk_done and prize_done: return False else: # todo: pretty sure you should OR these paths together, maybe when there's one location and it can @@ -1468,6 +1484,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa if not valid: return False # todo: feel like you only open these if the boss is available??? + # todo: or if a crystal isn't valid placement on this boss if not state.prize_doors_opened and key_layout.prize_relevant: state_copy = state.copy() open_a_door(next(iter(state_copy.prize_door_set)), state_copy, flat_proposal, world, player) From c5f6c46bd879f21df840c550e40143703409318e Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 Sep 2021 15:24:23 -0600 Subject: [PATCH 06/24] Remove unique annotation from FastEnum class --- BaseClasses.py | 1 - 1 file changed, 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 77fdf968..b7c13f45 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2740,7 +2740,6 @@ class Settings(object): args.shufflepots[p] = True if settings[7] & 0x4 else False -@unique class KeyRuleType(FastEnum): WorstCase = 0 AllowSmall = 1 From eab94043988c92b96260ec09ae88bcd76aba56be Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 Sep 2021 15:40:55 -0600 Subject: [PATCH 07/24] Allow Blind's Cell to be shuffled if Blind is not the boss --- DungeonGenerator.py | 8 ++++++-- ItemList.py | 2 -- Main.py | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f579f285..19ab9a1d 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -585,7 +585,8 @@ def determine_paths_for_dungeon(world, player, all_regions, name): paths.append(boss) if 'Thieves Boss' in all_r_names: paths.append('Thieves Boss') - paths.append(('Thieves Blind\'s Cell', 'Thieves Boss')) + if world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind': + paths.append(('Thieves Blind\'s Cell', 'Thieves Boss')) for drop_check in drop_path_checks: if drop_check in all_r_names: paths.append((drop_check, non_hole_portals)) @@ -1275,6 +1276,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) + if key == 'Thieves Town' and world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind': + assign_sector(find_sector("Thieves Blind's Cell", candidate_sectors), current_dungeon, + candidate_sectors, global_pole) entrances_map, potentials, connections = connections_tuple accessible_sectors, reverse_d_map = set(), {} for key in dungeon_entrances.keys(): @@ -3898,7 +3902,7 @@ dungeon_boss_sectors = { 'Palace of Darkness': ['PoD Boss'], 'Swamp Palace': ['Swamp Boss'], 'Skull Woods': ['Skull Boss'], - 'Thieves Town': ['Thieves Blind\'s Cell', 'Thieves Boss'], + 'Thieves Town': ['Thieves Boss'], 'Ice Palace': ['Ice Boss'], 'Misery Mire': ['Mire Boss'], 'Turtle Rock': ['TR Boss'], diff --git a/ItemList.py b/ItemList.py index eda7291f..c0f2f86c 100644 --- a/ItemList.py +++ b/ItemList.py @@ -4,7 +4,6 @@ import math import RaceRandom as random from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState -from Bosses import place_bosses from Dungeons import get_dungeon_item_pool from EntranceShuffle import connect_entrance from Regions import shop_to_location_table, retro_shops, shop_table_by_location @@ -371,7 +370,6 @@ def generate_itempool(world, player): tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] world.required_medallions[player] = (mm_medallion, tr_medallion) - place_bosses(world, player) set_up_shops(world, player) if world.retro[player]: diff --git a/Main.py b/Main.py index be2cb2e6..11bf51d3 100644 --- a/Main.py +++ b/Main.py @@ -10,6 +10,7 @@ import time import zlib from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance, Settings +from Bosses import place_bosses from Items import ItemFactory from KeyDoorShuffle import validate_key_placement from OverworldGlitchRules import create_owg_connections @@ -146,6 +147,7 @@ def main(args, seed=None, fish=None): create_rooms(world, player) create_dungeons(world, player) adjust_locations(world, player) + place_bosses(world, player) if any(world.potshuffle.values()): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) From bce3bcf4fe8036c3a767c9d8cbbf98debbb68e07 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 14 Sep 2021 16:02:31 -0600 Subject: [PATCH 08/24] Remove stonewall pre-opening code in favor of dynamic softlock prevention (Promoted from experimental) --- DoorShuffle.py | 6 ++---- DungeonGenerator.py | 44 ------------------------------------------ Main.py | 2 +- RELEASENOTES.md | 8 ++++++++ Rom.py | 9 +-------- asm/overrides.asm | 6 +----- data/base2current.bps | Bin 135945 -> 135928 bytes 7 files changed, 13 insertions(+), 62 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 9d22893c..ee9cbd01 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1358,10 +1358,8 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map): if recombine.master_sector is None: recombine.master_sector = builder.master_sector recombine.master_sector.name = recombine.name - recombine.pre_open_stonewalls = builder.pre_open_stonewalls else: recombine.master_sector.regions.extend(builder.master_sector.regions) - recombine.pre_open_stonewalls.update(builder.pre_open_stonewalls) recombine.layout_starts = list(entrances_map[recombine.name]) dungeon_builders[recombine.name] = recombine @@ -2043,8 +2041,8 @@ class DROptions(Flag): Debug = 0x08 # Rails = 0x10 # Unused bit now OriginalPalettes = 0x20 - Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required - Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required + # Open_PoD_Wall = 0x40 # No longer pre-opening pod wall - unused + # Open_Desert_Wall = 0x80 # No longer pre-opening desert wall - unused Hide_Total = 0x100 DarkWorld_Spawns = 0x200 diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 19ab9a1d..12aaa1c9 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -56,23 +56,10 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player): def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player): - stonewalls = check_for_stonewalls(builder) sector = generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player) - for stonewall in stonewalls: - if not stonewall_valid(stonewall): - builder.pre_open_stonewalls.add(stonewall) return sector -def check_for_stonewalls(builder): - stonewalls = set() - for sector in builder.sectors: - for door in sector.outstanding_doors: - if door.stonewall: - stonewalls.add(door) - return stonewalls - - def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player): if builder.valid_proposal: # we made this earlier in gen, just use it proposed_map = builder.valid_proposal @@ -612,35 +599,6 @@ def winnow_hangers(hangers, hooks): hangers[hanger].remove(door) -def stonewall_valid(stonewall): - bad_door = stonewall.dest - if bad_door.blocked: - return True # great we're done with this one - loop_region = stonewall.entrance.parent_region - start_regions = [bad_door.entrance.parent_region] - if bad_door.dependents: - for dep in bad_door.dependents: - start_regions.append(dep.entrance.parent_region) - queue = deque(start_regions) - visited = set(start_regions) - while len(queue) > 0: - region = queue.popleft() - if region == loop_region: - return False # guaranteed loop - possible_entrances = list(region.entrances) - for entrance in possible_entrances: - parent = entrance.parent_region - if parent.type != RegionType.Dungeon: - return False # you can get stuck from an entrance - else: - door = entrance.door - if (door is None or (door != stonewall and not door.blocked)) and parent not in visited: - visited.add(parent) - queue.append(parent) - # we didn't find anything bad - return True - - def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception): # todo: info about dungeon events - not sure about that graph_piece = GraphPiece() @@ -1198,8 +1156,6 @@ class DungeonBuilder(object): self.path_entrances = None # used for pathing/key doors, I think self.split_flag = False - self.pre_open_stonewalls = set() # used by stonewall system - self.candidates = None self.total_keys = None self.key_doors_num = None diff --git a/Main.py b/Main.py index 11bf51d3..0db8af04 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__ = '0.5.1.1-u' +__version__ = '0.5.1.2-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4d212275..55a34c3f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,14 @@ CLI: ```--bombbag``` # Bug Fixes and Notes. +* 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) diff --git a/Rom.py b/Rom.py index d6248488..22f017e3 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'ef6e3e1aa59838c01dbd5b1b2387e70c' +RANDOMIZERBASEHASH = '11f4f494e999a919aafd7d2624e67679' class JsonRom(object): @@ -754,13 +754,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(paired_door.address_a(world, player), paired_door.rom_data_a(world, player)) rom.write_bytes(paired_door.address_b(world, player), paired_door.rom_data_b(world, player)) if world.doorShuffle[player] != 'vanilla': - if not world.experimental[player]: - for builder in world.dungeon_layouts[player].values(): - for stonewall in builder.pre_open_stonewalls: - if stonewall.name == 'Desert Wall Slide NW': - dr_flags |= DROptions.Open_Desert_Wall - elif stonewall.name == 'PoD Bow Statue Down Ladder': - dr_flags |= DROptions.Open_PoD_Wall for name, pair in boss_indicator.items(): dungeon_id, boss_door = pair opposite_door = world.get_door(boss_door, player).dest diff --git a/asm/overrides.asm b/asm/overrides.asm index f9842866..a041ae30 100644 --- a/asm/overrides.asm +++ b/asm/overrides.asm @@ -35,11 +35,7 @@ rtl OnFileLoadOverride: jsl OnFileLoad ; what I wrote over - lda.l DRFlags : and #$80 : beq + ;flag is off - lda $7ef086 : ora #$80 : sta $7ef086 - + lda.l DRFlags : and #$40 : beq + ;flag is off - lda $7ef036 : ora #$80 : sta $7ef036 - + lda.l DRFlags : and #$02 : beq + + + lda.l DRFlags : and #$02 : beq + ; Mirror Scroll lda $7ef353 : bne + lda #$01 : sta $7ef353 + rtl diff --git a/data/base2current.bps b/data/base2current.bps index 17b3e4976a8e05b8f7e0a167166c55236e3a9806..46dee4bec02691a6036774e7670b7a070e81727c 100644 GIT binary patch delta 1022 zcmW-de@q)?7{~9uw)CK^6tJ!)W4shtsRIR>QbZOk$e0eoh76n&8?uQ8H+5pj62k5Z zx2-|iYhTJ)Dgt8%6gEn{kOE6Bn-Xw;xNaGn&N#P#Q6~vkp&%g+Z~pcDkK;8ZC6i9=tEdv(0eet7PJtAD6juZMaXXrk9K@GcULzfp zfF?984+1N)6Gw^pcHMWJcDbDo6Z-&g^Z8Nb0BCuuVnYg){CQQn9uWL^ZY2O}q{uq} z)O>XwcvgAEn10V<__Lt%Sk=#i6{zm`EnH&c^WLi~27P@0rQdTv9%@_E5jl2J#sPQ= zO)c&Ob?B$Xq{sk+TIo&3*2?3*rO3Ah7+ULDB^4sI$?(}rJf0fT)EnxxxsHooDXG)r zLbSJHr8yYk0X9+V-FssPQQbRy!g2vP^MRumCna#)@%@&d={o)I#`KmTgHd*|E4D+s z&0x}){;#MfRoEVA*zrZ5gAx4?44g#{CnuqMTO+>RJ>i$?$zUWWr2%}Qh1|wTd+>8k zsA3>a_mqtBg}&}5fJOvRbuTSM^~igjB-Fqhvk5JXl>6or8BPMnQ zuN;}A2%Gt`jFZ9u^M~IxLo3h)I@;u@x{s2kTw;zg`IAujWZMHuZfk8@XuB>BblDcf z2cyxD7UZCtAsslyZ-kT}s{)ZvP0MV%NXrpKI*5mtUl&gye=$vCn2=^;sbtpN-iXL& z*z>N>5g7w(kzwVHG!J4wae4?$sP`tXHbW=7e*B=tJ%Ci@78eGBI`7_<5f%GuHX2!> zVh^#l3#{=3dG+FKvp-XShfw}16JOJbnkY_=1tkY+Iz6#z-kK=%U^Nlm>CkD;{N14& z3YY|)tLW*uIZrr6)c(djLLUJ$P+~YW?&FsPcWH|yCeYMb^5LcS2zU$~4jbdYbh_hJ zU3PJ$o<&BIie{B)B%BU{=*RF*(1q5+Wq73@?OWT0m%WQVT1!{HC3Dl)GON0aPf~2w zcHXo0@{Y*<#>~0GvnlX8yA%hKC9$c<7Z(rS&meM-`lzB=jlpLw09JKk;iunN8xqKy z*lPsrzM$u&<*E6jeub#cjWL zx`n;(1wAsfe;|Wm&10Yx-~X5I))>eJlGyC8e8ks4qg4$nrPnOOTUSPQm%FUBeJ(cZ ZHh_|>va7y|+hBAr@maM$aAWG>`hTN1v+e)@ delta 1039 zcmW-be^8Tk9LJyUvtJJk$AI8wh5Isuu^L5!H+~3BFoX zkV)*0&3=3!F~V=kho|U zC}3)fz!Q={dCj+`{3R`=Q-3p5kB%Jv3UA)d6uo++9Lz8Smlidk9(64!i8>o8paFEE zz``cbh3+h*5n(H8*UOQC;r+T|LYDm<&Cm@CO`Ox_<1t{Im#=%7}QmG!qkX0coQ?BUue64Q9$?|L!QEs)Pak69?rXv6ZhIe2^57l&r%_hc z^Y%ZHV3ZZWr)(U1-3G5&@^v!Z|A>BwHeN4GJ$Kpn1l=cH#9CV-tK8 z8H%C#MgX_8ksD}!FMh@W^Q;gz_DZiZ;<57&3o@>NYWs|AY_F)#LCWM%W6+_nS2i}k z!%FjDU55_twbBqCw5qfi97Oln)U6F;=G^;Eez9Yjol!-xVH$%O$cWP-E^uyy7O3K} z;c@r2@$)!*4jC~zj(Q0dFK2911oVW(a9Kd}Vc7cnglonqM*YS(oxSD037c-VomR$j>Z0#U~u9s*+n<;*( z#kup1OWi`)iOM6TNkNCZPTFJRPF7N=l+?#j2ar3W2CHZ;vI$J0#mI|z>Lk*vW#TD1 zRKKQ{qzT=|PjV{WFF&TIj;1iKwZg|^`eS#G?SBdPBaK{_w{ts8zk3A~m~9Ix~1 z%Vw)I{zfe@C(BPYPMVui$uF@HVNOfizu0XoiZ#DHH4nhPxieI?Uw#180ExYjf7Tvz zBB)Ou$bBs6Z?6GmwL*7w#bR*>b<+oW1-vM?qZ&s+C9fp+H~;h~$OSy+rR28Gx}m)-(bN{HFKva|0P@cI7$0bslV From ddd3aaf9a2e502335834d23f81fd8f0edf69eae5 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 16 Sep 2021 15:20:29 -0600 Subject: [PATCH 09/24] More prize door refinement Incorporating drop entrances better in the find proposal algorithm --- DoorShuffle.py | 3 ++- DungeonGenerator.py | 14 +++++++++----- KeyDoorShuffle.py | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index ee9cbd01..c70cc040 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -14,7 +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 KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout +from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock from Utils import ncr, kth_combination @@ -1463,6 +1463,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True 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) + determine_prize_lock(key_layout, world, player) while not validate_key_layout(key_layout, world, player): itr += 1 stop_early = False diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 12aaa1c9..f88499bd 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -56,11 +56,6 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player): def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player): - sector = generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player) - return sector - - -def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player): if builder.valid_proposal: # we made this earlier in gen, just use it proposed_map = builder.valid_proposal else: @@ -99,6 +94,15 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon if (access_region.name in world.inaccessible_regions[player] and region.name not in world.enabled_entrances[player]): excluded[region] = None + elif len(region.entrances) == 1: # for holes + access_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') + if access_region.name == 'Sewer Drop': + access_region = next(x.parent_region for x in access_region.entrances) + 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 = {} all_regions = set() diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 1b74ae80..a84fe5e8 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -27,6 +27,7 @@ class KeyLayout(object): self.found_doors = set() self.prize_relevant = None + self.prize_can_lock = None # if true, then you may need to beat the bo # bk special? # bk required? True if big chests or big doors exists @@ -1447,7 +1448,10 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa found_forced_bk = state.found_forced_bk() smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations) bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk) - prize_done = not key_layout.prize_relevant or state.prize_doors_opened + # prize door should not be opened if the boss is reachable - but not reached yet + allow_for_prize_lock = (key_layout.prize_can_lock and + not any(x for x in state.found_locations if '- Prize' in x.name)) + prize_done = not key_layout.prize_relevant or state.prize_doors_opened or allow_for_prize_lock if smalls_done and bk_done and prize_done: return False else: @@ -1531,6 +1535,39 @@ def enough_small_locations(state, avail_small_loc): return avail_small_loc >= len(unique_d_set) +def determine_prize_lock(key_layout, world, player): + if ((world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) + or world.logic[player] == 'nologic'): + return # done, doesn't matter what + flat_proposal = key_layout.flat_prop + state = ExplorationState(dungeon=key_layout.sector.name) + state.key_locations = key_layout.max_chests + state.big_key_special = check_bk_special(key_layout.sector.regions, world, player) + prize_lock_possible = False + for region in key_layout.start_regions: + dungeon_entrance, portal_door = find_outside_connection(region) + prize_relevant_flag = prize_relevance(key_layout, dungeon_entrance) + if prize_relevant_flag: + state.append_door_to_list(portal_door, state.prize_doors) + state.prize_door_set[portal_door] = dungeon_entrance + key_layout.prize_relevant = prize_relevant_flag + prize_lock_possible = True + else: + state.visit_region(region, key_checks=True) + state.add_all_doors_check_keys(region, flat_proposal, world, player) + if not prize_lock_possible: + return # done, no prize entrances to worry about + expand_key_state(state, flat_proposal, world, player) + while len(state.small_doors) > 0 or len(state.big_doors) > 0: + if len(state.big_doors) > 0: + open_a_door(state.big_doors[0].door, state, flat_proposal, world, player) + elif len(state.small_doors) > 0: + open_a_door(state.small_doors[0].door, state, flat_proposal, world, player) + expand_key_state(state, flat_proposal, world, player) + if any(x for x in state.found_locations if '- Prize' in x.name): + key_layout.prize_can_lock = True + + def cnt_avail_small_locations(free_locations, key_only, state, world, player): if not world.keyshuffle[player] and not world.retro[player]: bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0 From 1eda6cfa9a392a6fa3f4f99d6f560b2ff9066c95 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 16 Sep 2021 15:46:40 -0600 Subject: [PATCH 10/24] Fix lambda by binding bk_name at lambda creation --- Rules.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Rules.py b/Rules.py index 3114bde0..42bfe2f2 100644 --- a/Rules.py +++ b/Rules.py @@ -1954,7 +1954,7 @@ def add_key_logic_rules(world, player): 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) + set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player)) if world.retro[player]: for d_name, layout in world.key_layout[player].items(): for door in layout.flat_prop: @@ -1992,6 +1992,10 @@ def eval_small_key_door(door_name, dungeon, player): return lambda state: eval_small_key_door_main(state, door_name, dungeon, player) +def allow_big_key_in_big_chest(bk_name, player): + return lambda state, item: item.name == bk_name and item.player == player + + def retro_in_hc(spot): return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False From a11c23bde9c4cf6d00de4a32acae1a99b9338720 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 28 Sep 2021 15:09:29 -0500 Subject: [PATCH 11/24] Moving Tavern North to mandatory connections --- EntranceShuffle.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 8fda03fc..59f3aa28 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -257,9 +257,6 @@ def link_entrances(world, player): connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) single_doors.extend(bomb_shop_doors) - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - # place remaining doors connect_doors(world, single_doors, door_targets, player) elif world.shuffle[player] == 'restricted': @@ -303,9 +300,6 @@ def link_entrances(world, player): else: dw_entrances.remove(links_house) - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - # in restricted, the only mandatory exits are in dark world (lw in inverted) if not invFlag: connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) @@ -406,9 +400,6 @@ def link_entrances(world, player): lw_entrances.append('Desert Palace Entrance (North)') old_man_house = list(Old_Man_House) - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode[player] == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) @@ -664,9 +655,6 @@ def link_entrances(world, player): connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - #place must-exit caves connect_mandatory_exits(world, entrances, caves, must_exits, player) @@ -773,9 +761,6 @@ def link_entrances(world, player): hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Back Drop', 'Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle'] - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode[player] == 'standard': # cannot move uncle cave connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) @@ -2107,7 +2092,10 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), ('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'), ('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'), - ('Ganon Drop', 'Bottom of Pyramid') + ('Ganon Drop', 'Bottom of Pyramid'), + + # Unshuffled Entrances + ('Tavern North', 'Tavern') ] open_mandatory_connections = [('Sanctuary S&Q', 'Sanctuary'), @@ -2124,7 +2112,6 @@ default_connections = [('Lumberjack House', 'Lumberjack House'), ('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'), ('Light Hype Fairy', 'Swamp Healer Fairy'), ('Desert Fairy', 'Desert Healer Fairy'), - ('Tavern North', 'Tavern'), ('Lost Woods Gamble', 'Lost Woods Gamble'), ('Fortune Teller (Light)', 'Fortune Teller (Light)'), ('Snitch Lady (East)', 'Snitch Lady (East)'), From ced1c7785215dc031f5a7d9d8a127176d0dd80dc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 28 Sep 2021 15:12:46 -0500 Subject: [PATCH 12/24] Moved Dungeon Full shuffle logic to external function --- EntranceShuffle.py | 167 ++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 59f3aa28..8c4a69bd 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -38,15 +38,9 @@ def link_entrances(world, player): if not invFlag: for exitname, regionname in open_default_connections: connect_simple(world, exitname, regionname, player) - if world.shuffle[player] == 'vanilla': - for exitname, regionname in open_default_dungeon_connections: - connect_simple(world, exitname, regionname, player) else: for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) - if world.shuffle[player] == 'vanilla': - for exitname, regionname in inverted_default_dungeon_connections: - connect_simple(world, exitname, regionname, player) # inverted entrance mods for owid in swapped_connections.keys(): @@ -68,84 +62,7 @@ def link_entrances(world, player): if world.shuffle[player] == 'dungeonssimple': simple_shuffle_dungeons(world, player) elif world.shuffle[player] == 'dungeonsfull': - skull_woods_shuffle(world, player) - - dungeon_exits = list(Dungeon_Exits) - lw_entrances = list(LW_Dungeon_Entrances) if not invFlag else list(Inverted_LW_Dungeon_Entrances_Must_Exit) - dw_entrances = list(DW_Dungeon_Entrances) if not invFlag else list(Inverted_DW_Dungeon_Entrances) - - if world.mode[player] != 'standard': - lw_entrances.append('Hyrule Castle Entrance (South)') - - if not invFlag: - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - elif world.doorShuffle[player] == 'vanilla': - dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - else: - lw_dungeon_entrances_must_exit = list(Inverted_LW_Dungeon_Entrances_Must_Exit) - # randomize which desert ledge door is a must-exit - if random.randint(0, 1) == 0: - lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') - lw_entrances.append('Desert Palace Entrance (West)') - else: - lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)') - lw_entrances.append('Desert Palace Entrance (North)') - dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower' if not invFlag else 'Agahnims Tower', 'Ganons Tower Exit', player) - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] - else: - if not invFlag: - dw_entrances.append('Ganons Tower') - else: - lw_entrances.append('Agahnims Tower') - dungeon_exits.append('Ganons Tower Exit') - hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Agahnims Tower'] - - if not invFlag: - if world.mode[player] == 'standard': - # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert - hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')] - connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player) - connect_caves(world, lw_entrances, [], hyrule_castle_exits, player) - elif world.doorShuffle[player] != 'vanilla': - # sanc is in light world, so must all of HC if door shuffle is on - connect_mandatory_exits(world, lw_entrances, - [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], - list(LW_Dungeon_Entrances_Must_Exit), player) - else: - connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) - connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) - else: - # shuffle aga door first. If it's on HC ledge, remaining HC ledge door must be must-exit - all_entrances_aga = lw_entrances + dw_entrances - aga_doors = [i for i in all_entrances_aga] - random.shuffle(aga_doors) - aga_door = aga_doors.pop() - - if aga_door in hc_ledge_entrances: - lw_entrances.remove(aga_door) - hc_ledge_entrances.remove(aga_door) - - random.shuffle(hc_ledge_entrances) - hc_ledge_must_exit = hc_ledge_entrances.pop() - lw_entrances.remove(hc_ledge_must_exit) - lw_dungeon_entrances_must_exit.append(hc_ledge_must_exit) - - if aga_door in lw_entrances: - lw_entrances.remove(aga_door) - elif aga_door in dw_entrances: - dw_entrances.remove(aga_door) - - connect_two_way(world, aga_door, 'Agahnims Tower Exit', player) - dungeon_exits.remove('Agahnims Tower Exit') - - connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) - - connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) + full_shuffle_dungeons(world, Dungeon_Exits, player) elif world.shuffle[player] == 'simple': simple_shuffle_dungeons(world, player) @@ -1343,6 +1260,88 @@ def simple_shuffle_dungeons(world, player): connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) +def full_shuffle_dungeons(world, Dungeon_Exits, player): + invFlag = world.mode[player] == 'inverted' + + skull_woods_shuffle(world, player) + + dungeon_exits = list(Dungeon_Exits) + lw_entrances = list(LW_Dungeon_Entrances) if not invFlag else list(Inverted_LW_Dungeon_Entrances_Must_Exit) + dw_entrances = list(DW_Dungeon_Entrances) if not invFlag else list(Inverted_DW_Dungeon_Entrances) + + if world.mode[player] != 'standard': + lw_entrances.append('Hyrule Castle Entrance (South)') + + if not invFlag: + if world.mode[player] == 'standard': + # must connect front of hyrule castle to do escape + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) + elif world.doorShuffle[player] == 'vanilla': + dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) + else: + lw_dungeon_entrances_must_exit = list(Inverted_LW_Dungeon_Entrances_Must_Exit) + # randomize which desert ledge door is a must-exit + if random.randint(0, 1) == 0: + lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') + lw_entrances.append('Desert Palace Entrance (West)') + else: + lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)') + lw_entrances.append('Desert Palace Entrance (North)') + dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) + + if not world.shuffle_ganon: + connect_two_way(world, 'Ganons Tower' if not invFlag else 'Agahnims Tower', 'Ganons Tower Exit', player) + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] + else: + if not invFlag: + dw_entrances.append('Ganons Tower') + else: + lw_entrances.append('Agahnims Tower') + dungeon_exits.append('Ganons Tower Exit') + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Agahnims Tower'] + + if not invFlag: + if world.mode[player] == 'standard': + # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert + hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')] + connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player) + connect_caves(world, lw_entrances, [], hyrule_castle_exits, player) + elif world.doorShuffle[player] != 'vanilla': + # sanc is in light world, so must all of HC if door shuffle is on + connect_mandatory_exits(world, lw_entrances, + [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], + list(LW_Dungeon_Entrances_Must_Exit), player) + else: + connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) + connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) + else: + # shuffle aga door first. If it's on HC ledge, remaining HC ledge door must be must-exit + all_entrances_aga = lw_entrances + dw_entrances + aga_doors = [i for i in all_entrances_aga] + random.shuffle(aga_doors) + aga_door = aga_doors.pop() + + if aga_door in hc_ledge_entrances: + lw_entrances.remove(aga_door) + hc_ledge_entrances.remove(aga_door) + + random.shuffle(hc_ledge_entrances) + hc_ledge_must_exit = hc_ledge_entrances.pop() + lw_entrances.remove(hc_ledge_must_exit) + lw_dungeon_entrances_must_exit.append(hc_ledge_must_exit) + + if aga_door in lw_entrances: + lw_entrances.remove(aga_door) + elif aga_door in dw_entrances: + dw_entrances.remove(aga_door) + + connect_two_way(world, aga_door, 'Agahnims Tower Exit', player) + dungeon_exits.remove('Agahnims Tower Exit') + + connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) + + connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) + def unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits): def shuffle_lists_in_list(ls): for i, item in enumerate(ls): From dd1ec0ce52faf1f23e1a70b171b97b112f807650 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 28 Sep 2021 15:14:02 -0500 Subject: [PATCH 13/24] Moved dungeon-related connections to one area --- EntranceShuffle.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 8c4a69bd..7485a546 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -59,7 +59,14 @@ def link_entrances(world, player): connect_two_way(world, 'Bumper Cave (Top)', 'Death Mountain Return Cave Exit (West)', player) # dungeon entrance shuffle - if world.shuffle[player] == 'dungeonssimple': + if world.shuffle[player] == 'vanilla': + if not invFlag: + for exitname, regionname in open_default_dungeon_connections: + connect_simple(world, exitname, regionname, player) + else: + for exitname, regionname in inverted_default_dungeon_connections: + connect_simple(world, exitname, regionname, player) + elif world.shuffle[player] == 'dungeonssimple': simple_shuffle_dungeons(world, player) elif world.shuffle[player] == 'dungeonsfull': full_shuffle_dungeons(world, Dungeon_Exits, player) From 54b6b3dc05a85a04c43110adc287ac029590ae6c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 28 Sep 2021 15:29:51 -0500 Subject: [PATCH 14/24] Minor entrance formatting and rearrangement --- EntranceShuffle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 7485a546..99655774 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -113,10 +113,13 @@ def link_entrances(world, player): links_house = random.choice(links_house_doors) connect_two_way(world, links_house, 'Links House Exit', player) connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should always match link's house, except for plandos + if links_house in bomb_shop_doors: bomb_shop_doors.remove(links_house) if links_house in blacksmith_doors: blacksmith_doors.remove(links_house) + if links_house in old_man_entrances: + old_man_entrances.remove(links_house) if invFlag: if links_house in old_man_entrances: From 56b8492985a331b6d6e14ed6abaaaea5f9f09a92 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 30 Sep 2021 18:48:53 -0500 Subject: [PATCH 15/24] Added entrance and exit pool framework to ER --- EntranceShuffle.py | 570 +++++++++++++++++++++++++++++++++------------ 1 file changed, 415 insertions(+), 155 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 99655774..9717258f 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -3,10 +3,33 @@ import RaceRandom as random # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. from collections import defaultdict +entrance_pool = list() +exit_pool = list() def link_entrances(world, player): invFlag = world.mode[player] == 'inverted' + global entrance_pool, exit_pool + entrance_pool = Entrance_Pool_Base.copy() + exit_pool = Exit_Pool_Base.copy() + default_drops = default_drop_connections.copy() + default_dropexits = default_dropexit_connections.copy() + isolated_entrances = Isolated_LH_Doors.copy() + + # modifications to lists + if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]): + default_drops.append(tuple(('Pyramid Hole', 'Pyramid'))) + default_dropexits.append(tuple(('Pyramid Entrance', 'Pyramid Exit'))) + connect_simple(world, 'Other World S&Q', 'Pyramid Area', player) + else: + entrance_pool.remove('Pyramid Hole') + entrance_pool.add('Inverted Pyramid Hole') + entrance_pool.remove('Pyramid Entrance') + entrance_pool.add('Inverted Pyramid Entrance') + default_drops.append(tuple(('Inverted Pyramid Hole', 'Pyramid'))) + default_dropexits.append(tuple(('Inverted Pyramid Entrance', 'Pyramid Exit'))) + connect_simple(world, 'Other World S&Q', 'Hyrule Castle Ledge', player) + Dungeon_Exits = Dungeon_Exits_Base.copy() Cave_Exits = Cave_Exits_Base.copy() Old_Man_House = Old_Man_House_Base.copy() @@ -25,22 +48,25 @@ def link_entrances(world, player): for exitname, regionname in inverted_mandatory_connections: connect_simple(world, exitname, regionname, player) + connect_simple(world, 'Tavern North', 'Tavern', player) + connect_custom(world, player) # if we do not shuffle, set default connections if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - for exitname, regionname in default_connections + default_connector_connections + default_drop_connections + default_item_connections + default_shop_connections: - connect_simple(world, exitname, regionname, player) - if world.shuffle[player] == 'vanilla': - for exitname, regionname in default_dungeon_connections: - connect_simple(world, exitname, regionname, player) + for entrancename, exitname in default_connections + default_drops + default_item_connections + default_shop_connections: + connect_logical(world, entrancename, exitname, player, False) + for entrancename, exitname in default_connector_connections + default_dropexits: + connect_logical(world, entrancename, exitname, player, True) if not invFlag: - for exitname, regionname in open_default_connections: - connect_simple(world, exitname, regionname, player) + for entrancename, exitname in open_default_connections: + connect_logical(world, entrancename, exitname, player, True) + connect_exit(world, 'Chris Houlihan Room Exit', 'Links House', player) else: - for exitname, regionname in inverted_default_connections: - connect_simple(world, exitname, regionname, player) + for entrancename, exitname in inverted_default_connections: + connect_logical(world, entrancename, exitname, player, True) + connect_exit(world, 'Chris Houlihan Room Exit', 'Big Bomb Shop', player) # inverted entrance mods for owid in swapped_connections.keys(): @@ -60,12 +86,17 @@ def link_entrances(world, player): # dungeon entrance shuffle if world.shuffle[player] == 'vanilla': + for entrancename, exitname in default_dungeon_connections: + connect_logical(world, entrancename, exitname, player, True) + for entrancename, exitname in default_skulldrop_connections: + connect_logical(world, entrancename, exitname, player, False) + if not invFlag: - for exitname, regionname in open_default_dungeon_connections: - connect_simple(world, exitname, regionname, player) + for entrancename, exitname in open_default_dungeon_connections: + connect_logical(world, entrancename, exitname, player, True) else: - for exitname, regionname in inverted_default_dungeon_connections: - connect_simple(world, exitname, regionname, player) + for entrancename, exitname in inverted_default_dungeon_connections: + connect_logical(world, entrancename, exitname, player, True) elif world.shuffle[player] == 'dungeonssimple': simple_shuffle_dungeons(world, player) elif world.shuffle[player] == 'dungeonsfull': @@ -855,7 +886,7 @@ def link_entrances(world, player): world.powder_patch_required[player] = True # check for ganon location - if world.get_entrance('Pyramid Hole' if invFlag == (0x03 in world.owswaps[player][0] and world.owMixed[player]) else 'Inverted Pyramid Hole', player).connected_region.name != 'Pyramid': + if world.get_entrance('Pyramid Hole' if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]) else 'Inverted Pyramid Hole', player).connected_region.name != 'Pyramid': world.ganon_at_pyramid[player] = False # check for Ganon's Tower location @@ -874,6 +905,29 @@ def connect_simple(world, exitname, regionname, player): world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) +def connect_logical(world, entrancename, exitname, player, isTwoWay = False): + if entrancename not in entrance_pool: + x = 9 + if exitname not in exit_pool: + x = 9 + assert entrancename in entrance_pool, 'Entrance not in pool: ' + entrancename + assert exitname in exit_pool, 'Exit not in pool: ' + exitname + try: + region = world.get_region(exitname, player) + exit = None + except RuntimeError: + exit = world.get_entrance(exitname, player) + region = exit.parent_region + + connect_simple(world, entrancename, region.name, player) + if isTwoWay: + region = world.get_entrance(entrancename, player).parent_region + connect_simple(world, exitname, region.name, player) + + entrance_pool.remove(entrancename) + exit_pool.remove(exitname) + + def connect_entrance(world, entrancename, exitname, player): entrance = world.get_entrance(entrancename, player) # check if we got an entrance or a region to connect to @@ -2071,6 +2125,285 @@ Inverted_Must_Exit_Invalid_Connections = defaultdict(set, { 'Hyrule Castle Entrance (East)': {'Hyrule Castle Entrance (West)', 'Agahnims Tower'}, }) +Entrance_Pool_Base = {'Links House', + 'Desert Palace Entrance (South)', + 'Desert Palace Entrance (West)', + 'Desert Palace Entrance (East)', + 'Desert Palace Entrance (North)', + 'Eastern Palace', + 'Tower of Hera', + 'Hyrule Castle Entrance (South)', + 'Hyrule Castle Entrance (West)', + 'Hyrule Castle Entrance (East)', + 'Agahnims Tower', + 'Thieves Town', + 'Skull Woods First Section Door', + 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)', + 'Skull Woods Final Section', + 'Ice Palace', + 'Misery Mire', + 'Palace of Darkness', + 'Swamp Palace', + 'Turtle Rock', + 'Dark Death Mountain Ledge (West)', + 'Dark Death Mountain Ledge (East)', + 'Turtle Rock Isolated Ledge Entrance', + 'Hyrule Castle Secret Entrance Stairs', + 'Kakariko Well Cave', + 'Bat Cave Cave', + 'Elder House (East)', + 'Elder House (West)', + 'North Fairy Cave', + 'Lost Woods Hideout Stump', + 'Lumberjack Tree Cave', + 'Two Brothers House (East)', + 'Two Brothers House (West)', + 'Sanctuary', + 'Old Man Cave (East)', + 'Old Man Cave (West)', + 'Old Man House (Bottom)', + 'Old Man House (Top)', + 'Death Mountain Return Cave (West)', + 'Death Mountain Return Cave (East)', + 'Spectacle Rock Cave (Bottom)', + 'Spectacle Rock Cave', + 'Spectacle Rock Cave Peak', + 'Paradox Cave (Bottom)', + 'Paradox Cave (Middle)', + 'Paradox Cave (Top)', + 'Fairy Ascension Cave (Bottom)', + 'Fairy Ascension Cave (Top)', + 'Spiral Cave (Bottom)', + 'Spiral Cave', + 'Bumper Cave (Top)', + 'Bumper Cave (Bottom)', + 'Superbunny Cave (Top)', + 'Superbunny Cave (Bottom)', + 'Hookshot Cave', + 'Hookshot Cave Back Entrance', + 'Ganons Tower', + 'Pyramid Entrance', + 'Waterfall of Wishing', + 'Dam', + 'Blinds Hideout', + 'Lumberjack House', + 'Bonk Fairy (Light)', + 'Bonk Fairy (Dark)', + 'Lake Hylia Fairy', + 'Light Hype Fairy', + 'Desert Fairy', + 'Dark Lake Hylia Fairy', + 'Dark Lake Hylia Ledge Fairy', + 'Dark Desert Fairy', + 'Dark Death Mountain Fairy', + 'Fortune Teller (Light)', + 'Lake Hylia Fortune Teller', + 'Kings Grave', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Cave Shop (Dark Death Mountain)', + 'Capacity Upgrade', + 'Blacksmiths Hut', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Cave 45', + 'Graveyard Cave', + 'Checkerboard Cave', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Bonk Rock Cave', + 'Library', + 'Kakariko Gamble Game', + 'Potion Shop', + 'Hookshot Fairy', + 'Pyramid Fairy', + 'East Dark World Hint', + 'Palace of Darkness Hint', + 'Big Bomb Shop', + 'Dark World Shop', + 'Dark Lake Hylia Shop', + 'Dark World Lumberjack Shop', + 'Dark World Potion Shop', + 'Dark Lake Hylia Ledge Spike Cave', + 'Dark Lake Hylia Ledge Hint', + 'Hype Cave', + 'Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Hammer Peg Cave', + 'Red Shield Shop', + 'Dark Sanctuary Hint', + 'Fortune Teller (Dark)', + 'Archery Game', + 'Mire Shed', + 'Dark Desert Hint', + 'Spike Cave', + 'Mimic Cave', + 'Kakariko Well Drop', + 'Hyrule Castle Secret Entrance Drop', + 'Bat Cave Drop', + 'North Fairy Cave Drop', + 'Lost Woods Hideout Drop', + 'Lumberjack Tree Tree', + 'Sanctuary Grave', + 'Skull Woods Second Section Hole', + 'Skull Woods First Section Hole (West)', + 'Skull Woods First Section Hole (East)', + 'Skull Woods First Section Hole (North)', + 'Pyramid Hole'} + +Exit_Pool_Base = {'Links House Exit', + 'Desert Palace Exit (South)', + 'Desert Palace Exit (West)', + 'Desert Palace Exit (East)', + 'Desert Palace Exit (North)', + 'Eastern Palace Exit', + 'Tower of Hera Exit', + 'Hyrule Castle Exit (South)', + 'Hyrule Castle Exit (West)', + 'Hyrule Castle Exit (East)', + 'Agahnims Tower Exit', + 'Thieves Town Exit', + 'Skull Woods First Section Exit', + 'Skull Woods Second Section Exit (East)', + 'Skull Woods Second Section Exit (West)', + 'Skull Woods Final Section Exit', + 'Ice Palace Exit', + 'Misery Mire Exit', + 'Palace of Darkness Exit', + 'Swamp Palace Exit', + 'Turtle Rock Exit (Front)', + 'Turtle Rock Ledge Exit (West)', + 'Turtle Rock Ledge Exit (East)', + 'Turtle Rock Isolated Ledge Exit', + 'Hyrule Castle Secret Entrance Exit', + 'Kakariko Well Exit', + 'Bat Cave Exit', + 'Elder House Exit (East)', + 'Elder House Exit (West)', + 'North Fairy Cave Exit', + 'Lost Woods Hideout Exit', + 'Lumberjack Tree Exit', + 'Two Brothers House Exit (East)', + 'Two Brothers House Exit (West)', + 'Sanctuary Exit', + 'Old Man Cave Exit (East)', + 'Old Man Cave Exit (West)', + 'Old Man House Exit (Bottom)', + 'Old Man House Exit (Top)', + 'Death Mountain Return Cave Exit (West)', + 'Death Mountain Return Cave Exit (East)', + 'Spectacle Rock Cave Exit', + 'Spectacle Rock Cave Exit (Top)', + 'Spectacle Rock Cave Exit (Peak)', + 'Paradox Cave Exit (Bottom)', + 'Paradox Cave Exit (Middle)', + 'Paradox Cave Exit (Top)', + 'Fairy Ascension Cave Exit (Bottom)', + 'Fairy Ascension Cave Exit (Top)', + 'Spiral Cave Exit', + 'Spiral Cave Exit (Top)', + 'Bumper Cave Exit (Top)', + 'Bumper Cave Exit (Bottom)', + 'Superbunny Cave Exit (Top)', + 'Superbunny Cave Exit (Bottom)', + 'Hookshot Cave Front Exit', + 'Hookshot Cave Back Exit', + 'Ganons Tower Exit', + 'Pyramid Exit', + 'Waterfall of Wishing', + 'Dam', + 'Blinds Hideout', + 'Lumberjack House', + 'Bonk Fairy (Light)', + 'Bonk Fairy (Dark)', + 'Lake Hylia Healer Fairy', + 'Swamp Healer Fairy', + 'Desert Healer Fairy', + 'Dark Lake Hylia Healer Fairy', + 'Dark Lake Hylia Ledge Healer Fairy', + 'Dark Desert Healer Fairy', + 'Dark Death Mountain Healer Fairy', + 'Fortune Teller (Light)', + 'Lake Hylia Fortune Teller', + 'Kings Grave', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Cave Shop (Dark Death Mountain)', + 'Capacity Upgrade', + 'Blacksmiths Hut', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Cave 45', + 'Graveyard Cave', + 'Checkerboard Cave', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Bonk Rock Cave', + 'Library', + 'Kakariko Gamble Game', + 'Potion Shop', + 'Hookshot Fairy', + 'Pyramid Fairy', + 'East Dark World Hint', + 'Palace of Darkness Hint', + 'Big Bomb Shop', + 'Village of Outcasts Shop', + 'Dark Lake Hylia Shop', + 'Dark World Lumberjack Shop', + 'Dark World Potion Shop', + 'Dark Lake Hylia Ledge Spike Cave', + 'Dark Lake Hylia Ledge Hint', + 'Hype Cave', + 'Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Hammer Peg Cave', + 'Red Shield Shop', + 'Dark Sanctuary Hint', + 'Fortune Teller (Dark)', + 'Archery Game', + 'Mire Shed', + 'Dark Desert Hint', + 'Spike Cave', + 'Mimic Cave', + 'Kakariko Well (top)', + 'Hyrule Castle Secret Entrance', + 'Bat Cave (right)', + 'North Fairy Cave', + 'Lost Woods Hideout (top)', + 'Lumberjack Tree (top)', + 'Sewer Drop', + 'Skull Back Drop', + 'Skull Left Drop', + 'Skull Pinball', + 'Skull Pot Circle', + 'Pyramid'} # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions mandatory_connections = [('Links House S&Q', 'Links House'), @@ -2101,18 +2434,12 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), ('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'), ('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'), - ('Ganon Drop', 'Bottom of Pyramid'), - - # Unshuffled Entrances - ('Tavern North', 'Tavern') + ('Ganon Drop', 'Bottom of Pyramid') ] -open_mandatory_connections = [('Sanctuary S&Q', 'Sanctuary'), - ('Other World S&Q', 'Pyramid Area')] +open_mandatory_connections = [('Sanctuary S&Q', 'Sanctuary')] -inverted_mandatory_connections = [('Sanctuary S&Q', 'Dark Sanctuary Hint'), - ('Other World S&Q', 'Hyrule Castle Ledge'), - ('Dark Sanctuary Hint Exit', 'Dark Chapel Area')] +inverted_mandatory_connections = [('Sanctuary S&Q', 'Dark Sanctuary Hint')] # non-shuffled entrance links default_connections = [('Lumberjack House', 'Lumberjack House'), @@ -2150,58 +2477,32 @@ default_connections = [('Lumberjack House', 'Lumberjack House'), ('Dark Death Mountain Fairy', 'Dark Death Mountain Healer Fairy'), ] -default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Ledge'), - ('Old Man Cave (East)', 'Old Man Cave'), - ('Old Man Cave Exit (West)', 'Mountain Entry Entrance'), - ('Old Man Cave Exit (East)', 'West Death Mountain (Bottom)'), - ('Old Man House (Bottom)', 'Old Man House'), - ('Old Man House Exit (Bottom)', 'West Death Mountain (Bottom)'), - ('Old Man House (Top)', 'Old Man House Back'), - ('Old Man House Exit (Top)', 'West Death Mountain (Bottom)'), - ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave'), - ('Death Mountain Return Cave (West)', 'Death Mountain Return Cave'), - ('Death Mountain Return Cave Exit (West)', 'Mountain Entry Ledge'), - ('Death Mountain Return Cave Exit (East)', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Peak)'), - ('Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave (Bottom)'), - ('Spectacle Rock Cave', 'Spectacle Rock Cave (Top)'), - ('Spectacle Rock Cave Exit', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Cave Exit (Top)', 'West Death Mountain (Bottom)'), - ('Spectacle Rock Cave Exit (Peak)', 'West Death Mountain (Bottom)'), - ('Spiral Cave', 'Spiral Cave (Top)'), - ('Spiral Cave (Bottom)', 'Spiral Cave (Bottom)'), - ('Spiral Cave Exit', 'East Death Mountain (Bottom)'), - ('Spiral Cave Exit (Top)', 'Spiral Cave Ledge'), - ('Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Bottom)'), - ('Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Top)'), - ('Fairy Ascension Cave Exit (Bottom)', 'Fairy Ascension Plateau'), - ('Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Ledge'), - ('Paradox Cave (Bottom)', 'Paradox Cave Front'), - ('Paradox Cave (Middle)', 'Paradox Cave'), - ('Paradox Cave (Top)', 'Paradox Cave'), - ('Paradox Cave Exit (Bottom)', 'East Death Mountain (Bottom)'), - ('Paradox Cave Exit (Middle)', 'East Death Mountain (Bottom)'), - ('Paradox Cave Exit (Top)', 'East Death Mountain (Top East)'), - ('Elder House (East)', 'Elder House'), - ('Elder House (West)', 'Elder House'), - ('Elder House Exit (East)', 'Kakariko Area'), - ('Elder House Exit (West)', 'Kakariko Area'), - ('Two Brothers House (East)', 'Two Brothers House'), - ('Two Brothers House (West)', 'Two Brothers House'), - ('Two Brothers House Exit (East)', 'Kakariko Suburb Area'), - ('Two Brothers House Exit (West)', 'Maze Race Ledge'), - ('Bumper Cave (Bottom)', 'Bumper Cave'), - ('Bumper Cave (Top)', 'Bumper Cave'), - ('Bumper Cave Exit (Top)', 'Bumper Cave Ledge'), - ('Bumper Cave Exit (Bottom)', 'Bumper Cave Entrance'), - ('Superbunny Cave (Top)', 'Superbunny Cave (Top)'), - ('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'), - ('Superbunny Cave Exit (Top)', 'East Dark Death Mountain (Top)'), - ('Superbunny Cave Exit (Bottom)', 'East Dark Death Mountain (Bottom)'), - ('Hookshot Cave', 'Hookshot Cave (Front)'), - ('Hookshot Cave Back Entrance', 'Hookshot Cave (Back)'), - ('Hookshot Cave Front Exit', 'East Dark Death Mountain (Top)'), - ('Hookshot Cave Back Exit', 'Dark Death Mountain Floating Island') +default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Exit (West)'), + ('Old Man Cave (East)', 'Old Man Cave Exit (East)'), + ('Old Man House (Bottom)', 'Old Man House Exit (Bottom)'), + ('Old Man House (Top)', 'Old Man House Exit (Top)'), + ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave Exit (East)'), + ('Death Mountain Return Cave (West)', 'Death Mountain Return Cave Exit (West)'), + ('Spectacle Rock Cave Peak', 'Spectacle Rock Cave Exit (Peak)'), + ('Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave Exit'), + ('Spectacle Rock Cave', 'Spectacle Rock Cave Exit (Top)'), + ('Spiral Cave', 'Spiral Cave Exit (Top)'), + ('Spiral Cave (Bottom)', 'Spiral Cave Exit'), + ('Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave Exit (Bottom)'), + ('Fairy Ascension Cave (Top)', 'Fairy Ascension Cave Exit (Top)'), + ('Paradox Cave (Bottom)', 'Paradox Cave Exit (Bottom)'), + ('Paradox Cave (Middle)', 'Paradox Cave Exit (Middle)'), + ('Paradox Cave (Top)', 'Paradox Cave Exit (Top)'), + ('Elder House (East)', 'Elder House Exit (East)'), + ('Elder House (West)', 'Elder House Exit (West)'), + ('Two Brothers House (East)', 'Two Brothers House Exit (East)'), + ('Two Brothers House (West)', 'Two Brothers House Exit (West)'), + ('Bumper Cave (Bottom)', 'Bumper Cave Exit (Bottom)'), + ('Bumper Cave (Top)', 'Bumper Cave Exit (Top)'), + ('Superbunny Cave (Top)', 'Superbunny Cave Exit (Top)'), + ('Superbunny Cave (Bottom)', 'Superbunny Cave Exit (Bottom)'), + ('Hookshot Cave', 'Hookshot Cave Front Exit'), + ('Hookshot Cave Back Entrance', 'Hookshot Cave Back Exit') ] default_item_connections = [('Mimic Cave', 'Mimic Cave'), @@ -2244,33 +2545,25 @@ default_shop_connections = [('Kakariko Shop', 'Kakariko Shop'), ] default_drop_connections = [('Lost Woods Hideout Drop', 'Lost Woods Hideout (top)'), - ('Lost Woods Hideout Stump', 'Lost Woods Hideout (bottom)'), - ('Lost Woods Hideout Exit', 'Lost Woods East Area'), ('Lumberjack Tree Tree', 'Lumberjack Tree (top)'), - ('Lumberjack Tree Cave', 'Lumberjack Tree (bottom)'), - ('Lumberjack Tree Exit', 'Lumberjack Area'), - ('Sanctuary', 'Sanctuary Portal'), ('Sanctuary Grave', 'Sewer Drop'), - ('Sanctuary Exit', 'Sanctuary Area'), ('North Fairy Cave Drop', 'North Fairy Cave'), - ('North Fairy Cave', 'North Fairy Cave'), - ('North Fairy Cave Exit', 'River Bend Area'), ('Kakariko Well Drop', 'Kakariko Well (top)'), - ('Kakariko Well Cave', 'Kakariko Well (bottom)'), - ('Kakariko Well Exit', 'Kakariko Area'), ('Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance'), - ('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance'), - ('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Courtyard Northeast'), ('Bat Cave Drop', 'Bat Cave (right)'), - ('Bat Cave Cave', 'Bat Cave (left)'), - ('Bat Cave Exit', 'Blacksmith Area'), - ('Pyramid Hole', 'Pyramid'), - ('Pyramid Entrance', 'Bottom of Pyramid'), - ('Inverted Pyramid Hole', 'Pyramid'), - ('Inverted Pyramid Entrance', 'Bottom of Pyramid'), - ('Pyramid Exit', 'Pyramid Exit Ledge') + #('Pyramid Hole', 'Pyramid') # this is dynamically added because of Inverted/OW Mixed ] +default_dropexit_connections = [('Lost Woods Hideout Stump', 'Lost Woods Hideout Exit'), + ('Lumberjack Tree Cave', 'Lumberjack Tree Exit'), + ('Sanctuary', 'Sanctuary Exit'), + ('North Fairy Cave', 'North Fairy Cave Exit'), + ('Kakariko Well Cave', 'Kakariko Well Exit'), + ('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit'), + ('Bat Cave Cave', 'Bat Cave Exit'), + #('Pyramid Entrance', 'Pyramid Exit') # this is dynamically added because of Inverted/OW Mixed + ] + swapped_connections = { 0x03: [ ('Old Man Cave (East)', 'Death Mountain Return Cave Exit (West)'), @@ -2282,75 +2575,44 @@ swapped_connections = { ('Death Mountain Return Cave (West)', 'Bumper Cave Exit (Top)'), ('Bumper Cave (Bottom)', 'Old Man Cave Exit (West)'), ('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy') - ], - 0x1b: [ - ('Inverted Pyramid Entrance', 'Pyramid Exit') ] } -open_default_connections = [('Links House', 'Links House'), - ('Links House Exit', 'Links House Area'), +open_default_connections = [('Links House', 'Links House Exit'), ('Big Bomb Shop', 'Big Bomb Shop') ] -inverted_default_connections = [('Links House', 'Big Bomb Shop'), - ('Links House Exit', 'Big Bomb Shop Area'), - ('Big Bomb Shop', 'Links House') +inverted_default_connections = [('Big Bomb Shop', 'Links House Exit'), + ('Links House', 'Big Bomb Shop') ] # non shuffled dungeons -default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert South Portal'), - ('Desert Palace Entrance (West)', 'Desert West Portal'), - ('Desert Palace Entrance (North)', 'Desert Back Portal'), - ('Desert Palace Entrance (East)', 'Desert East Portal'), - ('Desert Palace Exit (South)', 'Desert Palace Stairs'), - ('Desert Palace Exit (West)', 'Desert Ledge'), - ('Desert Palace Exit (East)', 'Desert Palace Mouth'), - ('Desert Palace Exit (North)', 'Desert Palace Entrance (North) Spot'), +default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace Exit (South)'), + ('Desert Palace Entrance (West)', 'Desert Palace Exit (West)'), + ('Desert Palace Entrance (North)', 'Desert Palace Exit (North)'), + ('Desert Palace Entrance (East)', 'Desert Palace Exit (East)'), + + ('Eastern Palace', 'Eastern Palace Exit'), + ('Tower of Hera', 'Tower of Hera Exit'), - ('Eastern Palace', 'Eastern Portal'), - ('Eastern Palace Exit', 'Eastern Palace Area'), - ('Tower of Hera', 'Hera Portal'), - ('Tower of Hera Exit', 'West Death Mountain (Top)'), + ('Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)'), + ('Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)'), + ('Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)'), - ('Hyrule Castle Entrance (South)', 'Hyrule Castle South Portal'), - ('Hyrule Castle Entrance (West)', 'Hyrule Castle West Portal'), - ('Hyrule Castle Entrance (East)', 'Hyrule Castle East Portal'), - ('Hyrule Castle Exit (South)', 'Hyrule Castle Courtyard'), - ('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'), - ('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'), + ('Thieves Town', 'Thieves Town Exit'), + ('Skull Woods First Section Door', 'Skull Woods First Section Exit'), + ('Skull Woods Second Section Door (East)', 'Skull Woods Second Section Exit (East)'), + ('Skull Woods Second Section Door (West)', 'Skull Woods Second Section Exit (West)'), + ('Skull Woods Final Section', 'Skull Woods Final Section Exit'), + ('Ice Palace', 'Ice Palace Exit'), + ('Misery Mire', 'Misery Mire Exit'), + ('Palace of Darkness', 'Palace of Darkness Exit'), + ('Swamp Palace', 'Swamp Palace Exit'), # requires additional patch for flooding moat if moved - ('Thieves Town', 'Thieves Town Portal'), - ('Thieves Town Exit', 'Village of Outcasts Area'), - ('Skull Woods First Section Hole (East)', 'Skull Pinball'), - ('Skull Woods First Section Hole (West)', 'Skull Left Drop'), - ('Skull Woods First Section Hole (North)', 'Skull Pot Circle'), - ('Skull Woods First Section Door', 'Skull 1 Portal'), - ('Skull Woods First Section Exit', 'Skull Woods Forest'), - ('Skull Woods Second Section Hole', 'Skull Back Drop'), - ('Skull Woods Second Section Door (East)', 'Skull 2 East Portal'), - ('Skull Woods Second Section Door (West)', 'Skull 2 West Portal'), - ('Skull Woods Second Section Exit (East)', 'Skull Woods Forest'), - ('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'), - ('Skull Woods Final Section', 'Skull 3 Portal'), - ('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'), - ('Ice Palace', 'Ice Portal'), - ('Ice Palace Exit', 'Ice Palace Area'), - ('Misery Mire', 'Mire Portal'), - ('Misery Mire Exit', 'Misery Mire Area'), - ('Palace of Darkness', 'Palace of Darkness Portal'), - ('Palace of Darkness Exit', 'Palace of Darkness Area'), - ('Swamp Palace', 'Swamp Portal'), # requires additional patch for flooding moat if moved - ('Swamp Palace Exit', 'Swamp Area'), - - ('Turtle Rock', 'Turtle Rock Main Portal'), - ('Turtle Rock Exit (Front)', 'Turtle Rock Area'), - ('Turtle Rock Ledge Exit (West)', 'Dark Death Mountain Ledge'), - ('Turtle Rock Ledge Exit (East)', 'Dark Death Mountain Ledge'), - ('Dark Death Mountain Ledge (West)', 'Turtle Rock Lazy Eyes Portal'), - ('Dark Death Mountain Ledge (East)', 'Turtle Rock Chest Portal'), - ('Turtle Rock Isolated Ledge Exit', 'Dark Death Mountain Isolated Ledge'), - ('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Eye Bridge Portal') + ('Turtle Rock', 'Turtle Rock Exit (Front)'), + ('Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)'), + ('Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)'), + ('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit') ] open_default_dungeon_connections = [('Ganons Tower', 'Ganons Tower Portal'), @@ -2359,10 +2621,8 @@ open_default_dungeon_connections = [('Ganons Tower', 'Ganons Tower Portal'), ('Agahnims Tower Exit', 'Hyrule Castle Ledge') ] -inverted_default_dungeon_connections = [('Ganons Tower', 'Agahnims Tower Portal'), - ('Ganons Tower Exit', 'Hyrule Castle Ledge'), - ('Agahnims Tower', 'Ganons Tower Portal'), - ('Agahnims Tower Exit', 'West Dark Death Mountain (Top)') +inverted_default_dungeon_connections = [('Ganons Tower', 'Agahnims Tower Exit'), + ('Agahnims Tower', 'Ganons Tower Exit') ] indirect_connections = { From 43fbd9c49cd7bb7085426db811ea7816bf8cbac8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 30 Sep 2021 18:49:51 -0500 Subject: [PATCH 16/24] Added entrance and exit pool framework to ER --- EntranceShuffle.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 9717258f..11809afa 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2615,10 +2615,14 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace ('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit') ] -open_default_dungeon_connections = [('Ganons Tower', 'Ganons Tower Portal'), - ('Ganons Tower Exit', 'West Dark Death Mountain (Top)'), - ('Agahnims Tower', 'Agahnims Tower Portal'), - ('Agahnims Tower Exit', 'Hyrule Castle Ledge') +default_skulldrop_connections = [('Skull Woods First Section Hole (East)', 'Skull Pinball'), + ('Skull Woods First Section Hole (West)', 'Skull Left Drop'), + ('Skull Woods First Section Hole (North)', 'Skull Pot Circle'), + ('Skull Woods Second Section Hole', 'Skull Back Drop') + ] + +open_default_dungeon_connections = [('Ganons Tower', 'Ganons Tower Exit'), + ('Agahnims Tower', 'Agahnims Tower Exit') ] inverted_default_dungeon_connections = [('Ganons Tower', 'Agahnims Tower Exit'), From cb51117c701c38fd18992622bfa2d6be38800366 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 09:59:23 -0500 Subject: [PATCH 17/24] Fixed Mystery to consider new Crossed/Mixed structure --- Mystery.py | 2 +- mystery_example.yml | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Mystery.py b/Mystery.py index 702a67d5..f18a4205 100644 --- a/Mystery.py +++ b/Mystery.py @@ -137,7 +137,7 @@ def roll_settings(weights): ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' ret.ow_crossed = get_choice('overworld_crossed') ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on' - ret.ow_mixed = get_choice('overworld_mixed') == 'on' + ret.ow_mixed = get_choice('overworld_swap') == 'on' overworld_flute = get_choice('flute_shuffle') ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla' entrance_shuffle = get_choice('entrance_shuffle') diff --git a/mystery_example.yml b/mystery_example.yml index 6bef94e6..9260b459 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -4,14 +4,18 @@ parallel: 2 full: 2 overworld_crossed: - on: 1 - off: 1 + none: 1 + polar: 1 + grouped: 1 + limited: 1 + chaos: 1 overworld_keepsimilar: on: 1 off: 1 - overworld_mixed: - on: 1 - off: 1 + overworld_swap: + vanilla: 1 + mixed: 2 + crossed: 2 flute_shuffle: vanilla: 0 balanced: 1 From ae5a7906a6bd3152e9f7fc416204851485ad9c64 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 10:16:35 -0500 Subject: [PATCH 18/24] Fixed spoiler log to consider non-boolean Crossed OW values --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index e0a52471..ab908177 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2766,8 +2766,8 @@ class Spoiler(object): outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla': outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No')) - outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_crossed'][player] else 'No')) - outfile.write('Mixed OW:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No')) + outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player]) + outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No')) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player]) From 9bff9e010dceebed7dfdd324c4c10fa3889a4af8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 13:38:30 -0500 Subject: [PATCH 19/24] Extended new entrance/exit pool to all other connect functions --- EntranceShuffle.py | 100 +++++++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 11809afa..6e77d33b 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -5,11 +5,12 @@ from collections import defaultdict entrance_pool = list() exit_pool = list() +ignore_pool = False def link_entrances(world, player): invFlag = world.mode[player] == 'inverted' - - global entrance_pool, exit_pool + + global entrance_pool, exit_pool, ignore_pool entrance_pool = Entrance_Pool_Base.copy() exit_pool = Exit_Pool_Base.copy() default_drops = default_drop_connections.copy() @@ -18,16 +19,16 @@ def link_entrances(world, player): # modifications to lists if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]): - default_drops.append(tuple(('Pyramid Hole', 'Pyramid'))) - default_dropexits.append(tuple(('Pyramid Entrance', 'Pyramid Exit'))) + drop_connections.append(tuple(('Pyramid Hole', 'Pyramid'))) + dropexit_connections.append(tuple(('Pyramid Entrance', 'Pyramid Exit'))) connect_simple(world, 'Other World S&Q', 'Pyramid Area', player) else: entrance_pool.remove('Pyramid Hole') entrance_pool.add('Inverted Pyramid Hole') entrance_pool.remove('Pyramid Entrance') entrance_pool.add('Inverted Pyramid Entrance') - default_drops.append(tuple(('Inverted Pyramid Hole', 'Pyramid'))) - default_dropexits.append(tuple(('Inverted Pyramid Entrance', 'Pyramid Exit'))) + drop_connections.append(tuple(('Inverted Pyramid Hole', 'Pyramid'))) + dropexit_connections.append(tuple(('Inverted Pyramid Entrance', 'Pyramid Exit'))) connect_simple(world, 'Other World S&Q', 'Hyrule Castle Ledge', player) Dungeon_Exits = Dungeon_Exits_Base.copy() @@ -54,18 +55,20 @@ def link_entrances(world, player): # if we do not shuffle, set default connections if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - for entrancename, exitname in default_connections + default_drops + default_item_connections + default_shop_connections: + for entrancename, exitname in default_connections + drop_connections + default_item_connections + default_shop_connections: connect_logical(world, entrancename, exitname, player, False) - for entrancename, exitname in default_connector_connections + default_dropexits: + for entrancename, exitname in default_connector_connections + dropexit_connections: connect_logical(world, entrancename, exitname, player, True) if not invFlag: for entrancename, exitname in open_default_connections: connect_logical(world, entrancename, exitname, player, True) + ignore_pool = True connect_exit(world, 'Chris Houlihan Room Exit', 'Links House', player) else: for entrancename, exitname in inverted_default_connections: connect_logical(world, entrancename, exitname, player, True) + ignore_pool = True connect_exit(world, 'Chris Houlihan Room Exit', 'Big Bomb Shop', player) # inverted entrance mods @@ -83,6 +86,7 @@ def link_entrances(world, player): elif invFlag != (0x0a in world.owswaps[player][0] and world.owMixed[player]) and \ invFlag == (0x03 in world.owswaps[player][0] and world.owMixed[player]): connect_two_way(world, 'Bumper Cave (Top)', 'Death Mountain Return Cave Exit (West)', player) + ignore_pool = False # dungeon entrance shuffle if world.shuffle[player] == 'vanilla': @@ -153,9 +157,6 @@ def link_entrances(world, player): old_man_entrances.remove(links_house) if invFlag: - if links_house in old_man_entrances: - old_man_entrances.remove(links_house) - # place dark sanc sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors] sanc_door = random.choice(sanc_doors) @@ -185,6 +186,7 @@ def link_entrances(world, player): connect_two_way(world, old_man_entrance if invFlag == (0x0a in world.owswaps[player][0] and world.owMixed[player]) else 'Bumper Cave (Bottom)', 'Old Man Cave Exit (West)', player) connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) + if invFlag and old_man_exit == 'Spike Cave': bomb_shop_doors.remove('Spike Cave') bomb_shop_doors.extend(old_man_entrances) @@ -245,10 +247,7 @@ def link_entrances(world, player): if world.mode[player] == 'standard' or not world.shufflelinks[player]: links_house = 'Links House' else: - if not invFlag: - links_house_doors = [i for i in lw_entrances if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in lw_entrances if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] + links_house_doors = [i for i in LW_Single_Cave_Doors if i not in isolated_entrances + ([] if not invFlag else Inverted_Dark_Sanctuary_Doors)] links_house = random.choice(links_house_doors) connect_two_way(world, links_house, 'Links House Exit', player) connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should always match link's house, except for plandos @@ -404,10 +403,7 @@ def link_entrances(world, player): if world.mode[player] == 'standard' or not world.shufflelinks[player]: links_house = 'Links House' else: - if not invFlag: - links_house_doors = [i for i in lw_entrances + lw_must_exits if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in dw_entrances if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] + links_house_doors = [i for i in LW_Single_Cave_Doors if i not in isolated_entrances + ([] if not invFlag else Inverted_Dark_Sanctuary_Doors)] links_house = random.choice(links_house_doors) connect_two_way(world, links_house, 'Links House Exit', player) connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should always match link's house, except for plandos @@ -589,10 +585,7 @@ def link_entrances(world, player): if world.mode[player] == 'standard' or not world.shufflelinks[player]: links_house = 'Links House' else: - if not invFlag: - links_house_doors = [i for i in entrances + must_exits if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in entrances + must_exits if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] + links_house_doors = [i for i in LW_Single_Cave_Doors if i not in isolated_entrances + ([] if not invFlag else Inverted_Dark_Sanctuary_Doors)] if not invFlag and world.doorShuffle[player] == 'crossed' and world.intensity[player] >= 3: exclusions = DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors\ + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Ganons Tower'] @@ -662,6 +655,7 @@ def link_entrances(world, player): connect_doors(world, entrances, door_targets, player) elif world.shuffle[player] == 'insanity': # beware ye who enter here + ignore_pool = True if not invFlag: entrances_must_exits = DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit + ['Skull Woods Second Section Door (West)'] @@ -781,10 +775,7 @@ def link_entrances(world, player): if world.mode[player] == 'standard' or not world.shufflelinks[player]: links_house = 'Links House' else: - if not invFlag: - links_house_doors = [i for i in doors if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] + links_house_doors = [i for i in LW_Single_Cave_Doors if i not in isolated_entrances + ([] if not invFlag else Inverted_Dark_Sanctuary_Doors)] if not invFlag and world.doorShuffle[player] == 'crossed' and world.intensity[player] >= 3: exclusions = DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors \ + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Ganons Tower'] @@ -906,12 +897,14 @@ def connect_simple(world, exitname, regionname, player): def connect_logical(world, entrancename, exitname, player, isTwoWay = False): - if entrancename not in entrance_pool: - x = 9 - if exitname not in exit_pool: - x = 9 - assert entrancename in entrance_pool, 'Entrance not in pool: ' + entrancename - assert exitname in exit_pool, 'Exit not in pool: ' + exitname + if not ignore_pool: + if entrancename not in entrance_pool: + x = 9 + if exitname not in exit_pool: + x = 9 + assert entrancename in entrance_pool, 'Entrance not in pool: ' + entrancename + assert exitname in exit_pool, 'Exit not in pool: ' + exitname + try: region = world.get_region(exitname, player) exit = None @@ -924,11 +917,20 @@ def connect_logical(world, entrancename, exitname, player, isTwoWay = False): region = world.get_entrance(entrancename, player).parent_region connect_simple(world, exitname, region.name, player) - entrance_pool.remove(entrancename) - exit_pool.remove(exitname) + if not ignore_pool: + entrance_pool.remove(entrancename) + exit_pool.remove(exitname) def connect_entrance(world, entrancename, exitname, player): + if not ignore_pool: + if entrancename not in entrance_pool: + x = 9 + if exitname not in exit_pool: + x = 9 + assert entrancename in entrance_pool, 'Entrance not in pool: ' + entrancename + assert exitname in exit_pool, 'Exit not in pool: ' + exitname + entrance = world.get_entrance(entrancename, player) # check if we got an entrance or a region to connect to try: @@ -946,11 +948,23 @@ def connect_entrance(world, entrancename, exitname, player): addresses = door_addresses[entrance.name][0] entrance.connect(region, addresses, target) + + if not ignore_pool: + entrance_pool.remove(entrancename) + exit_pool.remove(exitname) if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) def connect_exit(world, exitname, entrancename, player): + if not (ignore_pool or exitname == 'Chris Houlihan Room Exit'): + if entrancename not in entrance_pool: + x = 9 + if exitname not in exit_pool: + x = 9 + assert entrancename in entrance_pool, 'Entrance not in pool: ' + entrancename + assert exitname in exit_pool, 'Exit not in pool: ' + exitname + entrance = world.get_entrance(entrancename, player) exit = world.get_entrance(exitname, player) @@ -960,11 +974,23 @@ def connect_exit(world, exitname, entrancename, player): exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) + if not (ignore_pool or exitname == 'Chris Houlihan Room Exit'): + entrance_pool.remove(entrancename) + exit_pool.remove(exitname) + if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: world.spoiler.set_entrance(entrance.name, exit.name, 'exit', player) def connect_two_way(world, entrancename, exitname, player): + if not ignore_pool: + if entrancename not in entrance_pool: + x = 9 + if exitname not in exit_pool: + x = 9 + assert entrancename in entrance_pool, 'Entrance not in pool: ' + entrancename + assert exitname in exit_pool, 'Exit not in pool: ' + exitname + entrance = world.get_entrance(entrancename, player) exit = world.get_entrance(exitname, player) @@ -977,6 +1003,10 @@ def connect_two_way(world, entrancename, exitname, player): entrance.connect(exit.parent_region, door_addresses[entrance.name][0], exit_ids[exit.name][0]) exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) + if not ignore_pool: + entrance_pool.remove(entrancename) + exit_pool.remove(exitname) + if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: world.spoiler.set_entrance(entrance.name, exit.name, 'both', player) From c54bd67ddc33331b5e0f9d3cc530e18f485cb2fc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 13:40:14 -0500 Subject: [PATCH 20/24] Extended new entrance/exit pool to all other connect functions --- EntranceShuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 6e77d33b..89d23201 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -13,8 +13,8 @@ def link_entrances(world, player): global entrance_pool, exit_pool, ignore_pool entrance_pool = Entrance_Pool_Base.copy() exit_pool = Exit_Pool_Base.copy() - default_drops = default_drop_connections.copy() - default_dropexits = default_dropexit_connections.copy() + drop_connections = default_drop_connections.copy() + dropexit_connections = default_dropexit_connections.copy() isolated_entrances = Isolated_LH_Doors.copy() # modifications to lists From c99805f47d9c7f00b4c6232a6bfcdf8db3caffd3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 13:45:14 -0500 Subject: [PATCH 21/24] Minor tweaks in prep for ER rewrite --- BaseClasses.py | 3 +- EntranceShuffle.py | 37 ++-- Rules.py | 455 +++++++++++++++++++++++---------------------- 3 files changed, 243 insertions(+), 252 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ab908177..7ec38c89 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -12,7 +12,6 @@ except ImportError: from source.classes.BabelFish import BabelFish -from EntranceShuffle import door_addresses, indirect_connections from Utils import int16_as_bytes from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup from RoomData import Room @@ -598,6 +597,7 @@ class CollectionState(object): self.path[new_region] = (new_region.name, self.path.get(connection, None)) # Retry connections if the new region can unblock them + from EntranceShuffle import indirect_connections if new_region.name in indirect_connections: new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player) if new_entrance in bc and new_entrance.parent_region in rrp: @@ -2501,6 +2501,7 @@ class Shop(object): # [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index] entrances = self.region.entrances config = self.item_count + from EntranceShuffle import door_addresses if len(entrances) == 1 and entrances[0].name in door_addresses: door_id = door_addresses[entrances[0].name][0]+1 else: diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 89d23201..c5398aa1 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -53,6 +53,9 @@ def link_entrances(world, player): connect_custom(world, player) + if invFlag == (0x05 in world.owswaps[player][0] and world.owMixed[player]): + isolated_entrances.append('Mimic Cave') + # if we do not shuffle, set default connections if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: for entrancename, exitname in default_connections + drop_connections + default_item_connections + default_shop_connections: @@ -141,10 +144,7 @@ def link_entrances(world, player): if world.mode[player] == 'standard' or not world.shufflelinks[player]: links_house = 'Links House' else: - if not invFlag: - links_house_doors = [i for i in LW_Single_Cave_Doors if i not in Isolated_LH_Doors_Open] - else: - links_house_doors = [i for i in LW_Single_Cave_Doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] + links_house_doors = [i for i in LW_Single_Cave_Doors if i not in isolated_entrances + ([] if not invFlag else Inverted_Dark_Sanctuary_Doors)] links_house = random.choice(links_house_doors) connect_two_way(world, links_house, 'Links House Exit', player) connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should always match link's house, except for plandos @@ -1666,16 +1666,15 @@ LW_Single_Cave_Doors = ['Blinds Hideout', 'Mimic Cave', 'Links House'] -Isolated_LH_Doors_Open = ['Mimic Cave', - 'Kings Grave', - 'Waterfall of Wishing', - 'Desert Palace Entrance (South)', - 'Desert Palace Entrance (North)', - 'Capacity Upgrade', - 'Ice Palace', - 'Skull Woods Final Section', - 'Dark World Hammer Peg Cave', - 'Turtle Rock Isolated Ledge Entrance'] +Isolated_LH_Doors = ['Kings Grave', + 'Waterfall of Wishing', + 'Desert Palace Entrance (South)', + 'Desert Palace Entrance (North)', + 'Capacity Upgrade', + 'Ice Palace', + 'Skull Woods Final Section', + 'Dark World Hammer Peg Cave', + 'Turtle Rock Isolated Ledge Entrance'] DW_Single_Cave_Doors = ['Bonk Fairy (Dark)', 'Dark Sanctuary Hint', @@ -2125,16 +2124,6 @@ Inverted_Dark_Sanctuary_Doors = ['Dark Sanctuary Hint', 'Bumper Cave (Top)', 'Thieves Town'] -Isolated_LH_Doors = ['Kings Grave', - 'Waterfall of Wishing', - 'Desert Palace Entrance (South)', - 'Desert Palace Entrance (North)', - 'Capacity Upgrade', - 'Ice Palace', - 'Skull Woods Final Section', - 'Dark World Hammer Peg Cave', - 'Turtle Rock Isolated Ledge Entrance'] - # Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups Must_Exit_Invalid_Connections = defaultdict(set, { 'Dark Death Mountain Ledge (East)': {'Dark Death Mountain Ledge (West)', 'Mimic Cave'}, diff --git a/Rules.py b/Rules.py index b987eabb..702f7d2b 100644 --- a/Rules.py +++ b/Rules.py @@ -1629,239 +1629,240 @@ def find_rules_for_zelda_delivery(world, player): def set_big_bomb_rules(world, player): # this is a mess - bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0] - Normal_LW_entrances = ['Blinds Hideout', - 'Bonk Fairy (Light)', - 'Lake Hylia Fairy', - 'Light Hype Fairy', - 'Desert Fairy', - 'Chicken House', - 'Aginahs Cave', - 'Sahasrahlas Hut', - 'Cave Shop (Lake Hylia)', - 'Blacksmiths Hut', - 'Sick Kids House', - 'Lost Woods Gamble', - 'Fortune Teller (Light)', - 'Snitch Lady (East)', - 'Snitch Lady (West)', - 'Bush Covered House', - 'Tavern (Front)', - 'Light World Bomb Hut', - 'Kakariko Shop', - 'Mini Moldorm Cave', - 'Long Fairy Cave', - 'Good Bee Cave', - '20 Rupee Cave', - '50 Rupee Cave', - 'Ice Rod Cave', - 'Bonk Rock Cave', - 'Library', - 'Potion Shop', - 'Dam', - 'Lumberjack House', - 'Lake Hylia Fortune Teller', - 'Eastern Palace', - 'Kakariko Gamble Game', - 'Kakariko Well Cave', - 'Bat Cave Cave', - 'Elder House (East)', - 'Elder House (West)', - 'North Fairy Cave', - 'Lost Woods Hideout Stump', - 'Lumberjack Tree Cave', - 'Two Brothers House (East)', - 'Sanctuary', - 'Hyrule Castle Entrance (South)', - 'Hyrule Castle Secret Entrance Stairs'] - LW_walkable_entrances = ['Dark Lake Hylia Ledge Fairy', - 'Dark Lake Hylia Ledge Spike Cave', - 'Dark Lake Hylia Ledge Hint', - 'Mire Shed', - 'Dark Desert Hint', - 'Dark Desert Fairy', - 'Misery Mire'] - Northern_DW_entrances = ['Brewery', - 'C-Shaped House', - 'Chest Game', - 'Dark World Hammer Peg Cave', - 'Red Shield Shop', - 'Dark Sanctuary Hint', - 'Fortune Teller (Dark)', - 'Dark World Shop', - 'Dark World Lumberjack Shop', - 'Thieves Town', - 'Skull Woods First Section Door', - 'Skull Woods Second Section Door (East)'] - Southern_DW_entrances = ['Hype Cave', - 'Bonk Fairy (Dark)', - 'Archery Game', - 'Big Bomb Shop', - 'Dark Lake Hylia Shop', - 'Swamp Palace'] - Isolated_DW_entrances = ['Spike Cave', - 'Cave Shop (Dark Death Mountain)', - 'Dark Death Mountain Fairy', - 'Mimic Cave', - 'Skull Woods Second Section Door (West)', - 'Skull Woods Final Section', - 'Ice Palace', - 'Turtle Rock', - 'Dark Death Mountain Ledge (West)', - 'Dark Death Mountain Ledge (East)', - 'Bumper Cave (Top)', - 'Superbunny Cave (Top)', - 'Superbunny Cave (Bottom)', - 'Hookshot Cave', - 'Ganons Tower', - 'Turtle Rock Isolated Ledge Entrance', - 'Hookshot Cave Back Entrance'] - Isolated_LW_entrances = ['Capacity Upgrade', - 'Tower of Hera', - 'Death Mountain Return Cave (West)', - 'Paradox Cave (Top)', - 'Fairy Ascension Cave (Top)', - 'Spiral Cave', - 'Desert Palace Entrance (East)'] - West_LW_DM_entrances = ['Old Man Cave (East)', - 'Old Man House (Bottom)', - 'Old Man House (Top)', - 'Death Mountain Return Cave (East)', - 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave (Bottom)'] - East_LW_DM_entrances = ['Paradox Cave (Bottom)', - 'Paradox Cave (Middle)', - 'Hookshot Fairy', - 'Spiral Cave (Bottom)'] - Mirror_from_SDW_entrances = ['Two Brothers House (West)', - 'Cave 45'] - Castle_ledge_entrances = ['Hyrule Castle Entrance (West)', - 'Hyrule Castle Entrance (East)', - 'Agahnims Tower'] - Desert_mirrorable_ledge_entrances = ['Desert Palace Entrance (West)', - 'Desert Palace Entrance (North)', - 'Desert Palace Entrance (South)', - 'Checkerboard Cave'] + if len(world.get_region('Big Bomb Shop', player).entrances) > 0: + bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0] + Normal_LW_entrances = ['Blinds Hideout', + 'Bonk Fairy (Light)', + 'Lake Hylia Fairy', + 'Light Hype Fairy', + 'Desert Fairy', + 'Chicken House', + 'Aginahs Cave', + 'Sahasrahlas Hut', + 'Cave Shop (Lake Hylia)', + 'Blacksmiths Hut', + 'Sick Kids House', + 'Lost Woods Gamble', + 'Fortune Teller (Light)', + 'Snitch Lady (East)', + 'Snitch Lady (West)', + 'Bush Covered House', + 'Tavern (Front)', + 'Light World Bomb Hut', + 'Kakariko Shop', + 'Mini Moldorm Cave', + 'Long Fairy Cave', + 'Good Bee Cave', + '20 Rupee Cave', + '50 Rupee Cave', + 'Ice Rod Cave', + 'Bonk Rock Cave', + 'Library', + 'Potion Shop', + 'Dam', + 'Lumberjack House', + 'Lake Hylia Fortune Teller', + 'Eastern Palace', + 'Kakariko Gamble Game', + 'Kakariko Well Cave', + 'Bat Cave Cave', + 'Elder House (East)', + 'Elder House (West)', + 'North Fairy Cave', + 'Lost Woods Hideout Stump', + 'Lumberjack Tree Cave', + 'Two Brothers House (East)', + 'Sanctuary', + 'Hyrule Castle Entrance (South)', + 'Hyrule Castle Secret Entrance Stairs'] + LW_walkable_entrances = ['Dark Lake Hylia Ledge Fairy', + 'Dark Lake Hylia Ledge Spike Cave', + 'Dark Lake Hylia Ledge Hint', + 'Mire Shed', + 'Dark Desert Hint', + 'Dark Desert Fairy', + 'Misery Mire'] + Northern_DW_entrances = ['Brewery', + 'C-Shaped House', + 'Chest Game', + 'Dark World Hammer Peg Cave', + 'Red Shield Shop', + 'Dark Sanctuary Hint', + 'Fortune Teller (Dark)', + 'Dark World Shop', + 'Dark World Lumberjack Shop', + 'Thieves Town', + 'Skull Woods First Section Door', + 'Skull Woods Second Section Door (East)'] + Southern_DW_entrances = ['Hype Cave', + 'Bonk Fairy (Dark)', + 'Archery Game', + 'Big Bomb Shop', + 'Dark Lake Hylia Shop', + 'Swamp Palace'] + Isolated_DW_entrances = ['Spike Cave', + 'Cave Shop (Dark Death Mountain)', + 'Dark Death Mountain Fairy', + 'Mimic Cave', + 'Skull Woods Second Section Door (West)', + 'Skull Woods Final Section', + 'Ice Palace', + 'Turtle Rock', + 'Dark Death Mountain Ledge (West)', + 'Dark Death Mountain Ledge (East)', + 'Bumper Cave (Top)', + 'Superbunny Cave (Top)', + 'Superbunny Cave (Bottom)', + 'Hookshot Cave', + 'Ganons Tower', + 'Turtle Rock Isolated Ledge Entrance', + 'Hookshot Cave Back Entrance'] + Isolated_LW_entrances = ['Capacity Upgrade', + 'Tower of Hera', + 'Death Mountain Return Cave (West)', + 'Paradox Cave (Top)', + 'Fairy Ascension Cave (Top)', + 'Spiral Cave', + 'Desert Palace Entrance (East)'] + West_LW_DM_entrances = ['Old Man Cave (East)', + 'Old Man House (Bottom)', + 'Old Man House (Top)', + 'Death Mountain Return Cave (East)', + 'Spectacle Rock Cave Peak', + 'Spectacle Rock Cave', + 'Spectacle Rock Cave (Bottom)'] + East_LW_DM_entrances = ['Paradox Cave (Bottom)', + 'Paradox Cave (Middle)', + 'Hookshot Fairy', + 'Spiral Cave (Bottom)'] + Mirror_from_SDW_entrances = ['Two Brothers House (West)', + 'Cave 45'] + Castle_ledge_entrances = ['Hyrule Castle Entrance (West)', + 'Hyrule Castle Entrance (East)', + 'Agahnims Tower'] + Desert_mirrorable_ledge_entrances = ['Desert Palace Entrance (West)', + 'Desert Palace Entrance (North)', + 'Desert Palace Entrance (South)', + 'Checkerboard Cave'] - set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_reach('Pyramid Area', 'Region', player) or state.can_reach('East Dark World', 'Region', player)) and state.can_reach('Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) + set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_reach('Pyramid Area', 'Region', player) or state.can_reach('East Dark World', 'Region', player)) and state.can_reach('Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) - #crossing peg bridge starting from the southern dark world - def cross_peg_bridge(state): - return state.has('Hammer', player) and state.has_Pearl(player) + #crossing peg bridge starting from the southern dark world + def cross_peg_bridge(state): + return state.has('Hammer', player) and state.has_Pearl(player) - # returning via the eastern and southern teleporters needs the same items, so we use the southern teleporter for out routing. - # crossing preg bridge already requires hammer so we just add the gloves to the requirement - def southern_teleporter(state): - return state.can_lift_rocks(player) and cross_peg_bridge(state) + # returning via the eastern and southern teleporters needs the same items, so we use the southern teleporter for out routing. + # crossing preg bridge already requires hammer so we just add the gloves to the requirement + def southern_teleporter(state): + return state.can_lift_rocks(player) and cross_peg_bridge(state) - # the basic routes assume you can reach eastern light world with the bomb. - # you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp - def basic_routes(state): - return southern_teleporter(state) or state.has('Beat Agahnim 1', player) + # the basic routes assume you can reach eastern light world with the bomb. + # you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp + def basic_routes(state): + return southern_teleporter(state) or state.has('Beat Agahnim 1', player) - # Key for below abbreviations: - # P = pearl - # A = Aga1 - # H = hammer - # M = Mirror - # G = Glove + # Key for below abbreviations: + # P = pearl + # A = Aga1 + # H = hammer + # M = Mirror + # G = Glove - if bombshop_entrance.name in Normal_LW_entrances: - #1. basic routes - #2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror - # -> M or BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: basic_routes(state) or state.has_Mirror(player)) - elif bombshop_entrance.name in LW_walkable_entrances: - #1. Mirror then basic routes - # -> M and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and basic_routes(state)) - elif bombshop_entrance.name in Northern_DW_entrances: - #1. Mirror and basic routes - #2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl - # -> (Mitts and CPB) or (M and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and basic_routes(state))) - elif bombshop_entrance.name == 'Bumper Cave (Bottom)': - #1. Mirror and Lift rock and basic_routes - #2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) - #3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl - # -> (Mitts and CPB) or (((G or Flute) and M) and BR)) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.can_flute(player)) and state.has_Mirror(player)) and basic_routes(state))) - elif bombshop_entrance.name in Southern_DW_entrances: - #1. Mirror and enter via gate: Need mirror and Aga1 - #2. cross peg bridge: Need hammer and moon pearl - # -> CPB or (M and A) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player))) - elif bombshop_entrance.name in Isolated_DW_entrances: - # 1. mirror then flute then basic routes - # -> M and Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player) and basic_routes(state)) - elif bombshop_entrance.name in Isolated_LW_entrances: - # 1. flute then basic routes - # Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations. - # -> Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and basic_routes(state)) - elif bombshop_entrance.name in West_LW_DM_entrances: - # 1. flute then basic routes or mirror - # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly. - # -> Flute and (M or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('West Death Mountain (Bottom)', 'Region', player)) - elif bombshop_entrance.name in East_LW_DM_entrances: - # 1. flute then basic routes or mirror and hookshot - # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot - # -> Flute and ((M and Hookshot) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Death Mountain (Bottom)', 'Region', player)) - elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': - # Same as East_LW_DM_entrances except navigation without BR requires Mitts - # -> Flute and ((M and Hookshot and Mitts) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Death Mountain (Bottom)', 'Region', player) and state.can_lift_heavy_rocks(player)) - elif bombshop_entrance.name in Castle_ledge_entrances: - # 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror - # 2. flute then basic routes - # -> M or (Flute and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Hyrule Castle Ledge', 'Region', player)) - elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances: - # Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and: - # 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes - # 2. flute then basic routes - # -> (Mire access and M) or Flute) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Desert Ledge', 'Region', player)) - elif bombshop_entrance.name == 'Old Man Cave (West)': - # 1. Lift rock then basic_routes - # 2. flute then basic_routes - # -> (Flute or G) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Death Mountain Entrance', 'Region', player)) - elif bombshop_entrance.name == 'Graveyard Cave': - # 1. flute then basic routes - # 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge - # -> (Flute or (M and P and West Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('Dark Graveyard Area', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) - elif bombshop_entrance.name in Mirror_from_SDW_entrances: - # 1. flute then basic routes - # 2. (has South dark world access) use existing mirror spot, mirror again off ledge - # -> (Flute or (M and South Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('Pyramid Area', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) - elif bombshop_entrance.name == 'Dark World Potion Shop': - # 1. walk down by lifting rock: needs gloves and pearl` - # 2. walk down by hammering peg: needs hammer and pearl - # 3. mirror and basic routes - # -> (P and (H or Gloves)) or (M and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Dark Witch Area', 'Region', player)) - elif bombshop_entrance.name == 'Kings Grave': - # same as the Normal_LW_entrances case except that the pre-existing mirror is only possible if you have mitts - # (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot) - # to account for insanity, must consider a way to escape without a cave for basic_routes - # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) or state.can_flute(player) or (state.can_reach('Dark Graveyard Area', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player)))) - elif bombshop_entrance.name == 'Waterfall of Wishing': - # same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which - # means you need an escape route of either Flippers or Flute - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player))) - - #TODO: Fix red bomb rules, artifically adding a bunch of rules to help reduce unbeatable seeds in OW shuffle - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) - #add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Pyramid Area', 'Region', player)) - #add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has('Flippers', player) and state.can_flute(player) and state.has('Hammer', player) and state.has('Hookshot', player) and state.has_Pearl(player) and state.has_Mirror(player))) + if bombshop_entrance.name in Normal_LW_entrances: + #1. basic routes + #2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror + # -> M or BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: basic_routes(state) or state.has_Mirror(player)) + elif bombshop_entrance.name in LW_walkable_entrances: + #1. Mirror then basic routes + # -> M and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and basic_routes(state)) + elif bombshop_entrance.name in Northern_DW_entrances: + #1. Mirror and basic routes + #2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl + # -> (Mitts and CPB) or (M and BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and basic_routes(state))) + elif bombshop_entrance.name == 'Bumper Cave (Bottom)': + #1. Mirror and Lift rock and basic_routes + #2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) + #3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl + # -> (Mitts and CPB) or (((G or Flute) and M) and BR)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.can_flute(player)) and state.has_Mirror(player)) and basic_routes(state))) + elif bombshop_entrance.name in Southern_DW_entrances: + #1. Mirror and enter via gate: Need mirror and Aga1 + #2. cross peg bridge: Need hammer and moon pearl + # -> CPB or (M and A) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player))) + elif bombshop_entrance.name in Isolated_DW_entrances: + # 1. mirror then flute then basic routes + # -> M and Flute and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player) and basic_routes(state)) + elif bombshop_entrance.name in Isolated_LW_entrances: + # 1. flute then basic routes + # Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations. + # -> Flute and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and basic_routes(state)) + elif bombshop_entrance.name in West_LW_DM_entrances: + # 1. flute then basic routes or mirror + # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly. + # -> Flute and (M or BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('West Death Mountain (Bottom)', 'Region', player)) + elif bombshop_entrance.name in East_LW_DM_entrances: + # 1. flute then basic routes or mirror and hookshot + # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot + # -> Flute and ((M and Hookshot) or BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Death Mountain (Bottom)', 'Region', player)) + elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': + # Same as East_LW_DM_entrances except navigation without BR requires Mitts + # -> Flute and ((M and Hookshot and Mitts) or BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Death Mountain (Bottom)', 'Region', player) and state.can_lift_heavy_rocks(player)) + elif bombshop_entrance.name in Castle_ledge_entrances: + # 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror + # 2. flute then basic routes + # -> M or (Flute and BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Hyrule Castle Ledge', 'Region', player)) + elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances: + # Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and: + # 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes + # 2. flute then basic routes + # -> (Mire access and M) or Flute) and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Desert Ledge', 'Region', player)) + elif bombshop_entrance.name == 'Old Man Cave (West)': + # 1. Lift rock then basic_routes + # 2. flute then basic_routes + # -> (Flute or G) and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Death Mountain Entrance', 'Region', player)) + elif bombshop_entrance.name == 'Graveyard Cave': + # 1. flute then basic routes + # 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge + # -> (Flute or (M and P and West Dark World access) and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('Dark Graveyard Area', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) + elif bombshop_entrance.name in Mirror_from_SDW_entrances: + # 1. flute then basic routes + # 2. (has South dark world access) use existing mirror spot, mirror again off ledge + # -> (Flute or (M and South Dark World access) and BR + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('Pyramid Area', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) + elif bombshop_entrance.name == 'Dark World Potion Shop': + # 1. walk down by lifting rock: needs gloves and pearl` + # 2. walk down by hammering peg: needs hammer and pearl + # 3. mirror and basic routes + # -> (P and (H or Gloves)) or (M and BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Dark Witch Area', 'Region', player)) + elif bombshop_entrance.name == 'Kings Grave': + # same as the Normal_LW_entrances case except that the pre-existing mirror is only possible if you have mitts + # (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot) + # to account for insanity, must consider a way to escape without a cave for basic_routes + # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) or state.can_flute(player) or (state.can_reach('Dark Graveyard Area', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player)))) + elif bombshop_entrance.name == 'Waterfall of Wishing': + # same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which + # means you need an escape route of either Flippers or Flute + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player))) + + #TODO: Fix red bomb rules, artifically adding a bunch of rules to help reduce unbeatable seeds in OW shuffle + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) + #add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('Pyramid Area', 'Region', player)) + #add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has('Flippers', player) and state.can_flute(player) and state.has('Hammer', player) and state.has('Hookshot', player) and state.has_Pearl(player) and state.has_Mirror(player))) def set_inverted_big_bomb_rules(world, player): bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0] From 21525b6d18ed243862698c704432f17959b6736c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 13:46:32 -0500 Subject: [PATCH 22/24] Removal of OW world code in copy_world, in prep for ER rewrite --- Main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Main.py b/Main.py index 45ea40c8..220ca123 100644 --- a/Main.py +++ b/Main.py @@ -497,10 +497,6 @@ def copy_world(world): ret.state.stale = {player: True for player in range(1, world.players + 1)} ret.owedges = world.owedges - for edge in ret.owedges: - transition = ret.check_for_owedge(edge.name, edge.player) - if transition is not None: - transition.dest = edge ret.doors = world.doors for door in ret.doors: entrance = ret.check_for_entrance(door.name, door.player) From ac75575b5ea732f309f335b665fe590a91ecd895 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 14:07:46 -0500 Subject: [PATCH 23/24] Version bump 0.1.9.2 --- CHANGELOG.md | 5 +++++ OverworldShuffle.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6898aa..778324c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 0.1.9.2 +- Fixed spoiler log and mystery for new Crossed/Mixed structure +- Minor preparations and tweaks to ER framework (added global Entrance/Exit pool) +- ~~Merged DR v0.5.1.2 - Blind Prison shuffled outside TT/Keylogic Improvements~~ + ### 0.1.9.1 - Fixed logic issue with leaving IP entrance not requiring flippers - ~~Merged DR v0.5.1.1 - Map Indicator Fix/Boss Shuffle Bias/Shop Hints~~ diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 558f089f..7b7ad24c 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -2,7 +2,7 @@ import RaceRandom as random, logging, copy from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OpenStd, parallel_links, IsParallel -__version__ = '0.1.9.1-u' +__version__ = '0.1.9.2-u' def link_overworld(world, player): # setup mandatory connections From 237e1eccc57cc76ea8b307649aad20ff9cb0bf5e Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 2 Oct 2021 14:32:21 -0500 Subject: [PATCH 24/24] Fixed mystery example yml --- mystery_example.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mystery_example.yml b/mystery_example.yml index 9260b459..9d1d1501 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -4,7 +4,7 @@ parallel: 2 full: 2 overworld_crossed: - none: 1 + none: 4 polar: 1 grouped: 1 limited: 1 @@ -13,9 +13,8 @@ on: 1 off: 1 overworld_swap: - vanilla: 1 - mixed: 2 - crossed: 2 + on: 1 + off: 1 flute_shuffle: vanilla: 0 balanced: 1