diff --git a/BaseClasses.py b/BaseClasses.py index a82f3093..1a230d6b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -76,6 +76,7 @@ class World(object): self.can_take_damage = True self.hints = hints.copy() self.prizes = {} + self.default_zelda_region = {} self.dynamic_regions = [] self.dynamic_locations = [] self.spoiler_mode = spoiler_mode @@ -185,6 +186,7 @@ class World(object): set_player_attr('standardize_palettes', 'standardize') set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}) set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []}) + set_player_attr('default_zelda_region', 'Hyrule Dungeon Cellblock') set_player_attr('exp_cache', defaultdict(dict)) set_player_attr('enabled_entrances', {}) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28128e2b..8e83b3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.6.0.8 +- Re-fixed issue with Old Man spawning on pyramid +- Allowing Zelda to be in TT Prison for follower shuffle escape +- Fixed error with placing Old Man Cave in ER +- Fixed error when plando'ing followers at locations + ## 0.6.0.7 - Emergency fix for GT cutscene GFX diff --git a/Doors.py b/Doors.py index aa55cf0c..909fe40b 100644 --- a/Doors.py +++ b/Doors.py @@ -1302,8 +1302,12 @@ def create_doors(world, player): world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain') if world.mode[player] == 'standard' and 'Zelda Herself' not in [i.name for i in world.precollected_items if i.player == player]: - world.get_door('Hyrule Castle Throne Room Tapestry', player).event('Zelda Pickup') - world.get_door('Hyrule Castle Tapestry Backwards', player).event('Zelda Pickup') + if world.default_zelda_region[player] == 'Thieves Blind\'s Cell': + zelda_location = 'Suspicious Maiden' + else: + zelda_location = 'Zelda Pickup' + world.get_door('Hyrule Castle Throne Room Tapestry', player).event(zelda_location) + world.get_door('Hyrule Castle Tapestry Backwards', player).event(zelda_location) # crystal switches and barriers world.get_door('Hera Lobby Crystal Exit', player).c_switch() diff --git a/DungeonGenerator.py b/DungeonGenerator.py index d3e33bb8..3306b71d 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -586,13 +586,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name): paths.append(portal.door.entrance.parent_region.name) if world.mode[player] == 'standard': if name == 'Hyrule Castle': - paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) + paths.append(world.default_zelda_region[player]) + paths.append((world.default_zelda_region[player], 'Sanctuary')) if name == 'Hyrule Castle Sewers': paths.append('Sanctuary') if name == 'Hyrule Castle Dungeon': - paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) + paths.append(world.default_zelda_region[player]) + paths.append((world.default_zelda_region[player], 'Hyrule Castle Throne Room')) if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': paths.append('Thieves Attic Window') elif 'Thieves Attic Window' in all_r_names: @@ -1322,7 +1322,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge for r_name in dungeon_boss_sectors[key]: assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) if key == 'Hyrule Castle' and world.mode[player] == 'standard': - for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda + for r_name in [world.default_zelda_region[player], 'Sanctuary', 'Hyrule Castle Throne Room']: # 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' @@ -3091,7 +3091,7 @@ def split_dungeon_builder(builder, split_list, builder_info): if builder.name == 'Hyrule Castle': assign_sector(find_sector('Hyrule Castle Throne Room', candidate_sectors), dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole) - assign_sector(find_sector('Hyrule Dungeon Cellblock', candidate_sectors), + assign_sector(find_sector(world.default_zelda_region[player], candidate_sectors), dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole) dungeon_map['Hyrule Castle Dungeon'].throne_door = world.get_door('Hyrule Castle Throne Room N', player) dungeon_map['Hyrule Castle Sewers'].sewers_access = builder.throne_door diff --git a/ItemList.py b/ItemList.py index de36b74a..65d67f57 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1676,7 +1676,7 @@ def set_event_item(world, player, location_name, item_name=None): def shuffle_event_items(world, player): - if (world.shuffle_followers[player]): + if world.shuffle_followers[player]: available_quests = follower_quests.copy() available_pickups = [quests[0] for quests in available_quests.values()] @@ -1688,11 +1688,14 @@ def shuffle_event_items(world, player): available_pickups.remove(loc.item.name) - if world.mode[player] == 'standard': - if 'Zelda Herself' in available_pickups: - zelda_pickup = available_quests.pop('Zelda Pickup')[0] - available_pickups.remove(zelda_pickup) - set_event_item(world, player, 'Zelda Pickup', zelda_pickup) + if world.mode[player] == 'standard' and 'Zelda Herself' in available_pickups: + zelda_dropoff = 'Zelda Pickup' + if world.default_zelda_region[player] == 'Thieves Blind\'s Cell': + zelda_dropoff = 'Suspicious Maiden' + available_quests.pop(zelda_dropoff) + zelda_pickup = 'Zelda Herself' + available_pickups.remove(zelda_pickup) + set_event_item(world, player, zelda_dropoff, zelda_pickup) random.shuffle(available_pickups) @@ -1715,10 +1718,11 @@ def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_ item_player = player if len(item_parts) < 2 else int(item_parts[1]) item_name = item_parts[0] event_flag = False - if item_name in prize_set: - item_player = player # prizes must be for that player + if item_name in prize_set or item_name in follower_pickups: + item_player = player # must be for that player item_to_place = ItemFactory(item_name, item_player) - prize_pool.remove(item_name) + if item_name in prize_set: + prize_pool.remove(item_name) event_flag = True elif is_dungeon_item(item_name, world, item_player): item_to_place = next(x for x in dungeon_pool diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index d9c837b9..65ad0faf 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -1805,7 +1805,10 @@ def imp_locations_factory(world, player): return imp_locations imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] if world.mode[player] == 'standard': - imp_locations.append('Zelda Pickup') + if world.default_zelda_region[player] == 'Thieves Blinds\' Cell': + imp_locations.append('Suspicious Maiden') + else: + imp_locations.append('Zelda Pickup') imp_locations.append('Zelda Drop Off') return imp_locations diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 27c16ca1..6a7abb42 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.6.0.7' +version_number = '0.6.0.8' # branch indicator is intentionally different across branches version_branch = '' diff --git a/Rom.py b/Rom.py index faf0b6c3..b70e983b 100644 --- a/Rom.py +++ b/Rom.py @@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '93386b05ee4b5de6b9165941b9e14e97' +RANDOMIZERBASEHASH = '65fae75651987228878051028da066ad' class JsonRom(object): @@ -1581,6 +1581,18 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_bytes(snes_to_pc(0x09A045), [0xEA, 0xEA]) # allow super bomb to follow into UW holes rom.write_byte(snes_to_pc(0x09ACDF), 0x6B) # allow kiki/locksmith to follow after screen transition + if world.default_zelda_region[player] == 'Thieves Blind\'s Cell': + write_int16(rom, snes_to_pc(0x02D8D6), 0x45) # change zelda spawn point to maiden cell + rom.write_bytes(snes_to_pc(0x02D8F0), [0x08, 0x08, 0x08, 0x09, 0x0B, 0x0A, 0x0B, 0x0B]) + write_int16(rom, snes_to_pc(0x02D91C), 0x0B00) + write_int16(rom, snes_to_pc(0x02D92A), 0x0800) + write_int16(rom, snes_to_pc(0x02D938), 0x0860) + write_int16(rom, snes_to_pc(0x02D946), 0x0B90) + write_int16(rom, snes_to_pc(0x02D954), 0x0078) + write_int16(rom, snes_to_pc(0x02D962), 0x017F) + rom.write_byte(snes_to_pc(0x02D975), 0x00) + rom.write_byte(snes_to_pc(0x02D98A), 0x02) + if world.enemy_shuffle[player] != 'none': # informs zelda and maiden to draw over gfx slots that are guaranteed unused rom.write_bytes(0x1802C1, world.data_tables[player].room_headers[0x80].free_gfx[0:2]) diff --git a/Rules.py b/Rules.py index b062d917..d87eaa45 100644 --- a/Rules.py +++ b/Rules.py @@ -1669,7 +1669,7 @@ def standard_rules(world, player): def find_rules_for_zelda_delivery(world, player): # path rules for backtracking - start_region = world.get_region('Hyrule Dungeon Cellblock', player) + start_region = world.get_region(world.default_zelda_region[player], player) queue = deque([(start_region, [], [])]) visited = {start_region} blank_state = CollectionState(world) diff --git a/data/base2current.bps b/data/base2current.bps index 3ac6a7ce..d28bca6d 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 29a2c32f..d7ce3c4f 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -333,13 +333,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name): if portal.destination: paths.append(portal.door.entrance.parent_region.name) if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon': - paths.append('Hyrule Dungeon Cellblock') - paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) + paths.append(world.default_zelda_region[player]) + paths.append((world.default_zelda_region[player], 'Hyrule Castle Throne Room')) entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South') # todo: in non-er, we can use the other portals too - paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name)) + paths.append((world.default_zelda_region[player], entrance.door.entrance.parent_region.name)) paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name, - 'Hyrule Dungeon Cellblock'])) + world.default_zelda_region[player]])) if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': paths.append('Thieves Attic Window') elif 'Thieves Attic Window' in all_r_names: diff --git a/source/enemizer/Enemizer.py b/source/enemizer/Enemizer.py index f4e89207..cc32ba8c 100644 --- a/source/enemizer/Enemizer.py +++ b/source/enemizer/Enemizer.py @@ -521,6 +521,21 @@ def randomize_enemies(world, player): green_mail, blue_mail, red_mail = original_table[idx] del original_table[idx] world.data_tables[player].enemy_damage[i] = [green_mail, blue_mail, red_mail] + # determine default zelda follower location + if world.mode[player] == 'standard' and world.doorShuffle[player] == 'crossed' and world.shuffle_followers[player]: + def random_zelda(): + world.default_zelda_region[player] = random.choice(['Hyrule Dungeon Cellblock', 'Thieves Blind\'s Cell']) + if world.customizer: + placements = world.customizer.get_placements() + if placements and player in placements and 'Zelda Herself' in placements[player].values(): + location = [l for (l, item) in placements[player].items() if item == 'Zelda Herself'][0] + if location == 'Suspicious Maiden': + world.default_zelda_region[player] = 'Thieves Blind\'s Cell' + else: + random_zelda() + else: + random_zelda() + def write_enemy_shuffle_settings(world, player, rom): diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index c190c428..777edaec 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -403,6 +403,7 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world): else: region_name = 'West Dark Death Mountain (Top)' om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True, True)) + om_cave_options = [e for e in om_cave_options if e in avail.entrances] if avail.swapped: om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances] assert len(om_cave_options), 'No available entrances left to place Old Man Cave' @@ -643,7 +644,6 @@ def do_dark_sanc(entrances, exits, avail): entrances.remove(choice) exits.remove('Dark Sanctuary Hint') connect_entrance(choice, 'Dark Sanctuary Hint', avail) - ext.connect(avail.world.get_entrance(choice, avail.player).parent_region) if not avail.coupled: avail.decoupled_entrances.remove(choice) if avail.swapped and choice != 'Dark Sanctuary Hint': @@ -651,8 +651,7 @@ def do_dark_sanc(entrances, exits, avail): entrances.remove(swap_ent) exits.remove(swap_ext) elif not ext.connected_region: - # default to output to vanilla area, assume vanilla connection - ext.connect(avail.world.get_region('Dark Chapel Area', avail.player)) + raise Exception('Dark Sanctuary Hint was placed earlier but its exit not properly connected') def do_links_house(entrances, exits, avail, cross_world): @@ -722,8 +721,6 @@ def do_links_house(entrances, exits, avail, cross_world): connect_two_way(links_house, lh_exit, avail) else: connect_entrance(links_house, lh_exit, avail) - ext = avail.world.get_entrance('Big Bomb Shop Exit', avail.player) - ext.connect(avail.world.get_entrance(links_house, avail.player).parent_region) entrances.remove(links_house) exits.remove(lh_exit) if not avail.coupled: @@ -1907,6 +1904,12 @@ def connect_entrance(entrancename, exit_name, avail): avail.entrances.remove(entrancename) if avail.coupled: avail.exits.remove(exit_name) + if exit_name == 'Big Bomb Shop' and avail.world.is_bombshop_start(avail.player): + ext = avail.world.get_entrance('Big Bomb Shop Exit', avail.player) + ext.connect(avail.world.get_entrance(entrancename, avail.player).parent_region) + if exit_name == 'Dark Sanctuary Hint' and avail.world.is_dark_chapel_start(avail.player): + ext = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player) + ext.connect(avail.world.get_entrance(entrancename, avail.player).parent_region) world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) logging.getLogger('').debug(f'Connected (entr) {entrance.name} to {exit.name if exit is not None else region.name}')