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