Pokemon R/B: The Big Door Shuffle Update (#2861)

- Perhaps most critically, adds the ability for the door shuffle code to catch door shuffle exceptions and try again. Will try up to 10 times. Should mean Door Shuffle does not need to be disallowed in the big async🤞
- Door Shuffle code has been made drastically faster by searching for the first dead end instead of sorting the whole list of entrances by whether they are dead ends.
- Renames Full to Interiors, and adds a new Full door shuffle that shuffles interior-to-interior doors separately from exterior-to-interior doors.
- Adds a new Decoupled door shuffle.
- Warp Tile Shuffle now has 3 separate options, Vanilla, Shuffle, and Mixed. Shuffle shuffles the warp tiles among themselves, Mixed mixes them into the Door Shuffle pool.
- Safari Zone connections are now shuffled on Full, Insanity, and Decoupled.
- On Simple Door Shuffle, the Town Map is updated to show the new dungeon locations. The Town Map has been updated to show the locations of dungeons that previously were not shown unless you opened the map within them, and the Sea Cottage has been removed from it.
- Adds Auto Level Scaling that chooses the level scaling mode based on the Door Shuffle choice.
- Fixes issues with Flash and Fly move interventions (where it ensures an available Pokémon that can learn it is reachable depending on settings).
- Fixes a possible generation crash with type chart randomization.
- Should fix an issue where `stage_fill_hook` was able to remove the wrong item from the item pool resulting in a duplicated item reference existing.
- Adds a stage_post_fill function which searches for Pokémon in order of spheres, setting all but the first advancement Pokémon event found to `useful` so that spoiler playthrough calculation skips them. In a solo game gen test, this cut gen time from 15 seconds to 10 seconds with same seed number. Difference is likely to be much more massive in larger multiworlds.
This commit is contained in:
Alchav 2024-03-05 17:01:45 -05:00 committed by GitHub
parent bfa9e7da00
commit a5a1494a96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 621 additions and 416 deletions

View File

@ -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

View File

@ -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?

View File

@ -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()

View File

@ -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),

View File

@ -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):

View File

@ -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)

View File

@ -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",

View File

@ -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,