mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user