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
10 changed files with 621 additions and 416 deletions

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)