| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | """
 | 
					
						
							|  |  |  | Functions related to pokemon species and moves | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | import functools | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | from typing import TYPE_CHECKING, Dict, List, Set, Optional, Tuple | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-08 08:57:16 -08:00
										 |  |  | from .data import (NUM_REAL_SPECIES, OUT_OF_LOGIC_MAPS, EncounterType, EncounterTableData, LearnsetMove, SpeciesData, | 
					
						
							|  |  |  |                    MapData, data) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | from .options import (Goal, HmCompatibility, LevelUpMoves, RandomizeAbilities, RandomizeLegendaryEncounters, | 
					
						
							|  |  |  |                       RandomizeMiscPokemon, RandomizeStarters, RandomizeTypes, RandomizeWildPokemon, | 
					
						
							|  |  |  |                       TmTutorCompatibility) | 
					
						
							|  |  |  | from .util import bool_array_to_int, get_easter_egg, int_to_bool_array | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from random import Random | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     from . import PokemonEmeraldWorld | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | _DAMAGING_MOVES = frozenset({ | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |       1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  13, | 
					
						
							|  |  |  |      16,  17,  20,  21,  22,  23,  24,  25,  26,  27,  29,  30, | 
					
						
							|  |  |  |      31,  33,  34,  35,  36,  37,  38,  40,  41,  42,  44,  51, | 
					
						
							|  |  |  |      52,  53,  55,  56,  58,  59,  60,  61,  62,  63,  64,  65, | 
					
						
							|  |  |  |      66,  67,  69,  71,  72,  75,  76,  80,  82,  83,  84,  85, | 
					
						
							|  |  |  |      87,  88,  89,  91,  93,  94,  98,  99, 101, 121, 122, 123, | 
					
						
							|  |  |  |     124, 125, 126, 128, 129, 130, 131, 132, 136, 140, 141, 143, | 
					
						
							|  |  |  |     145, 146, 149, 152, 154, 155, 157, 158, 161, 162, 163, 167, | 
					
						
							|  |  |  |     168, 172, 175, 177, 179, 181, 183, 185, 188, 189, 190, 192, | 
					
						
							|  |  |  |     196, 198, 200, 202, 205, 209, 210, 211, 216, 217, 218, 221, | 
					
						
							|  |  |  |     222, 223, 224, 225, 228, 229, 231, 232, 233, 237, 238, 239, | 
					
						
							|  |  |  |     242, 245, 246, 247, 248, 250, 251, 253, 257, 263, 265, 267, | 
					
						
							|  |  |  |     276, 279, 280, 282, 284, 290, 292, 295, 296, 299, 301, 302, | 
					
						
							|  |  |  |     304, 305, 306, 307, 308, 309, 310, 311, 314, 315, 317, 318, | 
					
						
							|  |  |  |     323, 324, 325, 326, 327, 328, 330, 331, 332, 333, 337, 338, | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     340, 341, 342, 343, 344, 345, 348, 350, 351, 352, 353, 354, | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | }) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | """IDs for moves that safely deal direct damage, for avoiding putting the
 | 
					
						
							|  |  |  | player in a situation where they can only use status moves, or are forced | 
					
						
							|  |  |  | to faint themselves, or something of that nature."""
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | _MOVE_TYPES = [ | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |      0,  0,  1,  0,  0,  0,  0, 10, 15, 13,  0,  0,  0,  0,  0, | 
					
						
							|  |  |  |      0,  2,  2,  0,  2,  0,  0, 12,  0,  1,  0,  1,  1,  4,  0, | 
					
						
							|  |  |  |      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  3,  6,  6,  0, 17, | 
					
						
							|  |  |  |      0,  0,  0,  0,  0,  0,  3, 10, 10, 15, 11, 11, 11, 15, 15, | 
					
						
							|  |  |  |     14, 11, 15,  0,  2,  2,  1,  1,  1,  1,  0, 12, 12, 12,  0, | 
					
						
							|  |  |  |     12, 12,  3, 12, 12, 12,  6, 16, 10, 13, 13, 13, 13,  5,  4, | 
					
						
							|  |  |  |      4,  4,  3, 14, 14, 14, 14, 14,  0,  0, 14,  7,  0,  0,  0, | 
					
						
							|  |  |  |      0,  0,  0,  0,  7, 11,  0, 14, 14, 15, 14,  0,  0,  0,  2, | 
					
						
							|  |  |  |      0,  0,  7,  3,  3,  4, 10, 11, 11,  0,  0,  0,  0, 14, 14, | 
					
						
							|  |  |  |      0,  1,  0, 14,  3,  0,  6,  0,  2,  0, 11,  0, 12,  0, 14, | 
					
						
							|  |  |  |      0,  3, 11,  0,  0,  4, 14,  5,  0,  0,  0,  0,  0,  0,  0, | 
					
						
							|  |  |  |      0,  0,  1, 17,  6,  0,  7, 10,  0,  9,  0,  0,  2, 12,  1, | 
					
						
							|  |  |  |      7, 15,  0,  1,  0, 17,  0,  0,  3,  4, 11,  4, 13,  0,  7, | 
					
						
							|  |  |  |      0, 15,  1,  4,  0, 16,  5, 12,  0,  0,  5,  0,  0,  0, 13, | 
					
						
							|  |  |  |      6,  8,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10,  4,  1,  6, | 
					
						
							|  |  |  |     16,  0,  0, 17,  0,  0,  8,  8,  1,  0, 12,  0,  0,  1, 16, | 
					
						
							|  |  |  |     11, 10, 17, 14,  0,  0,  5,  7, 14,  1, 11, 17,  0,  0,  0, | 
					
						
							|  |  |  |      0,  0, 10, 15, 17, 17, 10, 17,  0,  1,  0,  0,  0, 13, 17, | 
					
						
							|  |  |  |      0, 14, 14,  0,  0, 12,  1, 14,  0,  1,  1,  0, 17,  0, 10, | 
					
						
							|  |  |  |     14, 14,  0,  7, 17,  0, 11,  1,  0,  6, 14, 14,  2,  0, 10, | 
					
						
							|  |  |  |      4, 15, 12,  0,  0,  3,  0, 10, 11,  8,  7,  0, 12, 17,  2, | 
					
						
							|  |  |  |     10,  0,  5,  6,  8, 12,  0, 14, 11,  6,  7, 14,  1,  4, 15, | 
					
						
							|  |  |  |     11, 12,  2, 15,  8,  0,  0, 16, 12,  1,  2,  4,  3,  0, 13, | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     12, 11, 14, 12, 16,  5, 13, 11,  8, 14, | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | ] | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | """Maps move ids to the type of that move""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _MOVES_BY_TYPE: Dict[int, List[int]] = {} | 
					
						
							|  |  |  | """Categorizes move ids by their type""" | 
					
						
							|  |  |  | for move, type in enumerate(_MOVE_TYPES): | 
					
						
							|  |  |  |     _MOVES_BY_TYPE.setdefault(type, []).append(move) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | HM_MOVES = frozenset({ | 
					
						
							|  |  |  |     data.constants["MOVE_CUT"], | 
					
						
							|  |  |  |     data.constants["MOVE_FLY"], | 
					
						
							|  |  |  |     data.constants["MOVE_SURF"], | 
					
						
							|  |  |  |     data.constants["MOVE_STRENGTH"], | 
					
						
							|  |  |  |     data.constants["MOVE_FLASH"], | 
					
						
							|  |  |  |     data.constants["MOVE_ROCK_SMASH"], | 
					
						
							|  |  |  |     data.constants["MOVE_WATERFALL"], | 
					
						
							|  |  |  |     data.constants["MOVE_DIVE"], | 
					
						
							|  |  |  | }) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | _MOVE_BLACKLIST = frozenset({ | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     0,    # MOVE_NONE | 
					
						
							|  |  |  |     165,  # Struggle | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | } | HM_MOVES) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | @functools.lru_cache(maxsize=386) | 
					
						
							|  |  |  | def get_species_id_by_label(label: str) -> int: | 
					
						
							|  |  |  |     return next(species.species_id for species in data.species.values() if species.label == label) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_random_type(random: "Random") -> int: | 
					
						
							|  |  |  |     picked_type = random.randrange(0, 18) | 
					
						
							|  |  |  |     while picked_type == 9:  # Don't pick the ??? type | 
					
						
							|  |  |  |         picked_type = random.randrange(0, 18) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return picked_type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_random_move( | 
					
						
							|  |  |  |         random: "Random", | 
					
						
							|  |  |  |         blacklist: Optional[Set[int]] = None, | 
					
						
							|  |  |  |         type_bias: int = 0, | 
					
						
							|  |  |  |         normal_bias: int = 0, | 
					
						
							|  |  |  |         type_target: Optional[Tuple[int, int]] = None) -> int: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     expanded_blacklist = _MOVE_BLACKLIST | (blacklist if blacklist is not None else set()) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     bias = random.random() * 100 | 
					
						
							|  |  |  |     if bias < type_bias: | 
					
						
							|  |  |  |         pass  # Keep type_target unchanged | 
					
						
							|  |  |  |     elif bias < type_bias + ((100 - type_bias) * (normal_bias / 100)): | 
					
						
							|  |  |  |         type_target = (0, 0) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         type_target = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     chosen_move = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # The blacklist is relatively small, so if we don't need to restrict | 
					
						
							|  |  |  |     # ourselves to any particular types, it's usually much faster to pick | 
					
						
							|  |  |  |     # a random number and hope it works. Limit this to 5 tries in case the | 
					
						
							|  |  |  |     # blacklist is actually significant enough to make this unlikely to work. | 
					
						
							|  |  |  |     if type_target is None: | 
					
						
							|  |  |  |         remaining_attempts = 5 | 
					
						
							|  |  |  |         while remaining_attempts > 0: | 
					
						
							|  |  |  |             remaining_attempts -= 1 | 
					
						
							|  |  |  |             chosen_move = random.randrange(0, data.constants["MOVES_COUNT"]) | 
					
						
							|  |  |  |             if chosen_move not in expanded_blacklist: | 
					
						
							|  |  |  |                 return chosen_move | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             chosen_move = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # We're either matching types or failed to pick a move above | 
					
						
							|  |  |  |     if type_target is None: | 
					
						
							| 
									
										
										
										
											2023-11-22 10:21:15 -08:00
										 |  |  |         possible_moves = [i for i in range(data.constants["MOVES_COUNT"]) if i not in expanded_blacklist] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         possible_moves = [move for move in _MOVES_BY_TYPE[type_target[0]] if move not in expanded_blacklist] + \ | 
					
						
							|  |  |  |                          [move for move in _MOVES_BY_TYPE[type_target[1]] if move not in expanded_blacklist] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if len(possible_moves) == 0: | 
					
						
							|  |  |  |         return get_random_move(random, None, type_bias, normal_bias, type_target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return random.choice(possible_moves) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_random_damaging_move(random: "Random", blacklist: Optional[Set[int]] = None) -> int: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     expanded_blacklist = _MOVE_BLACKLIST | (blacklist if blacklist is not None else set()) | 
					
						
							|  |  |  |     move_options = list(_DAMAGING_MOVES) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     move = random.choice(move_options) | 
					
						
							|  |  |  |     while move in expanded_blacklist: | 
					
						
							|  |  |  |         move = random.choice(move_options) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return move | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def filter_species_by_nearby_bst(species: List[SpeciesData], target_bst: int) -> List[SpeciesData]: | 
					
						
							|  |  |  |     # Sort by difference in bst, then chop off the tail of the list that's more than | 
					
						
							|  |  |  |     # 10% different. If that leaves the list empty, increase threshold to 20%, then 30%, etc. | 
					
						
							|  |  |  |     species = sorted(species, key=lambda species: abs(sum(species.base_stats) - target_bst)) | 
					
						
							|  |  |  |     cutoff_index = 0 | 
					
						
							|  |  |  |     max_percent_different = 10 | 
					
						
							|  |  |  |     while cutoff_index == 0 and max_percent_different < 10000: | 
					
						
							|  |  |  |         while cutoff_index < len(species) and abs(sum(species[cutoff_index].base_stats) - target_bst) < target_bst * (max_percent_different / 100): | 
					
						
							|  |  |  |             cutoff_index += 1 | 
					
						
							|  |  |  |         max_percent_different += 10 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return species[:cutoff_index + 1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def randomize_types(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.types == RandomizeTypes.option_shuffle: | 
					
						
							|  |  |  |         type_map = list(range(18)) | 
					
						
							|  |  |  |         world.random.shuffle(type_map) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # We never want to map to the ??? type, so swap whatever index maps to ??? with ??? | 
					
						
							|  |  |  |         # which forces ??? to always map to itself. There are no pokemon which have the ??? type | 
					
						
							|  |  |  |         mystery_type_index = type_map.index(9) | 
					
						
							|  |  |  |         type_map[mystery_type_index], type_map[9] = type_map[9], type_map[mystery_type_index] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for species in world.modified_species.values(): | 
					
						
							|  |  |  |             species.types = (type_map[species.types[0]], type_map[species.types[1]]) | 
					
						
							|  |  |  |     elif world.options.types == RandomizeTypes.option_completely_random: | 
					
						
							|  |  |  |         for species in world.modified_species.values(): | 
					
						
							|  |  |  |             new_type_1 = get_random_type(world.random) | 
					
						
							|  |  |  |             new_type_2 = new_type_1 | 
					
						
							|  |  |  |             if species.types[0] != species.types[1]: | 
					
						
							|  |  |  |                 while new_type_1 == new_type_2: | 
					
						
							|  |  |  |                     new_type_2 = get_random_type(world.random) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             species.types = (new_type_1, new_type_2) | 
					
						
							|  |  |  |     elif world.options.types == RandomizeTypes.option_follow_evolutions: | 
					
						
							|  |  |  |         already_modified: Set[int] = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Similar to follow evolutions for abilities, but only needs to loop through once. | 
					
						
							|  |  |  |         # For every pokemon without a pre-evolution, generates a random mapping from old types to new types | 
					
						
							|  |  |  |         # and then walks through the evolution tree applying that map. This means that evolutions that share | 
					
						
							|  |  |  |         # types will have those types mapped to the same new types, and evolutions with new or diverging types | 
					
						
							|  |  |  |         # will still have new or diverging types. | 
					
						
							|  |  |  |         # Consider: | 
					
						
							|  |  |  |         # - Charmeleon (Fire/Fire) -> Charizard (Fire/Flying) | 
					
						
							|  |  |  |         # - Onyx (Rock/Ground) -> Steelix (Steel/Ground) | 
					
						
							|  |  |  |         # - Nincada (Bug/Ground) -> Ninjask (Bug/Flying) && Shedinja (Bug/Ghost) | 
					
						
							|  |  |  |         # - Azurill (Normal/Normal) -> Marill (Water/Water) | 
					
						
							|  |  |  |         for species in world.modified_species.values(): | 
					
						
							|  |  |  |             if species.species_id in already_modified: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if species.pre_evolution is not None and species.pre_evolution not in already_modified: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             type_map = list(range(18)) | 
					
						
							|  |  |  |             world.random.shuffle(type_map) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # We never want to map to the ??? type, so swap whatever index maps to ??? with ??? | 
					
						
							|  |  |  |             # which forces ??? to always map to itself. There are no pokemon which have the ??? type | 
					
						
							|  |  |  |             mystery_type_index = type_map.index(9) | 
					
						
							|  |  |  |             type_map[mystery_type_index], type_map[9] = type_map[9], type_map[mystery_type_index] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             evolutions = [species] | 
					
						
							|  |  |  |             while len(evolutions) > 0: | 
					
						
							|  |  |  |                 evolution = evolutions.pop() | 
					
						
							|  |  |  |                 evolution.types = (type_map[evolution.types[0]], type_map[evolution.types[1]]) | 
					
						
							|  |  |  |                 already_modified.add(evolution.species_id) | 
					
						
							|  |  |  |                 evolutions += [world.modified_species[evo.species_id] for evo in evolution.evolutions] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-08 08:57:16 -08:00
										 |  |  | _encounter_subcategory_ranges: Dict[EncounterType, Dict[range, Optional[str]]] = { | 
					
						
							|  |  |  |     EncounterType.LAND: {range(0, 12): None}, | 
					
						
							|  |  |  |     EncounterType.WATER: {range(0, 5): None}, | 
					
						
							|  |  |  |     EncounterType.FISHING: {range(0, 2): "OLD_ROD", range(2, 5): "GOOD_ROD", range(5, 10): "SUPER_ROD"}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _rename_wild_events(world: "PokemonEmeraldWorld", map_data: MapData, new_slots: List[int], encounter_type: EncounterType): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Renames the events that correspond to wild encounters to reflect the new species there after randomization | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     for i, new_species_id in enumerate(new_slots): | 
					
						
							|  |  |  |         # Get the subcategory for rods | 
					
						
							|  |  |  |         subcategory_range, subcategory_name = next( | 
					
						
							|  |  |  |             (r, sc) | 
					
						
							|  |  |  |             for r, sc in _encounter_subcategory_ranges[encounter_type].items() | 
					
						
							|  |  |  |             if i in r | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         subcategory_species = [] | 
					
						
							|  |  |  |         for k in subcategory_range: | 
					
						
							|  |  |  |             if new_slots[k] not in subcategory_species: | 
					
						
							|  |  |  |                 subcategory_species.append(new_slots[k]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create the name of the location that corresponds to this encounter slot | 
					
						
							|  |  |  |         # Fishing locations include the rod name | 
					
						
							|  |  |  |         subcategory_str = "" if subcategory_name is None else "_" + subcategory_name | 
					
						
							|  |  |  |         encounter_location_index = subcategory_species.index(new_species_id) + 1 | 
					
						
							|  |  |  |         encounter_location_name = f"{map_data.name}_{encounter_type.value}_ENCOUNTERS{subcategory_str}_{encounter_location_index}" | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             # Get the corresponding location and change the event name to reflect the new species | 
					
						
							|  |  |  |             slot_location = world.multiworld.get_location(encounter_location_name, world.player) | 
					
						
							|  |  |  |             slot_location.item.name = f"CATCH_{data.species[new_species_id].name}" | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             pass  # Map probably isn't included; should be careful here about bad encounter location names | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | def randomize_wild_encounters(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.wild_pokemon == RandomizeWildPokemon.option_vanilla: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     from collections import defaultdict | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     should_match_bst = world.options.wild_pokemon in { | 
					
						
							|  |  |  |         RandomizeWildPokemon.option_match_base_stats, | 
					
						
							|  |  |  |         RandomizeWildPokemon.option_match_base_stats_and_type, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     should_match_type = world.options.wild_pokemon in { | 
					
						
							|  |  |  |         RandomizeWildPokemon.option_match_type, | 
					
						
							|  |  |  |         RandomizeWildPokemon.option_match_base_stats_and_type, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-04 13:44:38 -06:00
										 |  |  |     already_placed = set() | 
					
						
							|  |  |  |     num_placeable_species = NUM_REAL_SPECIES - len(world.blacklisted_wilds) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     priority_species = [data.constants["SPECIES_WAILORD"], data.constants["SPECIES_RELICANTH"]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Loop over map data to modify their encounter slots | 
					
						
							|  |  |  |     map_names = list(world.modified_maps.keys()) | 
					
						
							|  |  |  |     world.random.shuffle(map_names) | 
					
						
							|  |  |  |     for map_name in map_names: | 
					
						
							|  |  |  |         placed_priority_species = False | 
					
						
							|  |  |  |         map_data = world.modified_maps[map_name] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-08 08:57:16 -08:00
										 |  |  |         new_encounters: Dict[EncounterType, EncounterTableData] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for encounter_type, table in map_data.encounters.items(): | 
					
						
							|  |  |  |             # Create a map from the original species to new species | 
					
						
							|  |  |  |             # instead of just randomizing every slot. | 
					
						
							|  |  |  |             # Force area 1-to-1 mapping, in other words. | 
					
						
							|  |  |  |             species_old_to_new_map: Dict[int, int] = {} | 
					
						
							|  |  |  |             for species_id in table.slots: | 
					
						
							|  |  |  |                 if species_id not in species_old_to_new_map: | 
					
						
							|  |  |  |                     if not placed_priority_species and len(priority_species) > 0 \ | 
					
						
							|  |  |  |                             and encounter_type != EncounterType.ROCK_SMASH and map_name not in OUT_OF_LOGIC_MAPS: | 
					
						
							|  |  |  |                         new_species_id = priority_species.pop() | 
					
						
							|  |  |  |                         placed_priority_species = True | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         original_species = data.species[species_id] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # Construct progressive tiers of blacklists that can be peeled back if they | 
					
						
							|  |  |  |                         # collectively cover too much of the pokedex. A lower index in `blacklists` | 
					
						
							|  |  |  |                         # indicates a more important set of species to avoid. Entries at `0` will | 
					
						
							|  |  |  |                         # always be blacklisted. | 
					
						
							|  |  |  |                         blacklists: Dict[int, List[Set[int]]] = defaultdict(list) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # Blacklist pokemon already on this table | 
					
						
							|  |  |  |                         blacklists[0].append(set(species_old_to_new_map.values())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # If doing legendary hunt, blacklist Latios from wild encounters so | 
					
						
							|  |  |  |                         # it can be tracked as the roamer. Otherwise it may be impossible | 
					
						
							|  |  |  |                         # to tell whether a highlighted route is the roamer or a wild | 
					
						
							|  |  |  |                         # encounter. | 
					
						
							|  |  |  |                         if world.options.goal == Goal.option_legendary_hunt: | 
					
						
							|  |  |  |                             blacklists[0].append({data.constants["SPECIES_LATIOS"]}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # If dexsanity/catch 'em all mode, blacklist already placed species | 
					
						
							|  |  |  |                         # until every species has been placed once | 
					
						
							|  |  |  |                         if world.options.dexsanity and len(already_placed) < num_placeable_species: | 
					
						
							|  |  |  |                             blacklists[1].append(already_placed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # Blacklist from player options | 
					
						
							|  |  |  |                         blacklists[2].append(world.blacklisted_wilds) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         # Type matching blacklist | 
					
						
							|  |  |  |                         if should_match_type: | 
					
						
							|  |  |  |                             blacklists[3].append({ | 
					
						
							|  |  |  |                                 species.species_id | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                                 for species in world.modified_species.values() | 
					
						
							| 
									
										
										
										
											2025-03-08 08:57:16 -08:00
										 |  |  |                                 if not bool(set(species.types) & set(original_species.types)) | 
					
						
							|  |  |  |                             }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         merged_blacklist: Set[int] = set() | 
					
						
							|  |  |  |                         for max_priority in reversed(sorted(blacklists.keys())): | 
					
						
							|  |  |  |                             merged_blacklist = set() | 
					
						
							|  |  |  |                             for priority in blacklists.keys(): | 
					
						
							|  |  |  |                                 if priority <= max_priority: | 
					
						
							|  |  |  |                                     for blacklist in blacklists[priority]: | 
					
						
							|  |  |  |                                         merged_blacklist |= blacklist | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                             if len(merged_blacklist) < NUM_REAL_SPECIES: | 
					
						
							|  |  |  |                                 break | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             raise RuntimeError("This should never happen") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         candidates = [ | 
					
						
							|  |  |  |                             species | 
					
						
							|  |  |  |                             for species in world.modified_species.values() | 
					
						
							|  |  |  |                             if species.species_id not in merged_blacklist | 
					
						
							|  |  |  |                         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         if should_match_bst: | 
					
						
							|  |  |  |                             candidates = filter_species_by_nearby_bst(candidates, sum(original_species.base_stats)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         new_species_id = world.random.choice(candidates).species_id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     species_old_to_new_map[species_id] = new_species_id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if world.options.dexsanity and encounter_type != EncounterType.ROCK_SMASH \ | 
					
						
							|  |  |  |                             and map_name not in OUT_OF_LOGIC_MAPS: | 
					
						
							|  |  |  |                         already_placed.add(new_species_id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Actually create the new list of slots and encounter table | 
					
						
							|  |  |  |             new_slots: List[int] = [] | 
					
						
							|  |  |  |             for species_id in table.slots: | 
					
						
							|  |  |  |                 new_slots.append(species_old_to_new_map[species_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             new_encounters[encounter_type] = EncounterTableData(new_slots, table.address) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Rock smash encounters not used in logic, so they have no events | 
					
						
							|  |  |  |             if encounter_type != EncounterType.ROCK_SMASH: | 
					
						
							|  |  |  |                 _rename_wild_events(world, map_data, new_slots, encounter_type) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         map_data.encounters = new_encounters | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def randomize_abilities(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.abilities == RandomizeAbilities.option_vanilla: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Creating list of potential abilities | 
					
						
							|  |  |  |     ability_label_to_value = {ability.label.lower(): ability.ability_id for ability in data.abilities} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ability_blacklist_labels = {"cacophony"}  # Cacophony is defined and has a description, but no effect | 
					
						
							|  |  |  |     option_ability_blacklist = world.options.ability_blacklist.value | 
					
						
							|  |  |  |     if option_ability_blacklist is not None: | 
					
						
							|  |  |  |         ability_blacklist_labels |= {ability_label.lower() for ability_label in option_ability_blacklist} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ability_blacklist = {ability_label_to_value[label] for label in ability_blacklist_labels} | 
					
						
							|  |  |  |     ability_whitelist = [a.ability_id for a in data.abilities if a.ability_id not in ability_blacklist] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.abilities == RandomizeAbilities.option_follow_evolutions: | 
					
						
							|  |  |  |         already_modified: Set[int] = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Loops through species and only tries to modify abilities if the pokemon has no pre-evolution | 
					
						
							|  |  |  |         # or if the pre-evolution has already been modified. Then tries to modify all species that evolve | 
					
						
							|  |  |  |         # from this one which have the same abilities. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # The outer while loop only runs three times for vanilla ordering: Once for a first pass, once for | 
					
						
							|  |  |  |         # Hitmonlee/Hitmonchan, and once to verify that there's nothing left to do. | 
					
						
							|  |  |  |         while True: | 
					
						
							|  |  |  |             had_clean_pass = True | 
					
						
							|  |  |  |             for species in world.modified_species.values(): | 
					
						
							|  |  |  |                 if species.species_id in already_modified: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if species.pre_evolution is not None and species.pre_evolution not in already_modified: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 had_clean_pass = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 old_abilities = species.abilities | 
					
						
							|  |  |  |                 # 0 is the value for "no ability"; species with only 1 ability have the other set to 0 | 
					
						
							|  |  |  |                 new_abilities = ( | 
					
						
							|  |  |  |                     0 if old_abilities[0] == 0 else world.random.choice(ability_whitelist), | 
					
						
							|  |  |  |                     0 if old_abilities[1] == 0 else world.random.choice(ability_whitelist) | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # Recursively modify the abilities of anything that evolves from this pokemon | 
					
						
							|  |  |  |                 # until the evolution doesn't have a matching set of abilities | 
					
						
							|  |  |  |                 evolutions = [species] | 
					
						
							|  |  |  |                 while len(evolutions) > 0: | 
					
						
							|  |  |  |                     evolution = evolutions.pop() | 
					
						
							|  |  |  |                     if evolution.abilities == old_abilities: | 
					
						
							|  |  |  |                         evolution.abilities = new_abilities | 
					
						
							|  |  |  |                         already_modified.add(evolution.species_id) | 
					
						
							|  |  |  |                         evolutions += [ | 
					
						
							|  |  |  |                             world.modified_species[evolution.species_id] | 
					
						
							|  |  |  |                             for evolution in evolution.evolutions | 
					
						
							|  |  |  |                             if evolution.species_id not in already_modified | 
					
						
							|  |  |  |                         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if had_clean_pass: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |     else:  # Not following evolutions | 
					
						
							|  |  |  |         for species in world.modified_species.values(): | 
					
						
							|  |  |  |             old_abilities = species.abilities | 
					
						
							|  |  |  |             # 0 is the value for "no ability"; species with only 1 ability have the other set to 0 | 
					
						
							|  |  |  |             new_abilities = ( | 
					
						
							|  |  |  |                 0 if old_abilities[0] == 0 else world.random.choice(ability_whitelist), | 
					
						
							|  |  |  |                 0 if old_abilities[1] == 0 else world.random.choice(ability_whitelist) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             species.abilities = new_abilities | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def randomize_learnsets(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.level_up_moves == LevelUpMoves.option_vanilla: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     type_bias = world.options.move_match_type_bias.value | 
					
						
							|  |  |  |     normal_bias = world.options.move_normal_type_bias.value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for species in world.modified_species.values(): | 
					
						
							|  |  |  |         old_learnset = species.learnset | 
					
						
							|  |  |  |         new_learnset: List[LearnsetMove] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # All species have 4 moves at level 0. Up to 3 of them are blank spaces reserved for the | 
					
						
							|  |  |  |         # start with four moves option. This either replaces those moves or leaves it blank | 
					
						
							|  |  |  |         # and moves the cursor. | 
					
						
							|  |  |  |         cursor = 0 | 
					
						
							|  |  |  |         while old_learnset[cursor].move_id == 0: | 
					
						
							|  |  |  |             if world.options.level_up_moves == LevelUpMoves.option_start_with_four_moves: | 
					
						
							|  |  |  |                 new_move = get_random_move(world.random, | 
					
						
							|  |  |  |                                            {move.move_id for move in new_learnset} | world.blacklisted_moves, | 
					
						
							|  |  |  |                                            type_bias, normal_bias, species.types) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 new_move = 0 | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             new_learnset.append(old_learnset[cursor]._replace(move_id=new_move)) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             cursor += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # All moves from here onward are actual moves. | 
					
						
							|  |  |  |         while cursor < len(old_learnset): | 
					
						
							|  |  |  |             # Guarantees the starter has a good damaging move; i will always be <=3 when entering this loop | 
					
						
							|  |  |  |             if cursor == 3: | 
					
						
							|  |  |  |                 new_move = get_random_damaging_move(world.random, {move.move_id for move in new_learnset}) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 new_move = get_random_move(world.random, | 
					
						
							|  |  |  |                                            {move.move_id for move in new_learnset} | world.blacklisted_moves, | 
					
						
							|  |  |  |                                            type_bias, normal_bias, species.types) | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             new_learnset.append(old_learnset[cursor]._replace(move_id=new_move)) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             cursor += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         species.learnset = new_learnset | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  | def randomize_starters(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.starters == RandomizeStarters.option_vanilla: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     should_match_bst = world.options.starters in { | 
					
						
							|  |  |  |         RandomizeStarters.option_match_base_stats, | 
					
						
							|  |  |  |         RandomizeStarters.option_match_base_stats_and_type, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     should_match_type = world.options.starters in { | 
					
						
							|  |  |  |         RandomizeStarters.option_match_type, | 
					
						
							|  |  |  |         RandomizeStarters.option_match_base_stats_and_type, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     new_starters: List[SpeciesData] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     easter_egg_type, easter_egg_value = get_easter_egg(world.options.easter_egg.value) | 
					
						
							|  |  |  |     if easter_egg_type == 1: | 
					
						
							|  |  |  |         new_starters = [ | 
					
						
							|  |  |  |             world.modified_species[easter_egg_value], | 
					
						
							|  |  |  |             world.modified_species[easter_egg_value], | 
					
						
							|  |  |  |             world.modified_species[easter_egg_value] | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         for i, starter_id in enumerate(data.starters): | 
					
						
							|  |  |  |             original_starter = data.species[starter_id] | 
					
						
							|  |  |  |             type_blacklist = { | 
					
						
							|  |  |  |                 species.species_id | 
					
						
							|  |  |  |                 for species in world.modified_species.values() | 
					
						
							|  |  |  |                 if not bool(set(species.types) & set(original_starter.types)) | 
					
						
							|  |  |  |             } if should_match_type else set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             merged_blacklist = set(s.species_id for s in new_starters) | world.blacklisted_starters | type_blacklist | 
					
						
							|  |  |  |             if len(merged_blacklist) == NUM_REAL_SPECIES: | 
					
						
							|  |  |  |                 merged_blacklist = set(s.species_id for s in new_starters) | world.blacklisted_starters | 
					
						
							|  |  |  |             if len(merged_blacklist) == NUM_REAL_SPECIES: | 
					
						
							|  |  |  |                 merged_blacklist = set(s.species_id for s in new_starters) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             candidates = [ | 
					
						
							|  |  |  |                 species | 
					
						
							|  |  |  |                 for species in world.modified_species.values() | 
					
						
							|  |  |  |                 if species.species_id not in merged_blacklist | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if should_match_bst: | 
					
						
							|  |  |  |                 candidates = filter_species_by_nearby_bst(candidates, sum(original_starter.base_stats)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             new_starters.append(world.random.choice(candidates)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     world.modified_starters = ( | 
					
						
							|  |  |  |         new_starters[0].species_id, | 
					
						
							|  |  |  |         new_starters[1].species_id, | 
					
						
							|  |  |  |         new_starters[2].species_id | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Putting the unchosen starter onto the rival's team | 
					
						
							|  |  |  |     # (trainer name, index of starter in team, whether the starter is evolved) | 
					
						
							|  |  |  |     rival_teams: List[List[Tuple[str, int, bool]]] = [ | 
					
						
							|  |  |  |         [ | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_103_TREECKO", 0, False), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_RUSTBORO_TREECKO",  1, False), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_110_TREECKO", 2, True ), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_119_TREECKO", 2, True ), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_LILYCOVE_TREECKO",  3, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_103_TREECKO",     0, False), | 
					
						
							|  |  |  |             ("TRAINER_MAY_RUSTBORO_TREECKO",      1, False), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_110_TREECKO",     2, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_119_TREECKO",     2, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_LILYCOVE_TREECKO",      3, True ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         [ | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_103_TORCHIC", 0, False), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_RUSTBORO_TORCHIC",  1, False), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_110_TORCHIC", 2, True ), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_119_TORCHIC", 2, True ), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_LILYCOVE_TORCHIC",  3, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_103_TORCHIC",     0, False), | 
					
						
							|  |  |  |             ("TRAINER_MAY_RUSTBORO_TORCHIC",      1, False), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_110_TORCHIC",     2, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_119_TORCHIC",     2, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_LILYCOVE_TORCHIC",      3, True ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         [ | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_103_MUDKIP", 0, False), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_RUSTBORO_MUDKIP",  1, False), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_110_MUDKIP", 2, True ), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_ROUTE_119_MUDKIP", 2, True ), | 
					
						
							|  |  |  |             ("TRAINER_BRENDAN_LILYCOVE_MUDKIP",  3, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_103_MUDKIP",     0, False), | 
					
						
							|  |  |  |             ("TRAINER_MAY_RUSTBORO_MUDKIP",      1, False), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_110_MUDKIP",     2, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_ROUTE_119_MUDKIP",     2, True ), | 
					
						
							|  |  |  |             ("TRAINER_MAY_LILYCOVE_MUDKIP",      3, True ), | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for i, starter in enumerate([new_starters[1], new_starters[2], new_starters[0]]): | 
					
						
							|  |  |  |         potential_evolutions = [evolution.species_id for evolution in starter.evolutions] | 
					
						
							|  |  |  |         picked_evolution = starter.species_id | 
					
						
							|  |  |  |         if len(potential_evolutions) > 0: | 
					
						
							|  |  |  |             picked_evolution = world.random.choice(potential_evolutions) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for trainer_name, starter_position, is_evolved in rival_teams[i]: | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             new_species_id = picked_evolution if is_evolved else starter.species_id | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             trainer_data = world.modified_trainers[data.constants[trainer_name]] | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             trainer_data.party.pokemon[starter_position] = \ | 
					
						
							|  |  |  |                 trainer_data.party.pokemon[starter_position]._replace(species_id=new_species_id) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def randomize_legendary_encounters(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.legendary_encounters == RandomizeLegendaryEncounters.option_vanilla: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     elif world.options.legendary_encounters == RandomizeLegendaryEncounters.option_shuffle: | 
					
						
							|  |  |  |         # Just take the existing species and shuffle them | 
					
						
							|  |  |  |         shuffled_species = [encounter.species_id for encounter in data.legendary_encounters] | 
					
						
							|  |  |  |         world.random.shuffle(shuffled_species) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for i, encounter in enumerate(data.legendary_encounters): | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             world.modified_legendary_encounters.append(encounter._replace(species_id=shuffled_species[i])) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     else: | 
					
						
							|  |  |  |         should_match_bst = world.options.legendary_encounters in { | 
					
						
							|  |  |  |             RandomizeLegendaryEncounters.option_match_base_stats, | 
					
						
							|  |  |  |             RandomizeLegendaryEncounters.option_match_base_stats_and_type | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         should_match_type = world.options.legendary_encounters in { | 
					
						
							|  |  |  |             RandomizeLegendaryEncounters.option_match_type, | 
					
						
							|  |  |  |             RandomizeLegendaryEncounters.option_match_base_stats_and_type | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for encounter in data.legendary_encounters: | 
					
						
							|  |  |  |             original_species = world.modified_species[encounter.species_id] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             candidates = list(world.modified_species.values()) | 
					
						
							|  |  |  |             if should_match_type: | 
					
						
							|  |  |  |                 candidates = [ | 
					
						
							|  |  |  |                     species | 
					
						
							|  |  |  |                     for species in candidates | 
					
						
							|  |  |  |                     if bool(set(species.types) & set(original_species.types)) | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |             if should_match_bst: | 
					
						
							|  |  |  |                 candidates = filter_species_by_nearby_bst(candidates, sum(original_species.base_stats)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             world.modified_legendary_encounters.append(encounter._replace( | 
					
						
							|  |  |  |                 species_id=world.random.choice(candidates).species_id | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             )) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def randomize_misc_pokemon(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     if world.options.misc_pokemon == RandomizeMiscPokemon.option_vanilla: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     elif world.options.misc_pokemon == RandomizeMiscPokemon.option_shuffle: | 
					
						
							|  |  |  |         # Just take the existing species and shuffle them | 
					
						
							|  |  |  |         shuffled_species = [encounter.species_id for encounter in data.misc_pokemon] | 
					
						
							|  |  |  |         world.random.shuffle(shuffled_species) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         world.modified_misc_pokemon = [] | 
					
						
							|  |  |  |         for i, encounter in enumerate(data.misc_pokemon): | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             world.modified_misc_pokemon.append(encounter._replace(species_id=shuffled_species[i])) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     else: | 
					
						
							|  |  |  |         should_match_bst = world.options.misc_pokemon in { | 
					
						
							|  |  |  |             RandomizeMiscPokemon.option_match_base_stats, | 
					
						
							|  |  |  |             RandomizeMiscPokemon.option_match_base_stats_and_type, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         should_match_type = world.options.misc_pokemon in { | 
					
						
							|  |  |  |             RandomizeMiscPokemon.option_match_type, | 
					
						
							|  |  |  |             RandomizeMiscPokemon.option_match_base_stats_and_type, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for encounter in data.misc_pokemon: | 
					
						
							|  |  |  |             original_species = world.modified_species[encounter.species_id] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             candidates = list(world.modified_species.values()) | 
					
						
							|  |  |  |             if should_match_type: | 
					
						
							|  |  |  |                 candidates = [ | 
					
						
							|  |  |  |                     species | 
					
						
							|  |  |  |                     for species in candidates | 
					
						
							|  |  |  |                     if bool(set(species.types) & set(original_species.types)) | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |             if should_match_bst: | 
					
						
							|  |  |  |                 candidates = filter_species_by_nearby_bst(candidates, sum(original_species.base_stats)) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             player_filtered_candidates = [ | 
					
						
							|  |  |  |                 species | 
					
						
							|  |  |  |                 for species in candidates | 
					
						
							|  |  |  |                 if species.species_id not in world.blacklisted_wilds | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |             if len(player_filtered_candidates) > 0: | 
					
						
							|  |  |  |                 candidates = player_filtered_candidates | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-08 09:48:48 -07:00
										 |  |  |             world.modified_misc_pokemon.append(encounter._replace( | 
					
						
							|  |  |  |                 species_id=world.random.choice(candidates).species_id | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             )) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def randomize_tm_hm_compatibility(world: "PokemonEmeraldWorld") -> None: | 
					
						
							|  |  |  |     for species in world.modified_species.values(): | 
					
						
							|  |  |  |         # TM and HM compatibility is stored as a 64-bit bitfield | 
					
						
							|  |  |  |         combatibility_array = int_to_bool_array(species.tm_hm_compatibility) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TMs | 
					
						
							|  |  |  |         if world.options.tm_tutor_compatibility != TmTutorCompatibility.special_range_names["vanilla"]: | 
					
						
							|  |  |  |             for i in range(0, 50): | 
					
						
							|  |  |  |                 combatibility_array[i] = world.random.random() < world.options.tm_tutor_compatibility / 100 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # HMs | 
					
						
							|  |  |  |         if world.options.hm_compatibility != HmCompatibility.special_range_names["vanilla"]: | 
					
						
							|  |  |  |             for i in range(50, 58): | 
					
						
							|  |  |  |                 combatibility_array[i] = world.random.random() < world.options.hm_compatibility / 100 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         species.tm_hm_compatibility = bool_array_to_int(combatibility_array) |