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