diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 56502f50..beb2010b 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -195,11 +195,11 @@ class PokemonRedBlueWorld(World): normals -= subtract_amounts[2] while super_effectives + not_very_effectives + normals > 225 - immunities: r = self.multiworld.random.randint(0, 2) - if r == 0: + if r == 0 and super_effectives: super_effectives -= 1 - elif r == 1: + elif r == 1 and not_very_effectives: not_very_effectives -= 1 - else: + elif normals: normals -= 1 chart = [] for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives], @@ -249,14 +249,18 @@ class PokemonRedBlueWorld(World): itempool = progitempool + usefulitempool + filleritempool multiworld.random.shuffle(itempool) unplaced_items = [] - for item in itempool: + for i, item in enumerate(itempool): if item.player == loc.player and loc.can_fill(multiworld.state, item, False): - if item in progitempool: - progitempool.remove(item) - elif item in usefulitempool: - usefulitempool.remove(item) - elif item in filleritempool: - filleritempool.remove(item) + if item.advancement: + pool = progitempool + elif item.useful: + pool = usefulitempool + else: + pool = filleritempool + for i, check_item in enumerate(pool): + if item is check_item: + pool.pop(i) + break if item.advancement: state = sweep_from_pool(multiworld.state, progitempool + unplaced_items) if (not item.advancement) or state.can_reach(loc, "Location", loc.player): @@ -416,16 +420,16 @@ class PokemonRedBlueWorld(World): self.multiworld.victory_road_condition[self.player]) > 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")))): intervene_move = "Cut" - elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) and self.multiworld.dark_rock_tunnel_logic[self.player] - and (((self.multiworld.accessibility[self.player] != "minimal" or - (self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player])) or - self.multiworld.door_shuffle[self.player]))): + elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) + and self.multiworld.dark_rock_tunnel_logic[self.player] + and (self.multiworld.accessibility[self.player] != "minimal" + or self.multiworld.door_shuffle[self.player])): intervene_move = "Flash" # If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps # as reachable, and if on no door shuffle or simple, fly is simply never necessary. # We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been # considered in door shuffle. - elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and logic.can_learn_hm(test_state, "Fly", self.player) + elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and self.multiworld.door_shuffle[self.player] not in ("off", "simple") and [self.fly_map, self.town_map_fly_map] != ["Pallet Town", "Pallet Town"]): intervene_move = "Fly" @@ -554,23 +558,21 @@ class PokemonRedBlueWorld(World): else: raise Exception("Failed to remove corresponding item while deleting unreachable Dexsanity location") - - if self.multiworld.door_shuffle[self.player] == "decoupled": - swept_state = self.multiworld.state.copy() - swept_state.sweep_for_events(player=self.player) - locations = [location for location in - self.multiworld.get_reachable_locations(swept_state, self.player) if location.item is - None] - self.multiworld.random.shuffle(locations) - while len(locations) > 10: - location = locations.pop() - location.progress_type = LocationProgressType.EXCLUDED - - if self.multiworld.key_items_only[self.player]: - locations = [location for location in self.multiworld.get_unfilled_locations(self.player) if - location.progress_type == LocationProgressType.DEFAULT] - for location in locations: - location.progress_type = LocationProgressType.PRIORITY + @classmethod + def stage_post_fill(cls, multiworld): + # Convert all but one of each instance of a wild Pokemon to useful classification. + # This cuts down on time spent calculating the spoiler playthrough. + found_mons = set() + for sphere in multiworld.get_spheres(): + for location in sphere: + if (location.game == "Pokemon Red and Blue" and (location.item.name in poke_data.pokemon_data.keys() + or "Static " in location.item.name) + and location.item.advancement): + key = (location.player, location.item.name) + if key in found_mons: + location.item.classification = ItemClassification.useful + else: + found_mons.add(key) def create_regions(self): if (self.multiworld.old_man[self.player] == "vanilla" or diff --git a/worlds/pokemon_rb/basepatch_blue.bsdiff4 b/worlds/pokemon_rb/basepatch_blue.bsdiff4 index 5ccf4e9b..0f65564a 100644 Binary files a/worlds/pokemon_rb/basepatch_blue.bsdiff4 and b/worlds/pokemon_rb/basepatch_blue.bsdiff4 differ diff --git a/worlds/pokemon_rb/basepatch_red.bsdiff4 b/worlds/pokemon_rb/basepatch_red.bsdiff4 index 26d2eb0c..826b7bf8 100644 Binary files a/worlds/pokemon_rb/basepatch_red.bsdiff4 and b/worlds/pokemon_rb/basepatch_red.bsdiff4 differ diff --git a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md index b164d4b0..dc55aca0 100644 --- a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md +++ b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md @@ -41,6 +41,8 @@ and repeatable source of money. * You can disable and re-enable experience gains by talking to an aide in Oak's Lab. * You can reset static encounters (Poké Flute encounter, legendaries, and the trap Poké Ball battles in Power Plant) for any Pokémon you have defeated but not caught, by talking to an aide in Oak's Lab. +* Dungeons normally hidden on the Town Map are now present, and the "Sea Cottage" has been removed. This is to allow +Simple Door Shuffle to update the locations of all of the dungeons on the Town Map. ## What items and locations get shuffled? diff --git a/worlds/pokemon_rb/level_scaling.py b/worlds/pokemon_rb/level_scaling.py index 5f3dfc1a..79cda394 100644 --- a/worlds/pokemon_rb/level_scaling.py +++ b/worlds/pokemon_rb/level_scaling.py @@ -10,7 +10,9 @@ def level_scaling(multiworld): while locations: sphere = set() for world in multiworld.get_game_worlds("Pokemon Red and Blue"): - if multiworld.level_scaling[world.player] != "by_spheres_and_distance": + if (multiworld.level_scaling[world.player] != "by_spheres_and_distance" + and (multiworld.level_scaling[world.player] != "auto" or multiworld.door_shuffle[world.player] + in ("off", "simple"))): continue regions = {multiworld.get_region("Menu", world.player)} checked_regions = set() @@ -45,18 +47,18 @@ def level_scaling(multiworld): return True if (("Rock Tunnel 1F - Wild Pokemon" in location.name and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state) - for e in ['Rock Tunnel 1F-NE to Route 10-N', - 'Rock Tunnel 1F-NE to Rock Tunnel B1F-E', - 'Rock Tunnel 1F-NW to Rock Tunnel B1F-E', - 'Rock Tunnel 1F-NW to Rock Tunnel B1F-W', - 'Rock Tunnel 1F-S to Route 10-S', - 'Rock Tunnel 1F-S to Rock Tunnel B1F-W']])) or + for e in ['Rock Tunnel 1F-NE 1 to Route 10-N', + 'Rock Tunnel 1F-NE 2 to Rock Tunnel B1F-E 1', + 'Rock Tunnel 1F-NW 1 to Rock Tunnel B1F-E 2', + 'Rock Tunnel 1F-NW 2 to Rock Tunnel B1F-W 1', + 'Rock Tunnel 1F-S 1 to Route 10-S', + 'Rock Tunnel 1F-S 2 to Rock Tunnel B1F-W 2']])) or ("Rock Tunnel B1F - Wild Pokemon" in location.name and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state) - for e in ['Rock Tunnel B1F-E to Rock Tunnel 1F-NE', - 'Rock Tunnel B1F-E to Rock Tunnel 1F-NW', - 'Rock Tunnel B1F-W to Rock Tunnel 1F-NW', - 'Rock Tunnel B1F-W to Rock Tunnel 1F-S']]))): + for e in ['Rock Tunnel B1F-E 1 to Rock Tunnel 1F-NE 2', + 'Rock Tunnel B1F-E 2 to Rock Tunnel 1F-NW 1', + 'Rock Tunnel B1F-W 1 to Rock Tunnel 1F-NW 2', + 'Rock Tunnel B1F-W 2 to Rock Tunnel 1F-S 2']]))): # Even if checks in Rock Tunnel are out of logic due to lack of Flash, it is very easy to # wander in the dark and encounter wild Pokémon, even unintentionally while attempting to # leave the way you entered. We'll count the wild Pokémon as reachable as soon as the Rock @@ -135,4 +137,3 @@ def level_scaling(multiworld): sphere_objects[object].level = level_list_copy.pop(0) for world in multiworld.get_game_worlds("Pokemon Red and Blue"): world.finished_level_scaling.set() - diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index 3fff3b88..abaa58fc 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -1036,25 +1036,25 @@ location_data = [ type="Wild Encounter", level=12), LocationData("Mt Moon B2F-Wild", "Wild Pokemon - 10", "Clefairy", rom_addresses["Wild_MtMoonB2F"] + 19, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None, + LocationData("Route 4-E", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None, event=True, type="Wild Encounter", level=6), - LocationData("Route 4-Grass", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None, + LocationData("Route 4-E", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None, + LocationData("Route 4-E", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None, + LocationData("Route 4-E", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None, event=True, type="Wild Encounter", level=12), LocationData("Route 24", "Wild Pokemon - 1", ["Weedle", "Caterpie"], rom_addresses["Wild_Route24"] + 1, None, event=True, type="Wild Encounter", level=7), diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index 8afe91b8..bd651591 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -228,7 +228,7 @@ class SplitCardKey(Choice): class AllElevatorsLocked(Toggle): """Adds requirements to the Celadon Department Store elevator and Silph Co elevators to have the Lift Key. - No logical implications normally, but may have a significant impact on Insanity Door Shuffle.""" + No logical implications normally, but may have a significant impact on some Door Shuffle options.""" display_name = "All Elevators Locked" default = 1 @@ -317,42 +317,42 @@ class TownMapFlyLocation(Toggle): class DoorShuffle(Choice): """Simple: entrances are randomized together in groups: Pokemarts, Gyms, single exit dungeons, dual exit dungeons, single exit misc interiors, dual exit misc interiors are all shuffled separately. Safari Zone is not shuffled. - Full: Any outdoor entrance may lead to any interior. - Insanity: All rooms in the game are shuffled.""" + On Simple only, the Town Map will be updated to show the new locations for each dungeon. + Interiors: Any outdoor entrance may lead to any interior, but intra-interior doors are not shuffled. Previously + named Full. + Full: Exterior to interior entrances are shuffled, and interior to interior doors are shuffled, separately. + Insanity: All doors in the game are shuffled. + Decoupled: Doors may be decoupled from each other, so that leaving through an exit may not return you to the + door you entered from.""" display_name = "Door Shuffle" option_off = 0 option_simple = 1 - option_full = 2 - option_insanity = 3 - # Disabled for now, has issues with elevators that need to be resolved - # option_decoupled = 4 + option_interiors = 2 + option_full = 3 + option_insanity = 4 + option_decoupled = 5 default = 0 - # remove assertions that blow up checks for decoupled - def __eq__(self, other): - if isinstance(other, self.__class__): - return other.value == self.value - elif isinstance(other, str): - return other == self.current_key - elif isinstance(other, int): - return other == self.value - elif isinstance(other, bool): - return other == bool(self.value) - else: - raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") - -class WarpTileShuffle(Toggle): - """Shuffle the warp tiles in Silph Co and Sabrina's Gym among themselves, separately. - On Insanity, turning this off means they are mixed into the general door shuffle instead of only being shuffled - among themselves.""" +class WarpTileShuffle(Choice): + """Vanilla: The warp tiles in Silph Co and Sabrina's Gym are not changed. + Shuffle: The warp tile destinations are shuffled among themselves. + Mixed: The warp tiles are mixed into the pool of available doors for Full, Insanity, and Decoupled. Same as Shuffle + for any other door shuffle option.""" display_name = "Warp Tile Shuffle" default = 0 + option_vanilla = 0 + option_shuffle = 1 + option_mixed = 2 + alias_true = 1 + alias_on = 1 + alias_off = 0 + alias_false = 0 class RandomizeRockTunnel(Toggle): - """Randomize the layout of Rock Tunnel. - If Insanity Door Shuffle is on, this will cause only the main entrances to Rock Tunnel to be shuffled.""" + """Randomize the layout of Rock Tunnel. If Full, Insanity, or Decoupled Door Shuffle is on, this will cause only the + main entrances to Rock Tunnel to be shuffled.""" display_name = "Randomize Rock Tunnel" default = 0 @@ -401,15 +401,17 @@ class Stonesanity(Toggle): class LevelScaling(Choice): """Off: Encounters use vanilla game levels. By Spheres: Levels are scaled by access sphere. Areas reachable in later spheres will have higher levels. - Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by number - of internal region connections. This is a much more severe curving of levels and may lead to much less variation in - levels found in a particular map. However, it may make the higher door shuffle settings significantly more bearable, - as these options more often result in a smaller number of larger access spheres.""" + By Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by + number of internal region connections. This is a much more severe curving of levels and may lead to much less + variation in levels found in a particular map. However, it may make the higher door shuffle settings significantly + more bearable, as these options more often result in a smaller number of larger access spheres. + Auto: Scales by Spheres if Door Shuffle is off or on Simple, otherwise scales by Spheres and Distance""" display_name = "Level Scaling" option_off = 0 option_by_spheres = 1 option_by_spheres_and_distance = 2 - default = 1 + option_auto = 3 + default = 3 class ExpModifier(NamedRange): diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index 97e63c05..afeb301c 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -256,6 +256,22 @@ map_ids = { "Indigo Plateau Agatha's Room": 0xF7, } +town_map_coords = { + "Route 2-SW": ("Viridian Forest South Gate to Route 2-SW", 2, 4, (3,), "Viridian Forest", 4), #ViridianForestName + "Route 2-NE": ("Diglett's Cave Route 2 to Route 2-NE", 3, 4, (48,), "Diglett's Cave", 5), #DiglettsCaveName + "Route 4-W": ("Mt Moon 1F to Route 4-W", 6, 2, (5,), "Mt Moon 1F", 8), #MountMoonName + "Cerulean City-Cave": ("Cerulean Cave 1F-SE to Cerulean City-Cave", 9, 1, (54,), "Cerulean Cave 1F", 11), #CeruleanCaveName + "Vermilion City-Dock": ("Vermilion Dock to Vermilion City-Dock", 9, 10, (19,), "S.S. Anne 1F", 17), #SSAnneName + "Route 10-N": ("Rock Tunnel 1F-NE 1 to Route 10-N", 14, 3, (13, 57), "Rock Tunnel Pokemon Center", 19), #RockTunnelName + "Lavender Town": ("Pokemon Tower 1F to Lavender Town", 15, 5, (27,), "Pokemon Tower 2F", 22), #PokemonTowerName + "Celadon Game Corner-Hidden Stairs": ("Rocket Hideout B1F to Celadon Game Corner-Hidden Stairs", 7, 5, (50,), "Rocket Hideout B1F", 26), #RocketHQName + "Saffron City-Silph": ("Silph Co 1F to Saffron City-Silph", 10, 5, (51, 58), "Silph Co 2F", 28), #SilphCoName + "Route 20-IE": ("Seafoam Islands 1F to Route 20-IE", 5, 15, (32,), "Seafoam Islands B1F", 40), #SeafoamIslandsName + "Cinnabar Island-M": ("Pokemon Mansion 1F to Cinnabar Island-M", 2, 15, (35, 52), "Pokemon Mansion 1F", 43), #PokemonMansionName + "Route 23-C": ("Victory Road 1F-S to Route 23-C", 0, 4, (20, 45, 49), "Victory Road 1F", 47), #VictoryRoadName + "Route 10-P": ("Power Plant to Route 10-P", 15, 4, (14,), "Power Plant", 49), #PowerPlantName +} + warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishing': [], 'Fossil Level': [], 'Pokedex': [], 'Fossil': [], 'Celadon City': [ {'name': 'Celadon City to Celadon Department Store 1F W', 'address': 'Warps_CeladonCity', 'id': 0, @@ -461,15 +477,21 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi {'address': 'Warps_PokemonMansion3F', 'id': 0, 'to': {'map': 'Pokemon Mansion 2F', 'id': 1}}], 'Pokemon Mansion B1F': [ {'address': 'Warps_PokemonMansionB1F', 'id': 0, 'to': {'map': 'Pokemon Mansion 1F-SE', 'id': 5}}], - 'Rock Tunnel 1F-NE': [{'address': 'Warps_RockTunnel1F', 'id': 0, 'to': {'map': 'Route 10-N', 'id': 1}}, - {'address': 'Warps_RockTunnel1F', 'id': 4, - 'to': {'map': 'Rock Tunnel B1F-E', 'id': 0}}], 'Rock Tunnel 1F-NW': [ - {'address': 'Warps_RockTunnel1F', 'id': 5, 'to': {'map': 'Rock Tunnel B1F-E', 'id': 1}}, - {'address': 'Warps_RockTunnel1F', 'id': 6, 'to': {'map': 'Rock Tunnel B1F-W', 'id': 2}}], - 'Rock Tunnel 1F-S': [{'address': 'Warps_RockTunnel1F', 'id': 2, 'to': {'map': 'Route 10-S', 'id': 2}}, + 'Rock Tunnel 1F-NE 1': [{'address': 'Warps_RockTunnel1F', 'id': 0, 'to': {'map': 'Route 10-N', 'id': 1}}], + 'Rock Tunnel 1F-NE 2': + [{'address': 'Warps_RockTunnel1F', 'id': 4, + 'to': {'map': 'Rock Tunnel B1F-E 1', 'id': 0}}], 'Rock Tunnel 1F-NW 1': [ + {'address': 'Warps_RockTunnel1F', 'id': 5, 'to': {'map': 'Rock Tunnel B1F-E 2', 'id': 1}}], + 'Rock Tunnel 1F-NW 2': [ + {'address': 'Warps_RockTunnel1F', 'id': 6, 'to': {'map': 'Rock Tunnel B1F-W 1', 'id': 2}}], + 'Rock Tunnel 1F-S 1': [{'address': 'Warps_RockTunnel1F', 'id': 2, 'to': {'map': 'Route 10-S', 'id': 2}}], + 'Rock Tunnel 1F-S 2': [ {'address': 'Warps_RockTunnel1F', 'id': 7, - 'to': {'map': 'Rock Tunnel B1F-W', 'id': 3}}], 'Rock Tunnel 1F-Wild': [], - 'Rock Tunnel B1F-Wild': [], 'Seafoam Islands 1F': [ + 'to': {'map': 'Rock Tunnel B1F-W 2', 'id': 3}}], 'Rock Tunnel 1F-Wild': [], + 'Rock Tunnel B1F-Wild': [], + 'Rock Tunnel 1F-NE': [], 'Rock Tunnel 1F-NW': [], 'Rock Tunnel 1F-S': [], 'Rock Tunnel B1F-E': [], + 'Rock Tunnel B1F-W': [], + 'Seafoam Islands 1F': [ {'address': 'Warps_SeafoamIslands1F', 'id': (2, 3), 'to': {'map': 'Route 20-IE', 'id': 1}}, {'address': 'Warps_SeafoamIslands1F', 'id': 4, 'to': {'map': 'Seafoam Islands B1F', 'id': 1}}, {'address': 'Warps_SeafoamIslands1F', 'id': 5, 'to': {'map': 'Seafoam Islands B1F-NE', 'id': 6}}], @@ -569,12 +591,14 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi {'address': 'Warps_CeruleanCave2F', 'id': 3, 'to': {'map': 'Cerulean Cave 1F-N', 'id': 5}}], 'Cerulean Cave B1F': [ {'address': 'Warps_CeruleanCaveB1F', 'id': 0, 'to': {'map': 'Cerulean Cave 1F-NW', 'id': 8}}], - 'Cerulean Cave B1F-E': [], 'Rock Tunnel B1F-E': [ - {'address': 'Warps_RockTunnelB1F', 'id': 0, 'to': {'map': 'Rock Tunnel 1F-NE', 'id': 4}}, - {'address': 'Warps_RockTunnelB1F', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NW', 'id': 5}}], - 'Rock Tunnel B1F-W': [ - {'address': 'Warps_RockTunnelB1F', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-NW', 'id': 6}}, - {'address': 'Warps_RockTunnelB1F', 'id': 3, 'to': {'map': 'Rock Tunnel 1F-S', 'id': 7}}], + 'Cerulean Cave B1F-E': [], 'Rock Tunnel B1F-E 1': [ + {'address': 'Warps_RockTunnelB1F', 'id': 0, 'to': {'map': 'Rock Tunnel 1F-NE 2', 'id': 4}}], + 'Rock Tunnel B1F-E 2': [ + {'address': 'Warps_RockTunnelB1F', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NW 1', 'id': 5}}], + 'Rock Tunnel B1F-W 1': [ + {'address': 'Warps_RockTunnelB1F', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-NW 2', 'id': 6}}], + 'Rock Tunnel B1F-W 2': [ + {'address': 'Warps_RockTunnelB1F', 'id': 3, 'to': {'map': 'Rock Tunnel 1F-S 2', 'id': 7}}], 'Seafoam Islands B1F': [ {'address': 'Warps_SeafoamIslandsB1F', 'id': 0, 'to': {'map': 'Seafoam Islands B2F-NW', 'id': 0}}, {'address': 'Warps_SeafoamIslandsB1F', 'id': 1, 'to': {'map': 'Seafoam Islands 1F', 'id': 4}}, @@ -802,7 +826,7 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi 'Route 4-W': [{'address': 'Warps_Route4', 'id': 0, 'to': {'map': 'Route 4 Pokemon Center', 'id': 0}}, {'address': 'Warps_Route4', 'id': 1, 'to': {'map': 'Mt Moon 1F', 'id': 0}}], 'Route 4-C': [{'address': 'Warps_Route4', 'id': 2, 'to': {'map': 'Mt Moon B1F-NE', 'id': 7}}], - 'Route 4-E': [], 'Route 4-Lass': [], 'Route 4-Grass': [], + 'Route 4-Lass': [], 'Route 4-E': [], 'Route 5': [{'address': 'Warps_Route5', 'id': (1, 0), 'to': {'map': 'Route 5 Gate-N', 'id': (3, 2)}}, {'address': 'Warps_Route5', 'id': 3, 'to': {'map': 'Underground Path Route 5', 'id': 0}}, {'address': 'Warps_Route5', 'id': 4, 'to': {'map': 'Daycare', 'id': 0}}], 'Route 9': [], @@ -838,8 +862,8 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi {'address': 'Warps_Route8', 'id': 4, 'to': {'map': 'Underground Path Route 8', 'id': 0}}], 'Route 8-Grass': [], 'Route 10-N': [{'address': 'Warps_Route10', 'id': 0, 'to': {'map': 'Rock Tunnel Pokemon Center', 'id': 0}}, - {'address': 'Warps_Route10', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NE', 'id': 0}}], - 'Route 10-S': [{'address': 'Warps_Route10', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-S', 'id': 2}}], + {'address': 'Warps_Route10', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NE 1', 'id': 0}}], + 'Route 10-S': [{'address': 'Warps_Route10', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-S 1', 'id': 2}}], 'Route 10-P': [{'address': 'Warps_Route10', 'id': 3, 'to': {'map': 'Power Plant', 'id': 0}}], 'Route 10-C': [], 'Route 11': [{'address': 'Warps_Route11', 'id': 4, 'to': {'map': "Diglett's Cave Route 11", 'id': 0}}], @@ -1293,7 +1317,7 @@ def pair(a, b): return (f"{a} to {b}", f"{b} to {a}") -mandatory_connections = { +safari_zone_connections = { pair("Safari Zone Center-S", "Safari Zone Gate-N"), pair("Safari Zone East", "Safari Zone North"), pair("Safari Zone East", "Safari Zone Center-S"), @@ -1302,14 +1326,8 @@ mandatory_connections = { pair("Safari Zone North", "Safari Zone West-NW"), pair("Safari Zone West", "Safari Zone Center-NW"), } -insanity_mandatory_connections = { - # pair("Seafoam Islands B1F-NE", "Seafoam Islands 1F"), - # pair("Seafoam Islands 1F", "Seafoam Islands B1F"), - # pair("Seafoam Islands B2F-NW", "Seafoam Islands B1F"), - # pair("Seafoam Islands B3F-SE", "Seafoam Islands B2F-SE"), - # pair("Seafoam Islands B3F-NE", "Seafoam Islands B2F-NE"), - # pair("Seafoam Islands B4F", "Seafoam Islands B3F-NE"), - # pair("Seafoam Islands B4F", "Seafoam Islands B3F"), + +full_mandatory_connections = { pair("Player's House 1F", "Player's House 2F"), pair("Indigo Plateau Lorelei's Room", "Indigo Plateau Lobby-N"), pair("Indigo Plateau Bruno's Room", "Indigo Plateau Lorelei's Room"), @@ -1338,7 +1356,7 @@ safe_connecting_interior_dungeons = [ unsafe_connecting_interior_dungeons = [ ["Seafoam Islands 1F to Route 20-IE", "Seafoam Islands 1F-SE to Route 20-IW"], - ["Rock Tunnel 1F-NE to Route 10-N", "Rock Tunnel 1F-S to Route 10-S"], + ["Rock Tunnel 1F-NE 1 to Route 10-N", "Rock Tunnel 1F-S 1 to Route 10-S"], ["Victory Road 1F-S to Route 23-C", "Victory Road 2F-E to Route 23-N"], ] @@ -1357,7 +1375,7 @@ connecting_interior_dungeon_entrances = [ ["Route 2-NE to Diglett's Cave Route 2", "Route 11 to Diglett's Cave Route 11"], ['Route 20-IE to Seafoam Islands 1F', 'Route 20-IW to Seafoam Islands 1F-SE'], ['Route 4-W to Mt Moon 1F', 'Route 4-C to Mt Moon B1F-NE'], - ['Route 10-N to Rock Tunnel 1F-NE', 'Route 10-S to Rock Tunnel 1F-S'], + ['Route 10-N to Rock Tunnel 1F-NE 1', 'Route 10-S to Rock Tunnel 1F-S 1'], ['Route 23-C to Victory Road 1F-S', 'Route 23-N to Victory Road 2F-E'], ] @@ -1454,7 +1472,6 @@ mansion_stair_destinations = [ ] unreachable_outdoor_entrances = [ - "Route 4-C to Mt Moon B1F-NE", "Fuchsia City-Good Rod House Backyard to Fuchsia Good Rod House", "Cerulean City-Badge House Backyard to Cerulean Badge House", # TODO: This doesn't need to be forced if fly location is Pokemon League? @@ -1496,7 +1513,6 @@ def create_regions(self): start_inventory["Exp. All"] = 1 self.multiworld.push_precollected(self.create_item("Exp. All")) - # locations = [location for location in location_data if location.type in ("Item", "Trainer Parties")] self.item_pool = [] combined_traps = (self.multiworld.poison_trap_weight[self.player].value + self.multiworld.fire_trap_weight[self.player].value @@ -1556,7 +1572,6 @@ def create_regions(self): if event: location_object.place_locked_item(item) if location.type == "Trainer Parties": - # loc.item.classification = ItemClassification.filler location_object.party_data = deepcopy(location.party_data) else: self.item_pool.append(item) @@ -1566,7 +1581,7 @@ def create_regions(self): + [item.name for item in self.multiworld.precollected_items[self.player] if item.advancement] self.total_key_items = len( - # The stonesanity items are not checekd for here and instead just always added as the `+ 4` + # The stonesanity items are not checked for here and instead just always added as the `+ 4` # They will always exist, but if stonesanity is off, then only as events. # We don't want to just add 4 if stonesanity is off while still putting them in this list in case # the player puts stones in their start inventory, in which case they would be double-counted here. @@ -1619,16 +1634,15 @@ def create_regions(self): connect(multiworld, player, "Pewter City-E", "Route 3", lambda state: logic.route_3(state, player), one_way=True) connect(multiworld, player, "Route 3", "Pewter City-E", one_way=True) connect(multiworld, player, "Route 4-W", "Route 3") - connect(multiworld, player, "Route 24", "Cerulean City-Water", one_way=True) + connect(multiworld, player, "Route 24", "Cerulean City-Water", lambda state: logic.can_surf(state, player)) connect(multiworld, player, "Cerulean City-Water", "Route 4-Lass", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Mt Moon B2F", "Mt Moon B2F-Wild", one_way=True) connect(multiworld, player, "Mt Moon B2F-NE", "Mt Moon B2F-Wild", one_way=True) connect(multiworld, player, "Mt Moon B2F-C", "Mt Moon B2F-Wild", one_way=True) - connect(multiworld, player, "Route 4-Lass", "Route 4-E", one_way=True) + connect(multiworld, player, "Route 4-Lass", "Route 4-C", one_way=True) connect(multiworld, player, "Route 4-C", "Route 4-E", one_way=True) - connect(multiworld, player, "Route 4-E", "Route 4-Grass", one_way=True) - connect(multiworld, player, "Route 4-Grass", "Cerulean City", one_way=True) - connect(multiworld, player, "Cerulean City", "Route 24", one_way=True) + connect(multiworld, player, "Route 4-E", "Cerulean City") + connect(multiworld, player, "Cerulean City", "Route 24") connect(multiworld, player, "Cerulean City", "Cerulean City-T", lambda state: state.has("Help Bill", player)) connect(multiworld, player, "Cerulean City-Outskirts", "Cerulean City", one_way=True) connect(multiworld, player, "Cerulean City", "Cerulean City-Outskirts", lambda state: logic.can_cut(state, player), one_way=True) @@ -1785,7 +1799,6 @@ def create_regions(self): connect(multiworld, player, "Seafoam Islands B3F-SE", "Seafoam Islands B3F-Wild", one_way=True) connect(multiworld, player, "Seafoam Islands B4F", "Seafoam Islands B4F-W", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Seafoam Islands B4F-W", "Seafoam Islands B4F", one_way=True) - # This really shouldn't be necessary since if the boulders are reachable you can drop, but might as well be thorough connect(multiworld, player, "Seafoam Islands B3F", "Seafoam Islands B3F-SE", lambda state: logic.can_surf(state, player) and logic.can_strength(state, player) and state.has("Seafoam Exit Boulder", player, 6)) connect(multiworld, player, "Viridian City", "Viridian City-N", lambda state: state.has("Oak's Parcel", player) or state.multiworld.old_man[player].value == 2 or logic.can_cut(state, player)) connect(multiworld, player, "Route 11", "Route 11-C", lambda state: logic.can_strength(state, player) or not state.multiworld.extra_strength_boulders[player]) @@ -1804,6 +1817,16 @@ def create_regions(self): connect(multiworld, player, "Pokemon Mansion 2F-E", "Pokemon Mansion 2F-Wild", one_way=True) connect(multiworld, player, "Pokemon Mansion 1F-SE", "Pokemon Mansion 1F-Wild", one_way=True) connect(multiworld, player, "Pokemon Mansion 1F", "Pokemon Mansion 1F-Wild", one_way=True) + connect(multiworld, player, "Rock Tunnel 1F-S 1", "Rock Tunnel 1F-S", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-S 2", "Rock Tunnel 1F-S", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NW 1", "Rock Tunnel 1F-NW", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NW 2", "Rock Tunnel 1F-NW", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NE 1", "Rock Tunnel 1F-NE", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NE 2", "Rock Tunnel 1F-NE", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-W 1", "Rock Tunnel B1F-W", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-W 2", "Rock Tunnel B1F-W", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-E 1", "Rock Tunnel B1F-E", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-E 2", "Rock Tunnel B1F-E", lambda state: logic.rock_tunnel(state, player)) connect(multiworld, player, "Rock Tunnel 1F-S", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) connect(multiworld, player, "Rock Tunnel 1F-NW", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) connect(multiworld, player, "Rock Tunnel 1F-NE", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) @@ -1860,7 +1883,6 @@ def create_regions(self): logic.has_badges(state, self.multiworld.cerulean_cave_badges_condition[player].value, player) and logic.has_key_items(state, self.multiworld.cerulean_cave_key_items_condition[player].total, player) and logic.can_surf(state, player)) - # access to any part of a city will enable flying to the Pokemon Center connect(multiworld, player, "Cerulean City-Cave", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) connect(multiworld, player, "Cerulean City-Badge House Backyard", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) @@ -1876,7 +1898,6 @@ def create_regions(self): connect(multiworld, player, "Cinnabar Island-G", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-G to Cinnabar Island (Fly)") connect(multiworld, player, "Cinnabar Island-M", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-M to Cinnabar Island (Fly)") - # drops connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F (Drop)") connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F-NE", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F-NE (Drop)") @@ -1904,14 +1925,50 @@ def create_regions(self): lambda state: logic.can_fly(state, player) and state.has("Town Map", player), one_way=True, name="Town Map Fly Location") + cache = multiworld.regions.entrance_cache[self.player].copy() + if multiworld.badgesanity[player] or multiworld.door_shuffle[player] in ("off", "simple"): + badges = None + badge_locs = None + else: + badges = [item for item in self.item_pool if "Badge" in item.name] + for badge in badges: + self.item_pool.remove(badge) + badge_locs = [multiworld.get_location(loc, player) for loc in [ + "Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize", "Vermilion Gym - Lt. Surge Prize", + "Celadon Gym - Erika Prize", "Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize", + "Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize" + ]] + for attempt in range(10): + try: + door_shuffle(self, multiworld, player, badges, badge_locs) + except DoorShuffleException as e: + if attempt == 9: + raise e + for region in self.multiworld.get_regions(player): + for entrance in reversed(region.exits): + if isinstance(entrance, PokemonRBWarp): + region.exits.remove(entrance) + multiworld.regions.entrance_cache[self.player] = cache + if badge_locs: + for loc in badge_locs: + loc.item = None + loc.locked = False + else: + break + + +def door_shuffle(world, multiworld, player, badges, badge_locs): entrances = [] + full_interiors = [] for region_name, region_entrances in warp_data.items(): + region = multiworld.get_region(region_name, player) for entrance_data in region_entrances: - region = multiworld.get_region(region_name, player) shuffle = True - if not outdoor_map(region.name) and not outdoor_map(entrance_data['to']['map']) and \ - multiworld.door_shuffle[player] not in ("insanity", "decoupled"): - shuffle = False + interior = False + if not outdoor_map(region.name) and not outdoor_map(entrance_data['to']['map']): + if multiworld.door_shuffle[player] not in ("full", "insanity", "decoupled"): + shuffle = False + interior = True if multiworld.door_shuffle[player] == "simple": if sorted([entrance_data['to']['map'], region.name]) == ["Celadon Game Corner-Hidden Stairs", "Rocket Hideout B1F"]: @@ -1921,11 +1978,14 @@ def create_regions(self): if (multiworld.randomize_rock_tunnel[player] and "Rock Tunnel" in region.name and "Rock Tunnel" in entrance_data['to']['map']): shuffle = False - if (f"{region.name} to {entrance_data['to']['map']}" if "name" not in entrance_data else + elif (f"{region.name} to {entrance_data['to']['map']}" if "name" not in entrance_data else entrance_data["name"]) in silph_co_warps + saffron_gym_warps: - if multiworld.warp_tile_shuffle[player] or multiworld.door_shuffle[player] in ("insanity", - "decoupled"): + if multiworld.warp_tile_shuffle[player]: shuffle = True + if multiworld.warp_tile_shuffle[player] == "mixed" and multiworld.door_shuffle[player] == "full": + interior = True + else: + interior = False else: shuffle = False elif not multiworld.door_shuffle[player]: @@ -1935,33 +1995,49 @@ def create_regions(self): entrance_data else entrance_data["name"], region, entrance_data["id"], entrance_data["address"], entrance_data["flags"] if "flags" in entrance_data else "") - # if "Rock Tunnel" in region_name: - # entrance.access_rule = lambda state: logic.rock_tunnel(state, player) - entrances.append(entrance) + if interior and multiworld.door_shuffle[player] == "full": + full_interiors.append(entrance) + else: + entrances.append(entrance) region.exits.append(entrance) else: - # connect(multiworld, player, region.name, entrance_data['to']['map'], one_way=True) - if "Rock Tunnel" in region.name: - connect(multiworld, player, region.name, entrance_data["to"]["map"], - lambda state: logic.rock_tunnel(state, player), one_way=True) - else: - connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, - name=entrance_data["name"] if "name" in entrance_data else None) + connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, + name=entrance_data["name"] if "name" in entrance_data else None) forced_connections = set() + one_way_forced_connections = set() if multiworld.door_shuffle[player]: - forced_connections.update(mandatory_connections.copy()) + if multiworld.door_shuffle[player] in ("full", "insanity", "decoupled"): + safari_zone_doors = [door for pair in safari_zone_connections for door in pair] + safari_zone_doors.sort() + order = ["Center", "East", "North", "West"] + multiworld.random.shuffle(order) + usable_doors = ["Safari Zone Gate-N to Safari Zone Center-S"] + for section in order: + section_doors = [door for door in safari_zone_doors if door.startswith(f"Safari Zone {section}")] + connect_door_a = multiworld.random.choice(usable_doors) + connect_door_b = multiworld.random.choice(section_doors) + usable_doors.remove(connect_door_a) + section_doors.remove(connect_door_b) + forced_connections.add((connect_door_a, connect_door_b)) + usable_doors += section_doors + multiworld.random.shuffle(usable_doors) + while usable_doors: + forced_connections.add((usable_doors.pop(), usable_doors.pop())) + else: + forced_connections.update(safari_zone_connections) + usable_safe_rooms = safe_rooms.copy() if multiworld.door_shuffle[player] == "simple": forced_connections.update(simple_mandatory_connections) else: usable_safe_rooms += pokemarts - if self.multiworld.key_items_only[self.player]: + if multiworld.key_items_only[player]: usable_safe_rooms.remove("Viridian Pokemart to Viridian City") - if multiworld.door_shuffle[player] in ("insanity", "decoupled"): - forced_connections.update(insanity_mandatory_connections) + if multiworld.door_shuffle[player] in ("full", "insanity", "decoupled"): + forced_connections.update(full_mandatory_connections) r = multiworld.random.randint(0, 3) if r == 2: forced_connections.add(("Pokemon Mansion 1F-SE to Pokemon Mansion B1F", @@ -1969,6 +2045,9 @@ def create_regions(self): forced_connections.add(("Pokemon Mansion 2F to Pokemon Mansion 3F", multiworld.random.choice(mansion_stair_destinations + mansion_dead_ends + ["Pokemon Mansion B1F to Pokemon Mansion 1F-SE"]))) + if multiworld.door_shuffle[player] == "full": + forced_connections.add(("Pokemon Mansion 1F to Pokemon Mansion 2F", + "Pokemon Mansion 3F to Pokemon Mansion 2F")) elif r == 3: dead_end = multiworld.random.randint(0, 1) forced_connections.add(("Pokemon Mansion 3F-SE to Pokemon Mansion 2F-E", @@ -1987,7 +2066,8 @@ def create_regions(self): multiworld.random.choice(mansion_stair_destinations + ["Pokemon Mansion B1F to Pokemon Mansion 1F-SE"]))) - usable_safe_rooms += insanity_safe_rooms + if multiworld.door_shuffle[player] in ("insanity", "decoupled"): + usable_safe_rooms += insanity_safe_rooms safe_rooms_sample = multiworld.random.sample(usable_safe_rooms, 6) pallet_safe_room = safe_rooms_sample[-1] @@ -1995,16 +2075,28 @@ def create_regions(self): for a, b in zip(multiworld.random.sample(["Pallet Town to Player's House 1F", "Pallet Town to Oak's Lab", "Pallet Town to Rival's House"], 3), ["Oak's Lab to Pallet Town", "Player's House 1F to Pallet Town", pallet_safe_room]): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) + + if multiworld.door_shuffle[player] == "decoupled": + for a, b in zip(["Oak's Lab to Pallet Town", "Player's House 1F to Pallet Town", pallet_safe_room], + multiworld.random.sample(["Pallet Town to Player's House 1F", "Pallet Town to Oak's Lab", + "Pallet Town to Rival's House"], 3)): + one_way_forced_connections.add((a, b)) + for a, b in zip(safari_zone_houses, safe_rooms_sample): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) + if multiworld.door_shuffle[player] == "decoupled": + for a, b in zip(multiworld.random.sample(safe_rooms_sample[:-1], len(safe_rooms_sample) - 1), + safari_zone_houses): + one_way_forced_connections.add((a, b)) + if multiworld.door_shuffle[player] == "simple": # force Indigo Plateau Lobby to vanilla location on simple, otherwise shuffle with Pokemon Centers. for a, b in zip(multiworld.random.sample(pokemon_center_entrances[0:-1], 11), pokemon_centers[0:-1]): forced_connections.add((a, b)) forced_connections.add((pokemon_center_entrances[-1], pokemon_centers[-1])) forced_pokemarts = multiworld.random.sample(pokemart_entrances, 8) - if self.multiworld.key_items_only[self.player]: + if multiworld.key_items_only[player]: forced_pokemarts.sort(key=lambda i: i[0] != "Viridian Pokemart to Viridian City") for a, b in zip(forced_pokemarts, pokemarts): forced_connections.add((a, b)) @@ -2014,15 +2106,19 @@ def create_regions(self): # warping outside an entrance that isn't the Pokemon Center, just always put Pokemon Centers at Pokemon # Center entrances for a, b in zip(multiworld.random.sample(pokemon_center_entrances, 12), pokemon_centers): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) # Ensure a Pokemart is available at the beginning of the game if multiworld.key_items_only[player]: - forced_connections.add((multiworld.random.choice(initial_doors), "Viridian Pokemart to Viridian City")) - elif "Pokemart" not in pallet_safe_room: - forced_connections.add((multiworld.random.choice(initial_doors), multiworld.random.choice( - [mart for mart in pokemarts if mart not in safe_rooms_sample]))) + one_way_forced_connections.add((multiworld.random.choice(initial_doors), + "Viridian Pokemart to Viridian City")) - if multiworld.warp_tile_shuffle[player]: + elif "Pokemart" not in pallet_safe_room: + one_way_forced_connections.add((multiworld.random.choice(initial_doors), multiworld.random.choice( + [mart for mart in pokemarts if mart not in safe_rooms_sample]))) + + if multiworld.warp_tile_shuffle[player] == "shuffle" or (multiworld.warp_tile_shuffle[player] == "mixed" + and multiworld.door_shuffle[player] + in ("off", "simple", "interiors")): warps = multiworld.random.sample(silph_co_warps, len(silph_co_warps)) # The only warp tiles never reachable from the stairs/elevators are the two 7F-NW warps (where the rival is) # and the final 11F-W warp. As long as the two 7F-NW warps aren't connected to each other, everything should @@ -2055,13 +2151,38 @@ def create_regions(self): while warps: forced_connections.add((warps.pop(), warps.pop(),)) + dc_destinations = None + if multiworld.door_shuffle[player] == "decoupled": + dc_destinations = entrances.copy() + for pair in one_way_forced_connections: + entrance_a = multiworld.get_entrance(pair[0], player) + entrance_b = multiworld.get_entrance(pair[1], player) + entrance_a.connect(entrance_b) + entrances.remove(entrance_a) + dc_destinations.remove(entrance_b) + else: + forced_connections.update(one_way_forced_connections) + for pair in forced_connections: entrance_a = multiworld.get_entrance(pair[0], player) entrance_b = multiworld.get_entrance(pair[1], player) entrance_a.connect(entrance_b) entrance_b.connect(entrance_a) - entrances.remove(entrance_a) - entrances.remove(entrance_b) + if entrance_a in entrances: + entrances.remove(entrance_a) + elif entrance_a in full_interiors: + full_interiors.remove(entrance_a) + else: + raise DoorShuffleException("Attempted to force connection with entrance not in any entrance pool, likely because it tried to force an entrance to connect twice.") + if entrance_b in entrances: + entrances.remove(entrance_b) + elif entrance_b in full_interiors: + full_interiors.remove(entrance_b) + else: + raise DoorShuffleException("Attempted to force connection with entrance not in any entrance pool, likely because it tried to force an entrance to connect twice.") + if multiworld.door_shuffle[player] == "decoupled": + dc_destinations.remove(entrance_a) + dc_destinations.remove(entrance_b) if multiworld.door_shuffle[player] == "simple": def connect_connecting_interiors(interior_exits, exterior_entrances): @@ -2069,7 +2190,7 @@ def create_regions(self): for a, b in zip(interior, exterior): entrance_a = multiworld.get_entrance(a, player) if b is None: - #entrance_b = multiworld.get_entrance(entrances[0], player) + # entrance_b = multiworld.get_entrance(entrances[0], player) # should just be able to use the entrance_b from the previous link? pass else: @@ -2102,7 +2223,7 @@ def create_regions(self): single_entrance_dungeon_entrances = dungeon_entrances.copy() for i in range(2): - if True or not multiworld.random.randint(0, 2): + if not multiworld.random.randint(0, 2): placed_connecting_interior_dungeons.append(multi_purpose_dungeons[i]) interior_dungeon_entrances.append([multi_purpose_dungeon_entrances[i], None]) else: @@ -2185,7 +2306,7 @@ def create_regions(self): and interiors[0] in connecting_interiors[13:17] # Saffron Gate at Underground Path North South and interiors[13] in connecting_interiors[13:17] # Saffron Gate at Route 5 Saffron Gate and multi_purpose_dungeons[0] == placed_connecting_interior_dungeons[4] # Pokémon Mansion at Rock Tunnel, which is - and (not multiworld.tea[player]) # not traversable backwards + and (not multiworld.tea[player]) # not traversable backwards and multiworld.route_3_condition[player] == "defeat_brock" and multiworld.worlds[player].fly_map != "Cerulean City" and multiworld.worlds[player].town_map_fly_map != "Cerulean City"): @@ -2209,20 +2330,64 @@ def create_regions(self): entrance_b.connect(entrance_a) elif multiworld.door_shuffle[player]: if multiworld.door_shuffle[player] == "full": + multiworld.random.shuffle(full_interiors) + + def search_for_exit(entrance, region, checked_regions): + checked_regions.add(region) + for exit_candidate in region.exits: + if ((not exit_candidate.connected_region) + and exit_candidate in entrances and exit_candidate is not entrance): + return exit_candidate + for entrance_candidate in region.entrances: + if entrance_candidate.parent_region not in checked_regions: + found_exit = search_for_exit(entrance, entrance_candidate.parent_region, checked_regions) + if found_exit is not None: + return found_exit + return None + + while True: + for entrance_a in full_interiors: + if search_for_exit(entrance_a, entrance_a.parent_region, set()) is None: + for entrance_b in full_interiors: + if search_for_exit(entrance_b, entrance_b.parent_region, set()): + entrance_a.connect(entrance_b) + entrance_b.connect(entrance_a) + # Yes, it removes from full_interiors while iterating through it, but it immediately + # breaks out, from both loops. + full_interiors.remove(entrance_a) + full_interiors.remove(entrance_b) + break + else: + raise DoorShuffleException("No non-dead end interior sections found in Pokemon Red and Blue door shuffle.") + break + else: + break + + loop_out_interiors = [] + multiworld.random.shuffle(entrances) + for entrance in reversed(entrances): + if not outdoor_map(entrance.parent_region.name): + found_exit = search_for_exit(entrance, entrance.parent_region, set()) + if found_exit is None: + continue + loop_out_interiors.append([found_exit, entrance]) + entrances.remove(entrance) + + if len(loop_out_interiors) == 2: + break + + for entrance_a, entrance_b in zip(full_interiors[:len(full_interiors) // 2], + full_interiors[len(full_interiors) // 2:]): + entrance_a.connect(entrance_b) + entrance_b.connect(entrance_a) + + elif multiworld.door_shuffle[player] == "interiors": loop_out_interiors = [[multiworld.get_entrance(e[0], player), multiworld.get_entrance(e[1], player)] for e in multiworld.random.sample(unsafe_connecting_interior_dungeons + safe_connecting_interior_dungeons, 2)] entrances.remove(loop_out_interiors[0][1]) entrances.remove(loop_out_interiors[1][1]) if not multiworld.badgesanity[player]: - badges = [item for item in self.item_pool if "Badge" in item.name] - for badge in badges: - self.item_pool.remove(badge) - badge_locs = [] - for loc in ["Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize", "Vermilion Gym - Lt. Surge Prize", - "Celadon Gym - Erika Prize", "Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize", - "Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"]: - badge_locs.append(multiworld.get_location(loc, player)) multiworld.random.shuffle(badges) while badges[3].name == "Cascade Badge" and multiworld.badges_needed_for_hm_moves[player]: multiworld.random.shuffle(badges) @@ -2233,7 +2398,7 @@ def create_regions(self): for item, data in item_table.items(): if (data.id or item in poke_data.pokemon_data) and data.classification == ItemClassification.progression \ and ("Badge" not in item or multiworld.badgesanity[player]): - state.collect(self.create_item(item)) + state.collect(world.create_item(item)) multiworld.random.shuffle(entrances) reachable_entrances = [] @@ -2269,22 +2434,23 @@ def create_regions(self): "Defeat Viridian Gym Giovanni", ] - event_locations = self.multiworld.get_filled_locations(player) + event_locations = multiworld.get_filled_locations(player) - def adds_reachable_entrances(entrances_copy, item, dead_end_cache): - ret = dead_end_cache.get(item.name) - if (ret != None): - return ret + def adds_reachable_entrances(item): state_copy = state.copy() state_copy.collect(item, True) state.sweep_for_events(locations=event_locations) - ret = len([entrance for entrance in entrances_copy if entrance in reachable_entrances or - entrance.parent_region.can_reach(state_copy)]) > len(reachable_entrances) - dead_end_cache[item.name] = ret - return ret + new_reachable_entrances = len([entrance for entrance in entrances if entrance in reachable_entrances or + entrance.parent_region.can_reach(state_copy)]) + return new_reachable_entrances > len(reachable_entrances) - def dead_end(entrances_copy, e, dead_end_cache): + def dead_end(e): + if e.can_reach(state): + return True + elif multiworld.door_shuffle[player] == "decoupled": + # Any unreachable exit in decoupled is not a dead end + return False region = e.parent_region check_warps = set() checked_regions = {region} @@ -2292,93 +2458,105 @@ def create_regions(self): check_warps.remove(e) for location in region.locations: if location.item and location.item.name in relevant_events and \ - adds_reachable_entrances(entrances_copy, location.item, dead_end_cache): + adds_reachable_entrances(location.item): return False while check_warps: warp = check_warps.pop() warp = warp if warp not in reachable_entrances: - if "Rock Tunnel" not in warp.name or logic.rock_tunnel(state, player): - # confirm warp is in entrances list to ensure it's not a loop-out interior - if warp.connected_region is None and warp in entrances_copy: - return False - elif (isinstance(warp, PokemonRBWarp) and ("Rock Tunnel" not in warp.name or - logic.rock_tunnel(state, player))) or warp.access_rule(state): - if warp.connected_region and warp.connected_region not in checked_regions: - checked_regions.add(warp.connected_region) - check_warps.update(warp.connected_region.exits) - for location in warp.connected_region.locations: - if (location.item and location.item.name in relevant_events and - adds_reachable_entrances(entrances_copy, location.item, dead_end_cache)): - return False + # confirm warp is in entrances list to ensure it's not a loop-out interior + if warp.connected_region is None and warp in entrances: + return False + elif isinstance(warp, PokemonRBWarp) or warp.access_rule(state): + if warp.connected_region and warp.connected_region not in checked_regions: + checked_regions.add(warp.connected_region) + check_warps.update(warp.connected_region.exits) + for location in warp.connected_region.locations: + if (location.item and location.item.name in relevant_events and + adds_reachable_entrances(location.item)): + return False return True starting_entrances = len(entrances) - dc_connected = [] - rock_tunnel_entrances = [entrance for entrance in entrances if "Rock Tunnel" in entrance.name] - entrances = [entrance for entrance in entrances if entrance not in rock_tunnel_entrances] + while entrances: state.update_reachable_regions(player) state.sweep_for_events(locations=event_locations) - if rock_tunnel_entrances and logic.rock_tunnel(state, player): - entrances += rock_tunnel_entrances - rock_tunnel_entrances = None + multiworld.random.shuffle(entrances) + + if multiworld.door_shuffle[player] == "decoupled": + multiworld.random.shuffle(dc_destinations) + else: + entrances.sort(key=lambda e: e.name not in entrance_only) reachable_entrances = [entrance for entrance in entrances if entrance in reachable_entrances or entrance.parent_region.can_reach(state)] - assert reachable_entrances, \ - "Ran out of reachable entrances in Pokemon Red and Blue door shuffle" - multiworld.random.shuffle(entrances) - if multiworld.door_shuffle[player] == "decoupled" and len(entrances) == 1: - entrances += dc_connected - entrances[-1].connect(entrances[0]) - while len(entrances) > 1: - entrances.pop(0).connect(entrances[0]) - break - if multiworld.door_shuffle[player] == "full" or len(entrances) != len(reachable_entrances): - entrances.sort(key=lambda e: e.name not in entrance_only) - dead_end_cache = {} + entrances.sort(key=lambda e: e in reachable_entrances) + + if not reachable_entrances: + raise DoorShuffleException("Ran out of reachable entrances in Pokemon Red and Blue door shuffle") + + entrance_a = reachable_entrances.pop(0) + entrances.remove(entrance_a) + + is_outdoor_map = outdoor_map(entrance_a.parent_region.name) + + if multiworld.door_shuffle[player] in ("interiors", "full") or len(entrances) != len(reachable_entrances): + + find_dead_end = False + if (len(reachable_entrances) > + (1 if multiworld.door_shuffle[player] in ("insanity", "decoupled") else 8) and len(entrances) + <= (starting_entrances - 3)): + find_dead_end = True + + if (multiworld.door_shuffle[player] in ("interiors", "full") and len(entrances) < 48 + and not is_outdoor_map): + # Try to prevent a situation where the only remaining outdoor entrances are ones that cannot be + # reached except by connecting directly to it. + entrances.sort(key=lambda e: e.name not in unreachable_outdoor_entrances) + if entrances[0].name in unreachable_outdoor_entrances and len([entrance for entrance + in reachable_entrances if not outdoor_map(entrance.parent_region.name)]) > 1: + find_dead_end = True - # entrances list is empty while it's being sorted, must pass a copy to iterate through - entrances_copy = entrances.copy() if multiworld.door_shuffle[player] == "decoupled": - entrances.sort(key=lambda e: 1 if e.connected_region is not None else 2 if e not in - reachable_entrances else 0) - assert entrances[0].connected_region is None,\ - "Ran out of valid reachable entrances in Pokemon Red and Blue door shuffle" - elif len(reachable_entrances) > (1 if multiworld.door_shuffle[player] == "insanity" else 8) and len( - entrances) <= (starting_entrances - 3): - entrances.sort(key=lambda e: 0 if e in reachable_entrances else 2 if - dead_end(entrances_copy, e, dead_end_cache) else 1) + destinations = dc_destinations + elif multiworld.door_shuffle[player] in ("interiors", "full"): + destinations = [entrance for entrance in entrances if outdoor_map(entrance.parent_region.name) is + not is_outdoor_map] + if not destinations: + raise DoorShuffleException("Ran out of connectable destinations in Pokemon Red and Blue door shuffle") else: - entrances.sort(key=lambda e: 0 if e in reachable_entrances else 1 if - dead_end(entrances_copy, e, dead_end_cache) else 2) - if multiworld.door_shuffle[player] == "full": - outdoor = outdoor_map(entrances[0].parent_region.name) - if len(entrances) < 48 and not outdoor: - # Prevent a situation where the only remaining outdoor entrances are ones that cannot be reached - # except by connecting directly to it. - entrances.sort(key=lambda e: e.name in unreachable_outdoor_entrances) + destinations = entrances - entrances.sort(key=lambda e: outdoor_map(e.parent_region.name) != outdoor) - assert entrances[0] in reachable_entrances, \ - "Ran out of valid reachable entrances in Pokemon Red and Blue door shuffle" - if (multiworld.door_shuffle[player] == "decoupled" and len(reachable_entrances) > 8 and len(entrances) - <= (starting_entrances - 3)): - entrance_b = entrances.pop(1) + destinations.sort(key=lambda e: e == entrance_a) + for entrance in destinations: + if (dead_end(entrance) is find_dead_end and (multiworld.door_shuffle[player] != "decoupled" + or entrance.parent_region.name.split("-")[0] != + entrance_a.parent_region.name.split("-")[0])): + entrance_b = entrance + destinations.remove(entrance) + break + else: + entrance_b = destinations.pop(0) + + if multiworld.door_shuffle[player] in ("interiors", "full"): + # on Interiors/Full, the destinations variable does not point to the entrances list, so we need to + # remove from that list here. + entrances.remove(entrance_b) else: - entrance_b = entrances.pop() - entrance_a = entrances.pop(0) + # Everything is reachable. Just start connecting the rest of the doors at random. + if multiworld.door_shuffle[player] == "decoupled": + entrance_b = dc_destinations.pop(0) + else: + entrance_b = entrances.pop(0) + entrance_a.connect(entrance_b) - if multiworld.door_shuffle[player] == "decoupled": - entrances.append(entrance_b) - dc_connected.append(entrance_a) - else: + if multiworld.door_shuffle[player] != "decoupled": entrance_b.connect(entrance_a) - if multiworld.door_shuffle[player] == "full": + if multiworld.door_shuffle[player] in ("interiors", "full"): for pair in loop_out_interiors: pair[1].connected_region = pair[0].connected_region pair[1].parent_region.entrances.append(pair[0]) @@ -2443,11 +2621,18 @@ class PokemonRBWarp(Entrance): def access_rule(self, state): if self.connected_region is None: return False - if "Rock Tunnel" in self.parent_region.name or "Rock Tunnel" in self.connected_region.name: - return logic.rock_tunnel(state, self.player) + if "Elevator" in self.parent_region.name and ( + (state.multiworld.all_elevators_locked[self.player] + or "Rocket Hideout" in self.parent_region.name) + and not state.has("Lift Key", self.player)): + return False return True +class DoorShuffleException(Exception): + pass + + class PokemonRBRegion(Region): def __init__(self, name, player, multiworld): super().__init__(name, player, multiworld) diff --git a/worlds/pokemon_rb/rom.py b/worlds/pokemon_rb/rom.py index 81ab6648..b6c1221a 100644 --- a/worlds/pokemon_rb/rom.py +++ b/worlds/pokemon_rb/rom.py @@ -9,9 +9,10 @@ from .items import item_table from .pokemon import set_mon_palettes from .rock_tunnel import randomize_rock_tunnel from .rom_addresses import rom_addresses -from .regions import PokemonRBWarp, map_ids +from .regions import PokemonRBWarp, map_ids, town_map_coords from . import poke_data + def write_quizzes(self, data, random): def get_quiz(q, a): @@ -204,19 +205,21 @@ def generate_output(self, output_directory: str): basemd5 = hashlib.md5() basemd5.update(data) - lab_loc = self.multiworld.get_entrance("Oak's Lab to Pallet Town", self.player).target + pallet_connections = {entrance: self.multiworld.get_entrance(f"Pallet Town to {entrance}", + self.player).connected_region.name for + entrance in ["Player's House 1F", "Oak's Lab", + "Rival's House"]} paths = None - if lab_loc == 0: # Player's House + if pallet_connections["Player's House 1F"] == "Oak's Lab": paths = ((0x00, 4, 0x80, 5, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x20, 5, 0x80, 5, 0xFF)) - elif lab_loc == 1: # Rival's House + elif pallet_connections["Rival's House"] == "Oak's Lab": paths = ((0x00, 4, 0xC0, 3, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x10, 3, 0x80, 5, 0xFF)) if paths: write_bytes(data, paths[0], rom_addresses["Path_Pallet_Oak"]) write_bytes(data, paths[1], rom_addresses["Path_Pallet_Player"]) - home_loc = self.multiworld.get_entrance("Player's House 1F to Pallet Town", self.player).target - if home_loc == 1: # Rival's House + if pallet_connections["Rival's House"] == "Player's House 1F": write_bytes(data, [0x2F, 0xC7, 0x06, 0x0D, 0x00, 0x01], rom_addresses["Pallet_Fly_Coords"]) - elif home_loc == 2: # Oak's Lab + elif pallet_connections["Oak's Lab"] == "Player's House 1F": write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"]) for region in self.multiworld.get_regions(self.player): @@ -238,6 +241,14 @@ def generate_output(self, output_directory: str): data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i] data[address + 1] = map_ids[connected_map_name] + if self.multiworld.door_shuffle[self.player] == "simple": + for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values(): + destination = self.multiworld.get_entrance(entrance, self.player).connected_region.name + (_, x, y, _, _, map_order_entry) = town_map_coords[destination] + for map_coord_entry in map_coords_entries: + data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x + data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name] + if not self.multiworld.key_items_only[self.player]: for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM", "Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM", diff --git a/worlds/pokemon_rb/rom_addresses.py b/worlds/pokemon_rb/rom_addresses.py index ffb89a4d..e5c07397 100644 --- a/worlds/pokemon_rb/rom_addresses.py +++ b/worlds/pokemon_rb/rom_addresses.py @@ -1,10 +1,10 @@ rom_addresses = { "Option_Encounter_Minimum_Steps": 0x3c1, - "Option_Pitch_Black_Rock_Tunnel": 0x75c, - "Option_Blind_Trainers": 0x30c7, - "Option_Trainersanity1": 0x3157, - "Option_Split_Card_Key": 0x3e10, - "Option_Fix_Combat_Bugs": 0x3e11, + "Option_Pitch_Black_Rock_Tunnel": 0x76a, + "Option_Blind_Trainers": 0x30d5, + "Option_Trainersanity1": 0x3165, + "Option_Split_Card_Key": 0x3e1e, + "Option_Fix_Combat_Bugs": 0x3e1f, "Option_Lose_Money": 0x40d4, "Base_Stats_Mew": 0x4260, "Title_Mon_First": 0x4373, @@ -131,49 +131,49 @@ rom_addresses = { "Starter2_K": 0x19611, "Starter3_K": 0x19619, "Event_Rocket_Thief": 0x19733, - "Option_Cerulean_Cave_Badges": 0x19857, - "Option_Cerulean_Cave_Key_Items": 0x1985e, - "Text_Cerulean_Cave_Badges": 0x198c3, - "Text_Cerulean_Cave_Key_Items": 0x198d1, - "Event_Stranded_Man": 0x19b28, - "Event_Rivals_Sister": 0x19cfb, - "Warps_BluesHouse": 0x19d51, - "Warps_VermilionTradeHouse": 0x19da8, - "Require_Pokedex_D": 0x19e3f, - "Option_Elite_Four_Key_Items": 0x19e89, - "Option_Elite_Four_Pokedex": 0x19e90, - "Option_Elite_Four_Badges": 0x19e97, - "Text_Elite_Four_Badges": 0x19f33, - "Text_Elite_Four_Key_Items": 0x19f3d, - "Text_Elite_Four_Pokedex": 0x19f50, - "Shop10": 0x1a004, - "Warps_IndigoPlateauLobby": 0x1a030, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a158, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a166, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a174, - "Event_SKC4F": 0x1a187, - "Warps_SilphCo4F": 0x1a209, - "Missable_Silph_Co_4F_Item_1": 0x1a249, - "Missable_Silph_Co_4F_Item_2": 0x1a250, - "Missable_Silph_Co_4F_Item_3": 0x1a257, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3af, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3bd, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3cb, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3d9, - "Event_SKC5F": 0x1a3ec, - "Warps_SilphCo5F": 0x1a496, - "Missable_Silph_Co_5F_Item_1": 0x1a4de, - "Missable_Silph_Co_5F_Item_2": 0x1a4e5, - "Missable_Silph_Co_5F_Item_3": 0x1a4ec, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a61c, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a62a, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a638, - "Event_SKC6F": 0x1a659, - "Warps_SilphCo6F": 0x1a737, - "Missable_Silph_Co_6F_Item_1": 0x1a787, - "Missable_Silph_Co_6F_Item_2": 0x1a78e, - "Path_Pallet_Oak": 0x1a914, - "Path_Pallet_Player": 0x1a921, + "Option_Cerulean_Cave_Badges": 0x19861, + "Option_Cerulean_Cave_Key_Items": 0x19868, + "Text_Cerulean_Cave_Badges": 0x198d7, + "Text_Cerulean_Cave_Key_Items": 0x198e5, + "Event_Stranded_Man": 0x19b3c, + "Event_Rivals_Sister": 0x19d0f, + "Warps_BluesHouse": 0x19d65, + "Warps_VermilionTradeHouse": 0x19dbc, + "Require_Pokedex_D": 0x19e53, + "Option_Elite_Four_Key_Items": 0x19e9d, + "Option_Elite_Four_Pokedex": 0x19ea4, + "Option_Elite_Four_Badges": 0x19eab, + "Text_Elite_Four_Badges": 0x19f47, + "Text_Elite_Four_Key_Items": 0x19f51, + "Text_Elite_Four_Pokedex": 0x19f64, + "Shop10": 0x1a018, + "Warps_IndigoPlateauLobby": 0x1a044, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a16c, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a17a, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a188, + "Event_SKC4F": 0x1a19b, + "Warps_SilphCo4F": 0x1a21d, + "Missable_Silph_Co_4F_Item_1": 0x1a25d, + "Missable_Silph_Co_4F_Item_2": 0x1a264, + "Missable_Silph_Co_4F_Item_3": 0x1a26b, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3c3, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3d1, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3df, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3ed, + "Event_SKC5F": 0x1a400, + "Warps_SilphCo5F": 0x1a4aa, + "Missable_Silph_Co_5F_Item_1": 0x1a4f2, + "Missable_Silph_Co_5F_Item_2": 0x1a4f9, + "Missable_Silph_Co_5F_Item_3": 0x1a500, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a630, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a63e, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a64c, + "Event_SKC6F": 0x1a66d, + "Warps_SilphCo6F": 0x1a74b, + "Missable_Silph_Co_6F_Item_1": 0x1a79b, + "Missable_Silph_Co_6F_Item_2": 0x1a7a2, + "Path_Pallet_Oak": 0x1a928, + "Path_Pallet_Player": 0x1a935, "Warps_CinnabarIsland": 0x1c026, "Warps_Route1": 0x1c0e9, "Option_Extra_Key_Items_B": 0x1ca46, @@ -1074,112 +1074,112 @@ rom_addresses = { "Missable_Route_25_Item": 0x5080b, "Warps_IndigoPlateau": 0x5093a, "Warps_SaffronCity": 0x509e0, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_0_ITEM": 0x50d63, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_1_ITEM": 0x50d71, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_2_ITEM": 0x50d7f, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_3_ITEM": 0x50d8d, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_4_ITEM": 0x50d9b, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_5_ITEM": 0x50da9, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_6_ITEM": 0x50db7, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_7_ITEM": 0x50dc5, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_8_ITEM": 0x50dd3, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_9_ITEM": 0x50de1, - "Starter2_B": 0x50ffe, - "Starter3_B": 0x51000, - "Starter1_B": 0x51002, - "Starter2_A": 0x5111d, - "Starter3_A": 0x5111f, - "Starter1_A": 0x51121, - "Option_Route23_Badges": 0x5126e, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_0_ITEM": 0x51384, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_1_ITEM": 0x51392, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_2_ITEM": 0x513a0, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_3_ITEM": 0x513ae, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_4_ITEM": 0x513bc, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_5_ITEM": 0x513ca, - "Event_Nugget_Bridge": 0x513e1, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_0_ITEM": 0x51569, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_1_ITEM": 0x51577, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_2_ITEM": 0x51585, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_3_ITEM": 0x51593, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_4_ITEM": 0x515a1, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_5_ITEM": 0x515af, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_6_ITEM": 0x515bd, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_7_ITEM": 0x515cb, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_8_ITEM": 0x515d9, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0_ITEM": 0x51772, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1_ITEM": 0x51780, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2_ITEM": 0x5178e, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3_ITEM": 0x5179c, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4_ITEM": 0x517aa, - "Trainersanity_EVENT_BEAT_MOLTRES_ITEM": 0x517b8, - "Warps_VictoryRoad2F": 0x51855, - "Static_Encounter_Moltres": 0x5189f, - "Missable_Victory_Road_2F_Item_1": 0x518a7, - "Missable_Victory_Road_2F_Item_2": 0x518ae, - "Missable_Victory_Road_2F_Item_3": 0x518b5, - "Missable_Victory_Road_2F_Item_4": 0x518bc, - "Warps_MtMoonB1F": 0x5198d, - "Starter2_L": 0x51beb, - "Starter3_L": 0x51bf3, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_0_ITEM": 0x51ca4, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_1_ITEM": 0x51cb2, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_2_ITEM": 0x51cc0, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_3_ITEM": 0x51cce, - "Gift_Lapras": 0x51cef, - "Event_SKC7F": 0x51d7a, - "Warps_SilphCo7F": 0x51e49, - "Missable_Silph_Co_7F_Item_1": 0x51ea5, - "Missable_Silph_Co_7F_Item_2": 0x51eac, - "Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM": 0x51fd2, - "Warps_PokemonMansion2F": 0x52045, - "Missable_Pokemon_Mansion_2F_Item": 0x52063, - "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_0_ITEM": 0x52213, - "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_1_ITEM": 0x52221, - "Warps_PokemonMansion3F": 0x5225e, - "Missable_Pokemon_Mansion_3F_Item_1": 0x52280, - "Missable_Pokemon_Mansion_3F_Item_2": 0x52287, - "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_0_ITEM": 0x523c9, - "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_1_ITEM": 0x523d7, - "Warps_PokemonMansionB1F": 0x52414, - "Missable_Pokemon_Mansion_B1F_Item_1": 0x5242e, - "Missable_Pokemon_Mansion_B1F_Item_2": 0x52435, - "Missable_Pokemon_Mansion_B1F_Item_3": 0x5243c, - "Missable_Pokemon_Mansion_B1F_Item_4": 0x52443, - "Missable_Pokemon_Mansion_B1F_Item_5": 0x52450, - "Option_Safari_Zone_Battle_Type": 0x52565, - "Prize_Mon_A2": 0x527ef, - "Prize_Mon_B2": 0x527f0, - "Prize_Mon_C2": 0x527f1, - "Prize_Mon_D2": 0x527fa, - "Prize_Mon_E2": 0x527fb, - "Prize_Mon_F2": 0x527fc, - "Prize_Item_A": 0x52805, - "Prize_Item_B": 0x52806, - "Prize_Item_C": 0x52807, - "Prize_Mon_A": 0x5293c, - "Prize_Mon_B": 0x5293e, - "Prize_Mon_C": 0x52940, - "Prize_Mon_D": 0x52942, - "Prize_Mon_E": 0x52944, - "Prize_Mon_F": 0x52946, - "Start_Inventory": 0x52a7b, - "Map_Fly_Location": 0x52c75, - "Reset_A": 0x52d21, - "Reset_B": 0x52d4d, - "Reset_C": 0x52d79, - "Reset_D": 0x52da5, - "Reset_E": 0x52dd1, - "Reset_F": 0x52dfd, - "Reset_G": 0x52e29, - "Reset_H": 0x52e55, - "Reset_I": 0x52e81, - "Reset_J": 0x52ead, - "Reset_K": 0x52ed9, - "Reset_L": 0x52f05, - "Reset_M": 0x52f31, - "Reset_N": 0x52f5d, - "Reset_O": 0x52f89, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_0_ITEM": 0x50d8b, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_1_ITEM": 0x50d99, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_2_ITEM": 0x50da7, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_3_ITEM": 0x50db5, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_4_ITEM": 0x50dc3, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_5_ITEM": 0x50dd1, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_6_ITEM": 0x50ddf, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_7_ITEM": 0x50ded, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_8_ITEM": 0x50dfb, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_9_ITEM": 0x50e09, + "Starter2_B": 0x51026, + "Starter3_B": 0x51028, + "Starter1_B": 0x5102a, + "Starter2_A": 0x51145, + "Starter3_A": 0x51147, + "Starter1_A": 0x51149, + "Option_Route23_Badges": 0x51296, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_0_ITEM": 0x513ac, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_1_ITEM": 0x513ba, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_2_ITEM": 0x513c8, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_3_ITEM": 0x513d6, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_4_ITEM": 0x513e4, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_5_ITEM": 0x513f2, + "Event_Nugget_Bridge": 0x51409, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_0_ITEM": 0x51591, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_1_ITEM": 0x5159f, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_2_ITEM": 0x515ad, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_3_ITEM": 0x515bb, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_4_ITEM": 0x515c9, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_5_ITEM": 0x515d7, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_6_ITEM": 0x515e5, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_7_ITEM": 0x515f3, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_8_ITEM": 0x51601, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0_ITEM": 0x5179a, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1_ITEM": 0x517a8, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2_ITEM": 0x517b6, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3_ITEM": 0x517c4, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4_ITEM": 0x517d2, + "Trainersanity_EVENT_BEAT_MOLTRES_ITEM": 0x517e0, + "Warps_VictoryRoad2F": 0x5187d, + "Static_Encounter_Moltres": 0x518c7, + "Missable_Victory_Road_2F_Item_1": 0x518cf, + "Missable_Victory_Road_2F_Item_2": 0x518d6, + "Missable_Victory_Road_2F_Item_3": 0x518dd, + "Missable_Victory_Road_2F_Item_4": 0x518e4, + "Warps_MtMoonB1F": 0x519b5, + "Starter2_L": 0x51c13, + "Starter3_L": 0x51c1b, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_0_ITEM": 0x51ccc, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_1_ITEM": 0x51cda, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_2_ITEM": 0x51ce8, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_3_ITEM": 0x51cf6, + "Gift_Lapras": 0x51d17, + "Event_SKC7F": 0x51da2, + "Warps_SilphCo7F": 0x51e71, + "Missable_Silph_Co_7F_Item_1": 0x51ecd, + "Missable_Silph_Co_7F_Item_2": 0x51ed4, + "Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM": 0x51ffa, + "Warps_PokemonMansion2F": 0x5206d, + "Missable_Pokemon_Mansion_2F_Item": 0x5208b, + "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_0_ITEM": 0x5223b, + "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_1_ITEM": 0x52249, + "Warps_PokemonMansion3F": 0x52286, + "Missable_Pokemon_Mansion_3F_Item_1": 0x522a8, + "Missable_Pokemon_Mansion_3F_Item_2": 0x522af, + "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_0_ITEM": 0x523f1, + "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_1_ITEM": 0x523ff, + "Warps_PokemonMansionB1F": 0x5243c, + "Missable_Pokemon_Mansion_B1F_Item_1": 0x52456, + "Missable_Pokemon_Mansion_B1F_Item_2": 0x5245d, + "Missable_Pokemon_Mansion_B1F_Item_3": 0x52464, + "Missable_Pokemon_Mansion_B1F_Item_4": 0x5246b, + "Missable_Pokemon_Mansion_B1F_Item_5": 0x52478, + "Option_Safari_Zone_Battle_Type": 0x5258d, + "Prize_Mon_A2": 0x52817, + "Prize_Mon_B2": 0x52818, + "Prize_Mon_C2": 0x52819, + "Prize_Mon_D2": 0x52822, + "Prize_Mon_E2": 0x52823, + "Prize_Mon_F2": 0x52824, + "Prize_Item_A": 0x5282d, + "Prize_Item_B": 0x5282e, + "Prize_Item_C": 0x5282f, + "Prize_Mon_A": 0x52964, + "Prize_Mon_B": 0x52966, + "Prize_Mon_C": 0x52968, + "Prize_Mon_D": 0x5296a, + "Prize_Mon_E": 0x5296c, + "Prize_Mon_F": 0x5296e, + "Start_Inventory": 0x52aa3, + "Map_Fly_Location": 0x52c9d, + "Reset_A": 0x52d49, + "Reset_B": 0x52d75, + "Reset_C": 0x52da1, + "Reset_D": 0x52dcd, + "Reset_E": 0x52df9, + "Reset_F": 0x52e25, + "Reset_G": 0x52e51, + "Reset_H": 0x52e7d, + "Reset_I": 0x52ea9, + "Reset_J": 0x52ed5, + "Reset_K": 0x52f01, + "Reset_L": 0x52f2d, + "Reset_M": 0x52f59, + "Reset_N": 0x52f85, + "Reset_O": 0x52fb1, "Warps_Route2": 0x54026, "Missable_Route_2_Item_1": 0x5404a, "Missable_Route_2_Item_2": 0x54051, @@ -1539,16 +1539,18 @@ rom_addresses = { "Event_SKC11F": 0x623bd, "Warps_SilphCo11F": 0x62446, "Ghost_Battle4": 0x708e1, - "Trade_Terry": 0x71b77, - "Trade_Marcel": 0x71b85, - "Trade_Sailor": 0x71ba1, - "Trade_Dux": 0x71baf, - "Trade_Marc": 0x71bbd, - "Trade_Lola": 0x71bcb, - "Trade_Doris": 0x71bd9, - "Trade_Crinkles": 0x71be7, - "Trade_Spot": 0x71bf5, - "Mon_Palettes": 0x725d3, + "Town_Map_Order": 0x70f0f, + "Town_Map_Coords": 0x71381, + "Trade_Terry": 0x71b7a, + "Trade_Marcel": 0x71b88, + "Trade_Sailor": 0x71ba4, + "Trade_Dux": 0x71bb2, + "Trade_Marc": 0x71bc0, + "Trade_Lola": 0x71bce, + "Trade_Doris": 0x71bdc, + "Trade_Crinkles": 0x71bea, + "Trade_Spot": 0x71bf8, + "Mon_Palettes": 0x725d6, "Badge_Viridian_Gym": 0x749d9, "Event_Viridian_Gym": 0x749ed, "Trainersanity_EVENT_BEAT_VIRIDIAN_GYM_TRAINER_0_ITEM": 0x74a48,