 db5d9fbf70
			
		
	
	db5d9fbf70
	
	
	
		
			
			* Quiz updates * Enable Partial Trainersanity * Losable Key Items Still Count * New options api * Type Chart Seed * Continue switching to new options API * Level Scaling and Quiz fixes * Level Scaling and Quiz fixes * Clarify that palettes are only for Super Gameboy * Type chart seed groups use one random players' options * remove goal option again * Text updates * Trainersanity Trainers ignore Blind Trainers setting * Re-order simple connecting interiors so that directions are preserved when possible * Dexsanity exact number * Year update * Dexsanity Doc update * revert accidental file deletion * Fixes * Add world parameter to logic calls * restore correct seeded random object * missing world.options changes * Trainersanity table bug fix * delete entrances as well as exits when restarting door shuffle * Do not collect route 25 item for level scaling if trainer is trainersanity * world.options in level_scaling.py * Update worlds/pokemon_rb/level_scaling.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update worlds/pokemon_rb/encounters.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update worlds/pokemon_rb/encounters.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * world -> multiworld * Fix Cerulean Cave Hidden Item Center Rocks region * Fix Cerulean Cave Hidden Item Center Rocks region for real * Remove "self-locking" rules * Update worlds/pokemon_rb/regions.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Fossil events * Update worlds/pokemon_rb/level_scaling.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: alchav <alchav@jalchavware.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
		
			
				
	
	
		
			140 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			8.6 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 (world.options.level_scaling != "by_spheres_and_distance"
 | |
|                     and (world.options.level_scaling != "auto"
 | |
|                          or world.options.door_shuffle in ("off", "simple"))):
 | |
|                 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.worlds[location.player].options.blind_trainers.value < 100
 | |
|                         and "Route 25 - Jr. Trainer M" not in multiworld.regions.location_cache[location.player]):
 | |
|                     # 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 1 to Route 10-N',
 | |
|                                            'Rock Tunnel 1F-NE 2 to Rock Tunnel B1F-E 1',
 | |
|                                            'Rock Tunnel 1F-NW 1 to Rock Tunnel B1F-E 2',
 | |
|                                            'Rock Tunnel 1F-NW 2 to Rock Tunnel B1F-W 1',
 | |
|                                            'Rock Tunnel 1F-S 1 to Route 10-S',
 | |
|                                            'Rock Tunnel 1F-S 2 to Rock Tunnel B1F-W 2']])) 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 1 to Rock Tunnel 1F-NE 2',
 | |
|                                        'Rock Tunnel B1F-E 2 to Rock Tunnel 1F-NW 1',
 | |
|                                        'Rock Tunnel B1F-W 1 to Rock Tunnel 1F-NW 2',
 | |
|                                        'Rock Tunnel B1F-W 2 to Rock Tunnel 1F-S 2']]))):
 | |
|                     # 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, and 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 world.options.level_scaling == "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() |