139 lines
8.3 KiB
Python
139 lines
8.3 KiB
Python
|
from BaseClasses import CollectionState
|
||
|
from .locations import level_name_list, level_list
|
||
|
|
||
|
|
||
|
def level_scaling(multiworld):
|
||
|
state = CollectionState(multiworld)
|
||
|
locations = set(multiworld.get_filled_locations())
|
||
|
spheres = []
|
||
|
|
||
|
while locations:
|
||
|
sphere = set()
|
||
|
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
|
||
|
if multiworld.level_scaling[world.player] != "by_spheres_and_distance":
|
||
|
continue
|
||
|
regions = {multiworld.get_region("Menu", world.player)}
|
||
|
checked_regions = set()
|
||
|
distance = 0
|
||
|
while regions:
|
||
|
next_regions = set()
|
||
|
for region in regions:
|
||
|
if not getattr(region, "distance"):
|
||
|
region.distance = distance
|
||
|
next_regions.update({e.connected_region for e in region.exits if e.connected_region not in
|
||
|
checked_regions and e.access_rule(state)})
|
||
|
checked_regions.update(regions)
|
||
|
regions = next_regions
|
||
|
distance += 1
|
||
|
|
||
|
distances = {}
|
||
|
|
||
|
for location in locations:
|
||
|
def reachable():
|
||
|
if location.can_reach(state):
|
||
|
return True
|
||
|
if location.parent_region.name == "Fossil" and state.can_reach("Mt Moon B2F", "Region",
|
||
|
location.player):
|
||
|
# We want areas that are accessible earlier to have lower levels. If an important item is at a
|
||
|
# fossil location, it may not be in logic until much later, despite your ability to potentially
|
||
|
# reach them earlier. We treat them both as reachable right away for this purpose
|
||
|
return True
|
||
|
if (location.name == "Route 25 - Item" and state.can_reach("Route 25", "Region", location.player)
|
||
|
and multiworld.blind_trainers[location.player].value < 100):
|
||
|
# Assume they will take their one chance to get the trainer to walk out of the way to reach
|
||
|
# the item behind them
|
||
|
return True
|
||
|
if (("Rock Tunnel 1F - Wild Pokemon" in location.name
|
||
|
and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state)
|
||
|
for e in ['Rock Tunnel 1F-NE to Route 10-N',
|
||
|
'Rock Tunnel 1F-NE to Rock Tunnel B1F-E',
|
||
|
'Rock Tunnel 1F-NW to Rock Tunnel B1F-E',
|
||
|
'Rock Tunnel 1F-NW to Rock Tunnel B1F-W',
|
||
|
'Rock Tunnel 1F-S to Route 10-S',
|
||
|
'Rock Tunnel 1F-S to Rock Tunnel B1F-W']])) or
|
||
|
("Rock Tunnel B1F - Wild Pokemon" in location.name and
|
||
|
any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state)
|
||
|
for e in ['Rock Tunnel B1F-E to Rock Tunnel 1F-NE',
|
||
|
'Rock Tunnel B1F-E to Rock Tunnel 1F-NW',
|
||
|
'Rock Tunnel B1F-W to Rock Tunnel 1F-NW',
|
||
|
'Rock Tunnel B1F-W to Rock Tunnel 1F-S']]))):
|
||
|
# Even if checks in Rock Tunnel are out of logic due to lack of Flash, it is very easy to
|
||
|
# wander in the dark and encounter wild Pokémon, even unintentionally while attempting to
|
||
|
# leave the way you entered. We'll count the wild Pokémon as reachable as soon as the Rock
|
||
|
# Tunnel is reachable, so you don't have an opportunity to catch high level Pokémon early.
|
||
|
# If the connections between Rock Tunnel floors are vanilla, you will still potentially
|
||
|
# have very high level Pokémon in B1F if you reach it out of logic, but that would always
|
||
|
# mean intentionally breaking the logic you picked in your yaml, and may require
|
||
|
# defeating trainers in 1F that would be at the higher levels.
|
||
|
return True
|
||
|
return False
|
||
|
if reachable():
|
||
|
sphere.add(location)
|
||
|
parent_region = location.parent_region
|
||
|
if getattr(parent_region, "distance", None) is None:
|
||
|
distance = 0
|
||
|
else:
|
||
|
distance = parent_region.distance
|
||
|
if distance not in distances:
|
||
|
distances[distance] = {location}
|
||
|
else:
|
||
|
distances[distance].add(location)
|
||
|
|
||
|
if sphere:
|
||
|
for distance in sorted(distances.keys()):
|
||
|
spheres.append(distances[distance])
|
||
|
locations -= distances[distance]
|
||
|
else:
|
||
|
spheres.append(locations)
|
||
|
break
|
||
|
|
||
|
for location in sphere:
|
||
|
if not location.item:
|
||
|
continue
|
||
|
if (location.item.game == "Pokemon Red and Blue" and (location.item.name.startswith("Missable ") or
|
||
|
location.item.name.startswith("Static ")) and location.name !=
|
||
|
"Pokemon Tower 6F - Restless Soul"):
|
||
|
# Normally, missable Pokemon (starters, the dojo rewards) are not considered in logic static Pokemon
|
||
|
# are not considered for moves or evolutions, as you could release them and potentially soft lock
|
||
|
# the game. However, for level scaling purposes, we will treat them as not missable or static.
|
||
|
# We would not want someone playing a minimal accessibility Dexsanity game to get what would be
|
||
|
# technically an "out of logic" Mansion Key from selecting Bulbasaur at the beginning of the game
|
||
|
# and end up in the Mansion early and encountering level 67 Pokémon
|
||
|
state.collect(multiworld.worlds[location.item.player].create_item(
|
||
|
location.item.name.split("Missable ")[-1].split("Static ")[-1]), True, location)
|
||
|
else:
|
||
|
state.collect(location.item, True, location)
|
||
|
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
|
||
|
if multiworld.level_scaling[world.player] == "off":
|
||
|
continue
|
||
|
level_list_copy = level_list.copy()
|
||
|
for sphere in spheres:
|
||
|
sphere_objects = {loc.name: loc for loc in sphere if loc.player == world.player
|
||
|
and (loc.type == "Wild Encounter" or "Pokemon" in loc.type) and loc.level is not None}
|
||
|
party_objects = [loc for loc in sphere if loc.player == world.player and loc.type == "Trainer Parties"]
|
||
|
for parties in party_objects:
|
||
|
for party in parties.party_data:
|
||
|
if isinstance(party["level"], int):
|
||
|
sphere_objects[(party["party_address"][0] if isinstance(party["party_address"], list)
|
||
|
else party["party_address"], 0)] = parties
|
||
|
else:
|
||
|
for i, level in enumerate(party["level"]):
|
||
|
sphere_objects[(party["party_address"][0] if isinstance(party["party_address"], list)
|
||
|
else party["party_address"], i)] = parties
|
||
|
ordered_sphere_objects = list(sphere_objects.keys())
|
||
|
ordered_sphere_objects.sort(key=lambda obj: level_name_list.index(obj))
|
||
|
for object in ordered_sphere_objects:
|
||
|
if sphere_objects[object].type == "Trainer Parties":
|
||
|
for party in sphere_objects[object].party_data:
|
||
|
if (isinstance(party["party_address"], list) and party["party_address"][0] == object[0]) or party["party_address"] == object[0]:
|
||
|
if isinstance(party["level"], int):
|
||
|
party["level"] = level_list_copy.pop(0)
|
||
|
else:
|
||
|
party["level"][object[1]] = level_list_copy.pop(0)
|
||
|
break
|
||
|
else:
|
||
|
sphere_objects[object].level = level_list_copy.pop(0)
|
||
|
for world in multiworld.get_game_worlds("Pokemon Red and Blue"):
|
||
|
world.finished_level_scaling.set()
|
||
|
|