diff --git a/BaseClasses.py b/BaseClasses.py index b97f3db0..563c58a6 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -123,6 +123,8 @@ class World(object): set_player_attr('escape_assist', []) set_player_attr('crystals_needed_for_ganon', 7) set_player_attr('crystals_needed_for_gt', 7) + set_player_attr('crystals_ganon_orig', {}) + set_player_attr('crystals_gt_orig', {}) set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) @@ -1654,6 +1656,15 @@ class Location(object): return True return False + def gen_name(self): + name = self.name + world = self.parent_region.world if self.parent_region and self.parent_region.world else None + if self.parent_region.dungeon and world and world.doorShuffle[self.player] == 'crossed': + name += f' @ {self.parent_region.dungeon.name}' + if world and world.players > 1: + name += f' ({world.get_player_names(self.player)})' + return name + def __str__(self): return str(self.__unicode__()) @@ -1833,25 +1844,25 @@ class Spoiler(object): listed_locations = set() lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld] - self.locations['Light World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) + self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations]) listed_locations.update(lw_locations) dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld] - self.locations['Dark World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) + self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations]) listed_locations.update(dw_locations) cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave] - self.locations['Caves'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations]) + self.locations['Caves'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations]) listed_locations.update(cave_locations) for dungeon in self.world.dungeons: dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon] - self.locations[str(dungeon)] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) + self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) listed_locations.update(dungeon_locations) other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations] if other_locations: - self.locations['Other Locations'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in other_locations]) + self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations]) listed_locations.update(other_locations) self.shops = [] @@ -1970,8 +1981,10 @@ class Spoiler(object): outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) outfile.write('Intensity: %s\n' % self.metadata['intensity'][player]) - outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player]) - outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player]) + addition = ' (Random)' if self.world.crystals_gt_orig[player] == 'random' else '' + outfile.write('Crystals required for GT: %s\n' % (str(self.metadata['gt_crystals'][player]) + addition)) + addition = ' (Random)' if self.world.crystals_ganon_orig[player] == 'random' else '' + outfile.write('Crystals required for Ganon: %s\n' % (str(self.metadata['ganon_crystals'][player]) + addition)) outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No')) outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player]) outfile.write('Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No')) @@ -2019,7 +2032,7 @@ class Spoiler(object): # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # items: Item names outfile.write('\n\nLocations:\n\n') - outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for grouping in self.locations.values() for (location, item) in grouping.items()])) + outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item)) for grouping in self.locations.values() for (location, item) in grouping.items()])) # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # items: Item names diff --git a/DoorShuffle.py b/DoorShuffle.py index 7d72a1b2..2c6ef833 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1063,7 +1063,7 @@ def check_entrance_fixes(world, player): dungeon = entrance.connected_region.dungeon if dungeon: layout = world.dungeon_layouts[player][dungeon.name] - if 'Sanctuary' in layout.master_sector.region_set(): + if 'Sanctuary' in layout.master_sector.region_set() or dungeon.name in ['Hyrule Castle', 'Desert Palace', 'Skull Woods', 'Turtle Rock']: portal = None for portal_name in dungeon_portals[dungeon.name]: test_portal = world.get_portal(portal_name, player) @@ -1071,8 +1071,6 @@ def check_entrance_fixes(world, player): portal = test_portal break world.force_fix[player][key] = portal - elif dungeon.name in ['Hyrule Castle', 'Desert Palace', 'Skull Woods', 'Turtle Rock']: - world.force_fix[player][key] = portal def palette_assignment(world, player): diff --git a/Main.py b/Main.py index c102c509..c0e52af4 100644 --- a/Main.py +++ b/Main.py @@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items from Utils import output_path, parse_player_names -__version__ = '0.2.0.14u' +__version__ = '0.2.0.15u' class EnemizerError(RuntimeError): pass @@ -56,6 +56,8 @@ def main(args, seed=None, fish=None): world.bigkeyshuffle = args.bigkeyshuffle.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} + world.crystals_ganon_orig = args.crystals_ganon.copy() + world.crystals_gt_orig = args.crystals_gt.copy() world.open_pyramid = args.openpyramid.copy() world.boss_shuffle = args.shufflebosses.copy() world.enemy_shuffle = args.shuffleenemies.copy() @@ -374,6 +376,8 @@ def copy_world(world): ret.bigkeyshuffle = world.bigkeyshuffle.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() + ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() + ret.crystals_gt_orig = world.crystals_gt_orig.copy() ret.open_pyramid = world.open_pyramid.copy() ret.boss_shuffle = world.boss_shuffle.copy() ret.enemy_shuffle = world.enemy_shuffle.copy() @@ -419,6 +423,10 @@ def copy_world(world): copied_region = ret.get_region(region.name, region.player) copied_region.is_light_world = region.is_light_world copied_region.is_dark_world = region.is_dark_world + copied_region.dungeon = region.dungeon + copied_region.locations = [copy.copy(location) for location in region.locations] + for location in copied_region.locations: + location.parent_region = copied_region for entrance in region.entrances: ret.get_entrance(entrance.name, entrance.player).connect(copied_region) @@ -598,7 +606,7 @@ def create_playthrough(world): old_world.spoiler.paths = dict() for player in range(1, world.players + 1): - old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) + old_world.spoiler.paths.update({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) for _, path in dict(old_world.spoiler.paths).items(): if any(exit == 'Pyramid Fairy' for (_, exit) in path): if world.mode[player] != 'inverted': @@ -609,4 +617,4 @@ def create_playthrough(world): # we can finally output our playthrough old_world.spoiler.playthrough = OrderedDict([("0", [str(item) for item in world.precollected_items if item.advancement])]) for i, sphere in enumerate(collection_spheres): - old_world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sphere} + old_world.spoiler.playthrough[str(i + 1)] = {location.gen_name(): str(location.item) for location in sphere} diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 79193063..e4aca12d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -68,6 +68,18 @@ Redesign of Keysanity Menu complete for crossed dungeon and moved out of experim * 1st Column: indicate if you have foudn the map of not for that dungeon * 2nd and 3rd Column: You must have the compass to see these columns. A two-digit display that show you how many chests are left in the dungeon. If -keydropshuffle is off, this does not count key drop. If on, it does. + +## Potshuffle by compiling + +Same flag as before but uses python logic written by compiling instead of the enemizer logic-less version. Needs some +testing to verify logic is all good. + +## Other features + +### Spoiler log improvements + +* In crossed mode, the new dungeon is listed along with the location designated by a '@' sign +* Random gt crystals and ganon crystal are noted in the settings for better reproduction of seeds ### Experimental features @@ -80,6 +92,9 @@ Redesign of Keysanity Menu complete for crossed dungeon and moved out of experim # Bug Fixes +* 2.0.15u + * Allow Aga Tower lobby door as a a paired keydoor (typo) + * Fix portal check for multi-entrance dungeons * 2.0.14u * Removal of key doors no longer messes up certain lobbies * Fixed ER entrances when Desert Back is a connector diff --git a/Rom.py b/Rom.py index 2737c7c4..59a82fef 100644 --- a/Rom.py +++ b/Rom.py @@ -713,8 +713,6 @@ def patch_rom(world, rom, player, team, enemized): if portal.boss_exit_idx > -1: rom.write_byte(0x7939 + portal.boss_exit_idx, portal.current_room()) - world.force_fix[player]['sw'] |= world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla' - # fix exits, if not fixed during exit patching if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla': write_int16(rom, 0x15DB5 + 2 * exit_ids['Skull Woods Final Section Exit'][1], 0x00F8) diff --git a/Tables.py b/Tables.py index 58d4d120..5365e69c 100644 --- a/Tables.py +++ b/Tables.py @@ -62,7 +62,7 @@ door_pair_offset_table = { 0xb8: 0x01a4, 0xb9: 0x01a6, 0xba: 0x01aa, 0xbb: 0x01ad, 0xbc: 0x01b3, 0xbe: 0x01bb, 0xbf: 0x01be, 0xc0: 0x01bf, 0xc1: 0x01c2, 0xc2: 0x01ca, 0xc3: 0x01d2, 0xc4: 0x01d9, 0xc5: 0x01da, 0xc6: 0x01dd, 0xc7: 0x01e3, 0xc8: 0x01e6, 0xc9: 0x01e7, 0xcb: 0x01ec, 0xcc: 0x01ed, 0xce: 0x01f0, 0xd0: 0x01f1, 0xd1: 0x01f3, 0xd2: 0x01f7, 0xd5: 0x01f8, - 0xd6: 0x01fa, 0xd8: 0x01fd, 0xd9: 0x0200, 0xda: 0x0203, 0xdb: 0x0204, 0xdc: 0x0206, 0xe0: 0x020 + 0xd6: 0x01fa, 0xd8: 0x01fd, 0xd9: 0x0200, 0xda: 0x0203, 0xdb: 0x0204, 0xdc: 0x0206, 0xe0: 0x0207 } # Note: 0-7 correspond to 1,2,3,4,5,6,a,14 respectively, see doortables.asm : MultDivInfo