| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | """
 | 
					
						
							|  |  |  | Archipelago World definition for Pokemon Emerald Version | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | from collections import Counter | 
					
						
							|  |  |  | import copy | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2024-05-04 23:08:24 -06:00
										 |  |  | import pkgutil | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  | from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | from Fill import FillError, fill_restrictive | 
					
						
							| 
									
										
										
										
											2024-05-04 00:38:24 -06:00
										 |  |  | from Options import OptionError, Toggle | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | import settings | 
					
						
							|  |  |  | from worlds.AutoWorld import WebWorld, World | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from .client import PokemonEmeraldClient  # Unused, but required to register with BizHawkClient | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | from .data import LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, data as emerald_data | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | from .items import (ITEM_GROUPS, PokemonEmeraldItem, create_item_label_to_code_map, get_item_classification, | 
					
						
							|  |  |  |                     offset_item_value) | 
					
						
							|  |  |  | from .locations import (LOCATION_GROUPS, PokemonEmeraldLocation, create_location_label_to_id_map, | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                         create_locations_with_tags, set_free_fly, set_legendary_cave_entrances) | 
					
						
							|  |  |  | from .opponents import randomize_opponent_parties | 
					
						
							|  |  |  | from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType, PokemonEmeraldOptions, | 
					
						
							|  |  |  |                       RandomizeWildPokemon, RandomizeBadges, RandomizeHms, NormanRequirement) | 
					
						
							|  |  |  | from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets, | 
					
						
							|  |  |  |                       randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters, | 
					
						
							|  |  |  |                       randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters) | 
					
						
							| 
									
										
										
										
											2024-05-04 23:08:24 -06:00
										 |  |  | from .rom import PokemonEmeraldProcedurePatch, write_tokens  | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PokemonEmeraldWebWorld(WebWorld): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Webhost info for Pokemon Emerald | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     theme = "ocean" | 
					
						
							| 
									
										
										
										
											2024-02-28 21:54:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     setup_en = Tutorial( | 
					
						
							|  |  |  |         "Multiworld Setup Guide", | 
					
						
							|  |  |  |         "A guide to playing Pokémon Emerald with Archipelago.", | 
					
						
							|  |  |  |         "English", | 
					
						
							|  |  |  |         "setup_en.md", | 
					
						
							|  |  |  |         "setup/en", | 
					
						
							|  |  |  |         ["Zunawe"] | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 21:54:54 -03:00
										 |  |  |     setup_es = Tutorial( | 
					
						
							|  |  |  |         "Guía de configuración para Multiworld", | 
					
						
							|  |  |  |         "Una guía para jugar Pokémon Emerald en Archipelago", | 
					
						
							|  |  |  |         "Español", | 
					
						
							|  |  |  |         "setup_es.md", | 
					
						
							|  |  |  |         "setup/es", | 
					
						
							|  |  |  |         ["nachocua"] | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-07-25 09:30:23 +02:00
										 |  |  |      | 
					
						
							|  |  |  |     setup_sv = Tutorial( | 
					
						
							|  |  |  |         "Multivärld Installations Guide", | 
					
						
							|  |  |  |         "En guide för att kunna spela Pokémon Emerald med Archipelago.", | 
					
						
							|  |  |  |         "Svenska", | 
					
						
							|  |  |  |         "setup_sv.md", | 
					
						
							|  |  |  |         "setup/sv", | 
					
						
							|  |  |  |         ["Tsukino"] | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-02-28 21:54:54 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-25 09:30:23 +02:00
										 |  |  |     tutorials = [setup_en, setup_es, setup_sv] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PokemonEmeraldSettings(settings.Group): | 
					
						
							|  |  |  |     class PokemonEmeraldRomFile(settings.UserFilePath): | 
					
						
							|  |  |  |         """File name of your English Pokemon Emerald ROM""" | 
					
						
							|  |  |  |         description = "Pokemon Emerald ROM File" | 
					
						
							|  |  |  |         copy_to = "Pokemon - Emerald Version (USA, Europe).gba" | 
					
						
							| 
									
										
										
										
											2024-05-04 23:08:24 -06:00
										 |  |  |         md5s = [PokemonEmeraldProcedurePatch.hash] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     rom_file: PokemonEmeraldRomFile = PokemonEmeraldRomFile(PokemonEmeraldRomFile.copy_to) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PokemonEmeraldWorld(World): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Pokémon Emerald is the definitive Gen III Pokémon game and one of the most beloved in the franchise. | 
					
						
							|  |  |  |     Catch, train, and battle Pokémon, explore the Hoenn region, thwart the plots | 
					
						
							|  |  |  |     of Team Magma and Team Aqua, challenge gyms, and become the Pokémon champion! | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     game = "Pokemon Emerald" | 
					
						
							|  |  |  |     web = PokemonEmeraldWebWorld() | 
					
						
							|  |  |  |     topology_present = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     settings_key = "pokemon_emerald_settings" | 
					
						
							|  |  |  |     settings: ClassVar[PokemonEmeraldSettings] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     options_dataclass = PokemonEmeraldOptions | 
					
						
							|  |  |  |     options: PokemonEmeraldOptions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_name_to_id = create_item_label_to_code_map() | 
					
						
							|  |  |  |     location_name_to_id = create_location_label_to_id_map() | 
					
						
							|  |  |  |     item_name_groups = ITEM_GROUPS | 
					
						
							|  |  |  |     location_name_groups = LOCATION_GROUPS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 10:39:28 -06:00
										 |  |  |     required_client_version = (0, 4, 6) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] | 
					
						
							|  |  |  |     hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] | 
					
						
							|  |  |  |     free_fly_location_id: int | 
					
						
							|  |  |  |     blacklisted_moves: Set[int] | 
					
						
							|  |  |  |     blacklisted_wilds: Set[int] | 
					
						
							|  |  |  |     blacklisted_starters: Set[int] | 
					
						
							|  |  |  |     blacklisted_opponent_pokemon: Set[int] | 
					
						
							|  |  |  |     hm_requirements: Dict[str, Union[int, List[str]]] | 
					
						
							|  |  |  |     auth: bytes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     modified_species: Dict[int, SpeciesData] | 
					
						
							|  |  |  |     modified_maps: Dict[str, MapData] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     modified_tmhm_moves: List[int] | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     modified_legendary_encounters: List[int] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     modified_starters: Tuple[int, int, int] | 
					
						
							|  |  |  |     modified_trainers: List[TrainerData] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     def __init__(self, multiworld, player): | 
					
						
							|  |  |  |         super(PokemonEmeraldWorld, self).__init__(multiworld, player) | 
					
						
							|  |  |  |         self.badge_shuffle_info = None | 
					
						
							|  |  |  |         self.hm_shuffle_info = None | 
					
						
							|  |  |  |         self.free_fly_location_id = 0 | 
					
						
							|  |  |  |         self.blacklisted_moves = set() | 
					
						
							|  |  |  |         self.blacklisted_wilds = set() | 
					
						
							|  |  |  |         self.blacklisted_starters = set() | 
					
						
							|  |  |  |         self.blacklisted_opponent_pokemon = set() | 
					
						
							|  |  |  |         self.modified_maps = copy.deepcopy(emerald_data.maps) | 
					
						
							|  |  |  |         self.modified_species = copy.deepcopy(emerald_data.species) | 
					
						
							|  |  |  |         self.modified_tmhm_moves = [] | 
					
						
							|  |  |  |         self.modified_starters = emerald_data.starters | 
					
						
							|  |  |  |         self.modified_trainers = [] | 
					
						
							|  |  |  |         self.modified_legendary_encounters = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def stage_assert_generate(cls, multiworld: MultiWorld) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         from .sanity_check import validate_regions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         assert validate_regions() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |         return "Great Ball" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_early(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         self.hm_requirements = { | 
					
						
							|  |  |  |             "HM01 Cut": ["Stone Badge"], | 
					
						
							|  |  |  |             "HM02 Fly": ["Feather Badge"], | 
					
						
							|  |  |  |             "HM03 Surf": ["Balance Badge"], | 
					
						
							|  |  |  |             "HM04 Strength": ["Heat Badge"], | 
					
						
							|  |  |  |             "HM05 Flash": ["Knuckle Badge"], | 
					
						
							|  |  |  |             "HM06 Rock Smash": ["Dynamo Badge"], | 
					
						
							|  |  |  |             "HM07 Waterfall": ["Rain Badge"], | 
					
						
							|  |  |  |             "HM08 Dive": ["Mind Badge"], | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if self.options.hm_requirements == HmRequirements.option_fly_without_badge: | 
					
						
							|  |  |  |             self.hm_requirements["HM02 Fly"] = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.blacklisted_moves = {emerald_data.move_labels[label] for label in self.options.move_blacklist.value} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.blacklisted_wilds = { | 
					
						
							|  |  |  |             get_species_id_by_label(species_name) | 
					
						
							|  |  |  |             for species_name in self.options.wild_encounter_blacklist.value | 
					
						
							|  |  |  |             if species_name != "_Legendaries" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if "_Legendaries" in self.options.wild_encounter_blacklist.value: | 
					
						
							|  |  |  |             self.blacklisted_wilds |= LEGENDARY_POKEMON | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.blacklisted_starters = { | 
					
						
							|  |  |  |             get_species_id_by_label(species_name) | 
					
						
							|  |  |  |             for species_name in self.options.starter_blacklist.value | 
					
						
							|  |  |  |             if species_name != "_Legendaries" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if "_Legendaries" in self.options.starter_blacklist.value: | 
					
						
							|  |  |  |             self.blacklisted_starters |= LEGENDARY_POKEMON | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.blacklisted_opponent_pokemon = { | 
					
						
							|  |  |  |             get_species_id_by_label(species_name) | 
					
						
							|  |  |  |             for species_name in self.options.trainer_party_blacklist.value | 
					
						
							|  |  |  |             if species_name != "_Legendaries" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if "_Legendaries" in self.options.starter_blacklist.value: | 
					
						
							|  |  |  |             self.blacklisted_opponent_pokemon |= LEGENDARY_POKEMON | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # In race mode we don't patch any item location information into the ROM | 
					
						
							|  |  |  |         if self.multiworld.is_race and not self.options.remote_items: | 
					
						
							|  |  |  |             logging.warning("Pokemon Emerald: Forcing Player %s (%s) to use remote items due to race mode.", | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |                             self.player, self.player_name) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             self.options.remote_items.value = Toggle.option_true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.goal == Goal.option_legendary_hunt: | 
					
						
							|  |  |  |             # Prevent turning off all legendary encounters | 
					
						
							|  |  |  |             if len(self.options.allowed_legendary_hunt_encounters.value) == 0: | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |                 raise OptionError(f"Pokemon Emerald: Player {self.player} ({self.player_name}) needs to allow at " | 
					
						
							|  |  |  |                                    "least one legendary encounter when goal is legendary hunt.") | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Prevent setting the number of required legendaries higher than the number of enabled legendaries | 
					
						
							|  |  |  |             if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value): | 
					
						
							|  |  |  |                 logging.warning("Pokemon Emerald: Legendary hunt count for Player %s (%s) higher than number of allowed " | 
					
						
							|  |  |  |                                 "legendary encounters. Reducing to number of allowed encounters.", self.player, | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |                                 self.player_name) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 self.options.legendary_hunt_count.value = len(self.options.allowed_legendary_hunt_encounters.value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Require random wild encounters if dexsanity is enabled | 
					
						
							|  |  |  |         if self.options.dexsanity and self.options.wild_pokemon == RandomizeWildPokemon.option_vanilla: | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |             raise OptionError(f"Pokemon Emerald: Player {self.player} ({self.player_name}) must not leave wild " | 
					
						
							|  |  |  |                                "encounters vanilla if enabling dexsanity.") | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # If badges or HMs are vanilla, Norman locks you from using Surf, | 
					
						
							|  |  |  |         # which means you're not guaranteed to be able to reach Fortree Gym, | 
					
						
							|  |  |  |         # Mossdeep Gym, or Sootopolis Gym. So we can't require reaching those | 
					
						
							|  |  |  |         # gyms to challenge Norman or it creates a circular dependency. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # This is never a problem for completely random badges/hms because the | 
					
						
							|  |  |  |         # algo will not place Surf/Balance Badge on Norman on its own. It's | 
					
						
							|  |  |  |         # never a problem for shuffled badges/hms because there is no scenario | 
					
						
							|  |  |  |         # where Cut or the Stone Badge can be a lynchpin for access to any gyms, | 
					
						
							|  |  |  |         # so they can always be put on Norman in a worst case scenario. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # This will also be a problem in warp rando if direct access to Norman's | 
					
						
							|  |  |  |         # room requires Surf or if access any gym leader in general requires | 
					
						
							|  |  |  |         # Surf. We will probably have to force this to 0 in that case. | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         max_norman_count = 7 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.badges == RandomizeBadges.option_vanilla: | 
					
						
							|  |  |  |             max_norman_count = 4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.hms == RandomizeHms.option_vanilla: | 
					
						
							|  |  |  |             if self.options.norman_requirement == NormanRequirement.option_badges: | 
					
						
							|  |  |  |                 if self.options.badges != RandomizeBadges.option_completely_random: | 
					
						
							|  |  |  |                     max_norman_count = 4 | 
					
						
							|  |  |  |             if self.options.norman_requirement == NormanRequirement.option_gyms: | 
					
						
							|  |  |  |                 max_norman_count = 4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.norman_count.value > max_norman_count: | 
					
						
							|  |  |  |             logging.warning("Pokemon Emerald: Norman requirements for Player %s (%s) are unsafe in combination with " | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |                             "other settings. Reducing to 4.", self.player, self.player_name) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             self.options.norman_count.value = max_norman_count | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_regions(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         from .regions import create_regions | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         regions = create_regions(self) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         tags = {"Badge", "HM", "KeyItem", "Rod", "Bike", "EventTicket"}  # Tags with progression items always included | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         if self.options.overworld_items: | 
					
						
							|  |  |  |             tags.add("OverworldItem") | 
					
						
							|  |  |  |         if self.options.hidden_items: | 
					
						
							|  |  |  |             tags.add("HiddenItem") | 
					
						
							|  |  |  |         if self.options.npc_gifts: | 
					
						
							|  |  |  |             tags.add("NpcGift") | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         if self.options.berry_trees: | 
					
						
							|  |  |  |             tags.add("BerryTree") | 
					
						
							|  |  |  |         if self.options.dexsanity: | 
					
						
							|  |  |  |             tags.add("Pokedex") | 
					
						
							|  |  |  |         if self.options.trainersanity: | 
					
						
							|  |  |  |             tags.add("Trainer") | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         create_locations_with_tags(self, regions, tags) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.regions.extend(regions.values()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |         # Exclude locations which are always locked behind the player's goal | 
					
						
							|  |  |  |         def exclude_locations(location_names: List[str]): | 
					
						
							|  |  |  |             for location_name in location_names: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     self.multiworld.get_location(location_name, | 
					
						
							|  |  |  |                                                  self.player).progress_type = LocationProgressType.EXCLUDED | 
					
						
							|  |  |  |                 except KeyError: | 
					
						
							|  |  |  |                     continue  # Location not in multiworld | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.goal == Goal.option_champion: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             # Always required to beat champion before receiving these | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |             exclude_locations([ | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 "Littleroot Town - S.S. Ticket from Norman", | 
					
						
							|  |  |  |                 "Littleroot Town - Aurora Ticket from Norman", | 
					
						
							|  |  |  |                 "Littleroot Town - Eon Ticket from Norman", | 
					
						
							|  |  |  |                 "Littleroot Town - Mystic Ticket from Norman", | 
					
						
							|  |  |  |                 "Littleroot Town - Old Sea Map from Norman", | 
					
						
							|  |  |  |                 "Ever Grande City - Champion Wallace", | 
					
						
							|  |  |  |                 "Meteor Falls 1F - Rival Steven", | 
					
						
							|  |  |  |                 "Trick House Puzzle 8 - Item", | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |             ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Construction workers don't move until champion is defeated | 
					
						
							|  |  |  |             if "Safari Zone Construction Workers" not in self.options.remove_roadblocks.value: | 
					
						
							|  |  |  |                 exclude_locations([ | 
					
						
							|  |  |  |                     "Safari Zone NE - Hidden Item North", | 
					
						
							|  |  |  |                     "Safari Zone NE - Hidden Item East", | 
					
						
							|  |  |  |                     "Safari Zone NE - Item on Ledge", | 
					
						
							|  |  |  |                     "Safari Zone SE - Hidden Item in South Grass 1", | 
					
						
							|  |  |  |                     "Safari Zone SE - Hidden Item in South Grass 2", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                     "Safari Zone SE - Item in Grass", | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |                 ]) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         elif self.options.goal == Goal.option_steven: | 
					
						
							|  |  |  |             exclude_locations([ | 
					
						
							|  |  |  |                 "Meteor Falls 1F - Rival Steven", | 
					
						
							|  |  |  |             ]) | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |         elif self.options.goal == Goal.option_norman: | 
					
						
							|  |  |  |             # If the player sets their options such that Surf or the Balance | 
					
						
							|  |  |  |             # Badge is vanilla, a very large number of locations become | 
					
						
							|  |  |  |             # "post-Norman". Similarly, access to the E4 may require you to | 
					
						
							|  |  |  |             # defeat Norman as an event or to get his badge, making postgame | 
					
						
							|  |  |  |             # locations inaccessible. Detecting these situations isn't trivial | 
					
						
							|  |  |  |             # and excluding all locations requiring Surf would be a bad idea. | 
					
						
							|  |  |  |             # So for now we just won't touch it and blame the user for | 
					
						
							|  |  |  |             # constructing their options in this way. Players usually expect | 
					
						
							|  |  |  |             # to only partially complete their world when playing this goal | 
					
						
							|  |  |  |             # anyway. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Locations which are directly unlocked by defeating Norman. | 
					
						
							|  |  |  |             exclude_locations([ | 
					
						
							| 
									
										
										
										
											2024-03-28 15:20:55 -06:00
										 |  |  |                 "Petalburg Gym - Leader Norman", | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |                 "Petalburg Gym - Balance Badge", | 
					
						
							|  |  |  |                 "Petalburg Gym - TM42 from Norman", | 
					
						
							|  |  |  |                 "Petalburg City - HM03 from Wally's Uncle", | 
					
						
							|  |  |  |                 "Dewford Town - TM36 from Sludge Bomb Man", | 
					
						
							|  |  |  |                 "Mauville City - Basement Key from Wattson", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 "Mauville City - TM24 from Wattson", | 
					
						
							| 
									
										
										
										
											2024-01-16 07:09:47 -07:00
										 |  |  |             ]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |     def create_items(self) -> None: | 
					
						
							|  |  |  |         item_locations: List[PokemonEmeraldLocation] = [ | 
					
						
							|  |  |  |             location | 
					
						
							|  |  |  |             for location in self.multiworld.get_locations(self.player) | 
					
						
							|  |  |  |             if location.address is not None | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Filter progression items which shouldn't be shuffled into the itempool. | 
					
						
							|  |  |  |         # Their locations will still exist, but event items will be placed and | 
					
						
							|  |  |  |         # locked at their vanilla locations instead. | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         filter_tags = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not self.options.key_items: | 
					
						
							|  |  |  |             filter_tags.add("KeyItem") | 
					
						
							|  |  |  |         if not self.options.rods: | 
					
						
							|  |  |  |             filter_tags.add("Rod") | 
					
						
							|  |  |  |         if not self.options.bikes: | 
					
						
							|  |  |  |             filter_tags.add("Bike") | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         if not self.options.event_tickets: | 
					
						
							|  |  |  |             filter_tags.add("EventTicket") | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if self.options.badges in {RandomizeBadges.option_vanilla, RandomizeBadges.option_shuffle}: | 
					
						
							|  |  |  |             filter_tags.add("Badge") | 
					
						
							|  |  |  |         if self.options.hms in {RandomizeHms.option_vanilla, RandomizeHms.option_shuffle}: | 
					
						
							|  |  |  |             filter_tags.add("HM") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # If Badges and HMs are set to the `shuffle` option, don't add them to | 
					
						
							|  |  |  |         # the normal item pool, but do create their items and save them and | 
					
						
							|  |  |  |         # their locations for use in `pre_fill` later. | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         if self.options.badges == RandomizeBadges.option_shuffle: | 
					
						
							|  |  |  |             self.badge_shuffle_info = [ | 
					
						
							|  |  |  |                 (location, self.create_item_by_code(location.default_item_code)) | 
					
						
							|  |  |  |                 for location in [l for l in item_locations if "Badge" in l.tags] | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |         if self.options.hms == RandomizeHms.option_shuffle: | 
					
						
							|  |  |  |             self.hm_shuffle_info = [ | 
					
						
							|  |  |  |                 (location, self.create_item_by_code(location.default_item_code)) | 
					
						
							|  |  |  |                 for location in [l for l in item_locations if "HM" in l.tags] | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Filter down locations to actual items that will be filled and create | 
					
						
							|  |  |  |         # the itempool. | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         item_locations = [location for location in item_locations if len(filter_tags & location.tags) == 0] | 
					
						
							|  |  |  |         default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Take the itempool as-is | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         if self.options.item_pool_type == ItemPoolType.option_shuffled: | 
					
						
							|  |  |  |             self.multiworld.itempool += default_itempool | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Recreate the itempool from random items | 
					
						
							|  |  |  |         elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced): | 
					
						
							|  |  |  |             item_categories = ["Ball", "Heal", "Candy", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc", "Berry"] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Count occurrences of types of vanilla items in pool | 
					
						
							|  |  |  |             item_category_counter = Counter() | 
					
						
							|  |  |  |             for item in default_itempool: | 
					
						
							|  |  |  |                 if not item.advancement: | 
					
						
							|  |  |  |                     item_category_counter.update([tag for tag in item.tags if tag in item_categories]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             item_category_weights = [item_category_counter.get(category) for category in item_categories] | 
					
						
							|  |  |  |             item_category_weights = [weight if weight is not None else 0 for weight in item_category_weights] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Create lists of item codes that can be used to fill | 
					
						
							|  |  |  |             fill_item_candidates = emerald_data.items.values() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             fill_item_candidates = [item for item in fill_item_candidates if "Unique" not in item.tags] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             fill_item_candidates_by_category = {category: [] for category in item_categories} | 
					
						
							|  |  |  |             for item_data in fill_item_candidates: | 
					
						
							|  |  |  |                 for category in item_categories: | 
					
						
							|  |  |  |                     if category in item_data.tags: | 
					
						
							|  |  |  |                         fill_item_candidates_by_category[category].append(offset_item_value(item_data.item_id)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for category in fill_item_candidates_by_category: | 
					
						
							|  |  |  |                 fill_item_candidates_by_category[category].sort() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Ignore vanilla occurrences and pick completely randomly | 
					
						
							|  |  |  |             if self.options.item_pool_type == ItemPoolType.option_diverse: | 
					
						
							|  |  |  |                 item_category_weights = [ | 
					
						
							|  |  |  |                     len(category_list) | 
					
						
							|  |  |  |                     for category_list in fill_item_candidates_by_category.values() | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # TMs should not have duplicates until every TM has been used already | 
					
						
							|  |  |  |             all_tm_choices = fill_item_candidates_by_category["TM"].copy() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def refresh_tm_choices() -> None: | 
					
						
							|  |  |  |                 fill_item_candidates_by_category["TM"] = all_tm_choices.copy() | 
					
						
							|  |  |  |                 self.random.shuffle(fill_item_candidates_by_category["TM"]) | 
					
						
							| 
									
										
										
										
											2024-02-15 13:04:20 -07:00
										 |  |  |             refresh_tm_choices() | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Create items | 
					
						
							|  |  |  |             for item in default_itempool: | 
					
						
							|  |  |  |                 if not item.advancement and "Unique" not in item.tags: | 
					
						
							|  |  |  |                     category = self.random.choices(item_categories, item_category_weights)[0] | 
					
						
							|  |  |  |                     if category == "TM": | 
					
						
							|  |  |  |                         if len(fill_item_candidates_by_category["TM"]) == 0: | 
					
						
							|  |  |  |                             refresh_tm_choices() | 
					
						
							|  |  |  |                         item_code = fill_item_candidates_by_category["TM"].pop() | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         item_code = self.random.choice(fill_item_candidates_by_category[category]) | 
					
						
							|  |  |  |                     item = self.create_item_by_code(item_code) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 self.multiworld.itempool.append(item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def set_rules(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         from .rules import set_rules | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         set_rules(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_basic(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Create auth | 
					
						
							|  |  |  |         # self.auth = self.random.randbytes(16)  # Requires >=3.9 | 
					
						
							|  |  |  |         self.auth = self.random.getrandbits(16 * 8).to_bytes(16, "little") | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         randomize_types(self) | 
					
						
							|  |  |  |         randomize_wild_encounters(self) | 
					
						
							|  |  |  |         set_free_fly(self) | 
					
						
							|  |  |  |         set_legendary_cave_entrances(self) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Key items which are considered in access rules but not randomized are converted to events and placed | 
					
						
							|  |  |  |         # in their vanilla locations so that the player can have them in their inventory for logic. | 
					
						
							|  |  |  |         def convert_unrandomized_items_to_events(tag: str) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             for location in self.multiworld.get_locations(self.player): | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |                 if location.tags is not None and tag in location.tags: | 
					
						
							|  |  |  |                     location.place_locked_item(self.create_event(self.item_id_to_name[location.default_item_code])) | 
					
						
							| 
									
										
										
										
											2024-02-17 17:52:50 -07:00
										 |  |  |                     location.progress_type = LocationProgressType.DEFAULT | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |                     location.address = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.badges == RandomizeBadges.option_vanilla: | 
					
						
							|  |  |  |             convert_unrandomized_items_to_events("Badge") | 
					
						
							|  |  |  |         if self.options.hms == RandomizeHms.option_vanilla: | 
					
						
							|  |  |  |             convert_unrandomized_items_to_events("HM") | 
					
						
							|  |  |  |         if not self.options.rods: | 
					
						
							|  |  |  |             convert_unrandomized_items_to_events("Rod") | 
					
						
							|  |  |  |         if not self.options.bikes: | 
					
						
							|  |  |  |             convert_unrandomized_items_to_events("Bike") | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         if not self.options.event_tickets: | 
					
						
							|  |  |  |             convert_unrandomized_items_to_events("EventTicket") | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         if not self.options.key_items: | 
					
						
							|  |  |  |             convert_unrandomized_items_to_events("KeyItem") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def pre_fill(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Badges and HMs that are set to shuffle need to be placed at | 
					
						
							|  |  |  |         # their own subset of locations | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         if self.options.badges == RandomizeBadges.option_shuffle: | 
					
						
							|  |  |  |             badge_locations: List[PokemonEmeraldLocation] | 
					
						
							|  |  |  |             badge_items: List[PokemonEmeraldItem] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Sort order makes `fill_restrictive` try to place important badges later, which | 
					
						
							|  |  |  |             # makes it less likely to have to swap at all, and more likely for swaps to work. | 
					
						
							|  |  |  |             badge_locations, badge_items = [list(l) for l in zip(*self.badge_shuffle_info)] | 
					
						
							|  |  |  |             badge_priority = { | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 "Knuckle Badge": 3, | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |                 "Balance Badge": 1, | 
					
						
							|  |  |  |                 "Dynamo Badge": 1, | 
					
						
							|  |  |  |                 "Mind Badge": 2, | 
					
						
							|  |  |  |                 "Heat Badge": 2, | 
					
						
							|  |  |  |                 "Rain Badge": 3, | 
					
						
							|  |  |  |                 "Stone Badge": 4, | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 "Feather Badge": 5, | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             # In the case of vanilla HMs, navigating Granite Cave is required to access more than 2 gyms, | 
					
						
							|  |  |  |             # so Knuckle Badge deserves highest priority if Flash is logically required. | 
					
						
							|  |  |  |             if self.options.hms == RandomizeHms.option_vanilla and \ | 
					
						
							|  |  |  |                     self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): | 
					
						
							|  |  |  |                 badge_priority["Knuckle Badge"] = 0 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             badge_items.sort(key=lambda item: badge_priority.get(item.name, 0)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-17 17:52:50 -07:00
										 |  |  |             # Un-exclude badge locations, since we need to put progression items on them | 
					
						
							|  |  |  |             for location in badge_locations: | 
					
						
							|  |  |  |                 location.progress_type = LocationProgressType.DEFAULT \ | 
					
						
							|  |  |  |                     if location.progress_type == LocationProgressType.EXCLUDED \ | 
					
						
							|  |  |  |                     else location.progress_type | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             collection_state = self.multiworld.get_all_state(False) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # If HM shuffle is on, HMs are not placed and not in the pool, so | 
					
						
							|  |  |  |             # `get_all_state` did not contain them. Collect them manually for | 
					
						
							|  |  |  |             # this fill. We know that they will be included in all state after | 
					
						
							|  |  |  |             # this stage. | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             if self.hm_shuffle_info is not None: | 
					
						
							|  |  |  |                 for _, item in self.hm_shuffle_info: | 
					
						
							|  |  |  |                     collection_state.collect(item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # In specific very constrained conditions, fill_restrictive may run | 
					
						
							|  |  |  |             # out of swaps before it finds a valid solution if it gets unlucky. | 
					
						
							|  |  |  |             # This is a band-aid until fill/swap can reliably find those solutions. | 
					
						
							|  |  |  |             attempts_remaining = 2 | 
					
						
							|  |  |  |             while attempts_remaining > 0: | 
					
						
							|  |  |  |                 attempts_remaining -= 1 | 
					
						
							|  |  |  |                 self.random.shuffle(badge_locations) | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     fill_restrictive(self.multiworld, collection_state, badge_locations, badge_items, | 
					
						
							|  |  |  |                                      single_player_placement=True, lock=True, allow_excluded=True) | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 except FillError as exc: | 
					
						
							|  |  |  |                     if attempts_remaining == 0: | 
					
						
							|  |  |  |                         raise exc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     logging.debug(f"Failed to shuffle badges for player {self.player}. Retrying.") | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Badges are guaranteed to be either placed or in the multiworld's itempool now | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         if self.options.hms == RandomizeHms.option_shuffle: | 
					
						
							|  |  |  |             hm_locations: List[PokemonEmeraldLocation] | 
					
						
							|  |  |  |             hm_items: List[PokemonEmeraldItem] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Sort order makes `fill_restrictive` try to place important HMs later, which | 
					
						
							|  |  |  |             # makes it less likely to have to swap at all, and more likely for swaps to work. | 
					
						
							|  |  |  |             hm_locations, hm_items = [list(l) for l in zip(*self.hm_shuffle_info)] | 
					
						
							|  |  |  |             hm_priority = { | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 "HM05 Flash": 3, | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |                 "HM03 Surf": 1, | 
					
						
							|  |  |  |                 "HM06 Rock Smash": 1, | 
					
						
							|  |  |  |                 "HM08 Dive": 2, | 
					
						
							|  |  |  |                 "HM04 Strength": 2, | 
					
						
							|  |  |  |                 "HM07 Waterfall": 3, | 
					
						
							|  |  |  |                 "HM01 Cut": 4, | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 "HM02 Fly": 5, | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             # In the case of vanilla badges, navigating Granite Cave is required to access more than 2 gyms, | 
					
						
							|  |  |  |             # so Flash deserves highest priority if it's logically required. | 
					
						
							|  |  |  |             if self.options.badges == RandomizeBadges.option_vanilla and \ | 
					
						
							|  |  |  |                     self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): | 
					
						
							|  |  |  |                 hm_priority["HM05 Flash"] = 0 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             hm_items.sort(key=lambda item: hm_priority.get(item.name, 0)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-17 17:52:50 -07:00
										 |  |  |             # Un-exclude HM locations, since we need to put progression items on them | 
					
						
							|  |  |  |             for location in hm_locations: | 
					
						
							|  |  |  |                 location.progress_type = LocationProgressType.DEFAULT \ | 
					
						
							|  |  |  |                     if location.progress_type == LocationProgressType.EXCLUDED \ | 
					
						
							|  |  |  |                     else location.progress_type | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             collection_state = self.multiworld.get_all_state(False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # In specific very constrained conditions, fill_restrictive may run | 
					
						
							|  |  |  |             # out of swaps before it finds a valid solution if it gets unlucky. | 
					
						
							|  |  |  |             # This is a band-aid until fill/swap can reliably find those solutions. | 
					
						
							|  |  |  |             attempts_remaining = 2 | 
					
						
							|  |  |  |             while attempts_remaining > 0: | 
					
						
							|  |  |  |                 attempts_remaining -= 1 | 
					
						
							|  |  |  |                 self.random.shuffle(hm_locations) | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     fill_restrictive(self.multiworld, collection_state, hm_locations, hm_items, | 
					
						
							|  |  |  |                                      single_player_placement=True, lock=True, allow_excluded=True) | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 except FillError as exc: | 
					
						
							|  |  |  |                     if attempts_remaining == 0: | 
					
						
							|  |  |  |                         raise exc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     logging.debug(f"Failed to shuffle HMs for player {self.player}. Retrying.") | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_output(self, output_directory: str) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         self.modified_trainers = copy.deepcopy(emerald_data.trainers) | 
					
						
							|  |  |  |         self.modified_tmhm_moves = copy.deepcopy(emerald_data.tmhm_moves) | 
					
						
							|  |  |  |         self.modified_legendary_encounters = copy.deepcopy(emerald_data.legendary_encounters) | 
					
						
							|  |  |  |         self.modified_misc_pokemon = copy.deepcopy(emerald_data.misc_pokemon) | 
					
						
							|  |  |  |         self.modified_starters = copy.deepcopy(emerald_data.starters) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Modify catch rate | 
					
						
							|  |  |  |         min_catch_rate = min(self.options.min_catch_rate.value, 255) | 
					
						
							|  |  |  |         for species in self.modified_species.values(): | 
					
						
							|  |  |  |             species.catch_rate = max(species.catch_rate, min_catch_rate) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         # Modify TM moves | 
					
						
							|  |  |  |         if self.options.tm_tutor_moves: | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             new_moves: Set[int] = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for i in range(50): | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 new_move = get_random_move(self.random, new_moves | self.blacklisted_moves) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |                 new_moves.add(new_move) | 
					
						
							|  |  |  |                 self.modified_tmhm_moves[i] = new_move | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 18:05:39 -06:00
										 |  |  |         randomize_abilities(self) | 
					
						
							|  |  |  |         randomize_learnsets(self) | 
					
						
							|  |  |  |         randomize_tm_hm_compatibility(self) | 
					
						
							|  |  |  |         randomize_legendary_encounters(self) | 
					
						
							|  |  |  |         randomize_misc_pokemon(self) | 
					
						
							|  |  |  |         randomize_opponent_parties(self) | 
					
						
							|  |  |  |         randomize_starters(self) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |         patch = PokemonEmeraldProcedurePatch(player=self.player, player_name=self.player_name) | 
					
						
							| 
									
										
										
										
											2024-05-04 23:08:24 -06:00
										 |  |  |         patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/base_patch.bsdiff4")) | 
					
						
							|  |  |  |         write_tokens(self, patch) | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         del self.modified_trainers | 
					
						
							|  |  |  |         del self.modified_tmhm_moves | 
					
						
							|  |  |  |         del self.modified_legendary_encounters | 
					
						
							|  |  |  |         del self.modified_misc_pokemon | 
					
						
							|  |  |  |         del self.modified_starters | 
					
						
							|  |  |  |         del self.modified_species | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-04 23:08:24 -06:00
										 |  |  |         # Write Output | 
					
						
							|  |  |  |         out_file_name = self.multiworld.get_out_file_name_base(self.player) | 
					
						
							|  |  |  |         patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}")) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     def write_spoiler(self, spoiler_handle: TextIO): | 
					
						
							|  |  |  |         if self.options.dexsanity: | 
					
						
							|  |  |  |             from collections import defaultdict | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |             spoiler_handle.write(f"\n\nWild Pokemon ({self.player_name}):\n\n") | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |             species_maps = defaultdict(set) | 
					
						
							|  |  |  |             for map in self.modified_maps.values(): | 
					
						
							|  |  |  |                 if map.land_encounters is not None: | 
					
						
							|  |  |  |                     for encounter in map.land_encounters.slots: | 
					
						
							|  |  |  |                         species_maps[encounter].add(map.name[4:]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if map.water_encounters is not None: | 
					
						
							|  |  |  |                     for encounter in map.water_encounters.slots: | 
					
						
							|  |  |  |                         species_maps[encounter].add(map.name[4:]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if map.fishing_encounters is not None: | 
					
						
							|  |  |  |                     for encounter in map.fishing_encounters.slots: | 
					
						
							|  |  |  |                         species_maps[encounter].add(map.name[4:]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             lines = [f"{emerald_data.species[species].label}: {', '.join(maps)}\n" | 
					
						
							|  |  |  |                      for species, maps in species_maps.items()] | 
					
						
							|  |  |  |             lines.sort() | 
					
						
							|  |  |  |             for line in lines: | 
					
						
							|  |  |  |                 spoiler_handle.write(line) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         del self.modified_maps | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def extend_hint_information(self, hint_data): | 
					
						
							|  |  |  |         if self.options.dexsanity: | 
					
						
							|  |  |  |             from collections import defaultdict | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             slot_to_rod = { | 
					
						
							|  |  |  |                 0: "_OLD_ROD", | 
					
						
							|  |  |  |                 1: "_OLD_ROD", | 
					
						
							|  |  |  |                 2: "_GOOD_ROD", | 
					
						
							|  |  |  |                 3: "_GOOD_ROD", | 
					
						
							|  |  |  |                 4: "_GOOD_ROD", | 
					
						
							|  |  |  |                 5: "_SUPER_ROD", | 
					
						
							|  |  |  |                 6: "_SUPER_ROD", | 
					
						
							|  |  |  |                 7: "_SUPER_ROD", | 
					
						
							|  |  |  |                 8: "_SUPER_ROD", | 
					
						
							|  |  |  |                 9: "_SUPER_ROD", | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             species_maps = defaultdict(set) | 
					
						
							|  |  |  |             for map in self.modified_maps.values(): | 
					
						
							|  |  |  |                 if map.land_encounters is not None: | 
					
						
							|  |  |  |                     for encounter in map.land_encounters.slots: | 
					
						
							|  |  |  |                         species_maps[encounter].add(map.name[4:] + "_GRASS") | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 if map.water_encounters is not None: | 
					
						
							|  |  |  |                     for encounter in map.water_encounters.slots: | 
					
						
							|  |  |  |                         species_maps[encounter].add(map.name[4:] + "_WATER") | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |                 if map.fishing_encounters is not None: | 
					
						
							|  |  |  |                     for slot, encounter in enumerate(map.fishing_encounters.slots): | 
					
						
							|  |  |  |                         species_maps[encounter].add(map.name[4:] + slot_to_rod[slot]) | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             hint_data[self.player] = { | 
					
						
							|  |  |  |                 self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(maps) | 
					
						
							|  |  |  |                 for species, maps in species_maps.items() | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |     def modify_multidata(self, multidata: Dict[str, Any]): | 
					
						
							|  |  |  |         import base64 | 
					
						
							| 
									
										
										
										
											2024-06-01 04:12:37 -07:00
										 |  |  |         multidata["connect_names"][base64.b64encode(self.auth).decode("ascii")] = multidata["connect_names"][self.player_name] | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def fill_slot_data(self) -> Dict[str, Any]: | 
					
						
							|  |  |  |         slot_data = self.options.as_dict( | 
					
						
							|  |  |  |             "goal", | 
					
						
							|  |  |  |             "badges", | 
					
						
							|  |  |  |             "hms", | 
					
						
							|  |  |  |             "key_items", | 
					
						
							|  |  |  |             "bikes", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             "event_tickets", | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             "rods", | 
					
						
							|  |  |  |             "overworld_items", | 
					
						
							|  |  |  |             "hidden_items", | 
					
						
							|  |  |  |             "npc_gifts", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             "berry_trees", | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             "require_itemfinder", | 
					
						
							|  |  |  |             "require_flash", | 
					
						
							|  |  |  |             "elite_four_requirement", | 
					
						
							|  |  |  |             "elite_four_count", | 
					
						
							|  |  |  |             "norman_requirement", | 
					
						
							|  |  |  |             "norman_count", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             "legendary_hunt_catch", | 
					
						
							|  |  |  |             "legendary_hunt_count", | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             "extra_boulders", | 
					
						
							|  |  |  |             "remove_roadblocks", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             "allowed_legendary_hunt_encounters", | 
					
						
							|  |  |  |             "extra_bumpy_slope", | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |             "free_fly_location", | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |             "remote_items", | 
					
						
							|  |  |  |             "dexsanity", | 
					
						
							|  |  |  |             "trainersanity", | 
					
						
							|  |  |  |             "modify_118", | 
					
						
							|  |  |  |             "death_link", | 
					
						
							| 
									
										
										
										
											2024-09-11 04:20:07 -07:00
										 |  |  |             "normalize_encounter_rates", | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         ) | 
					
						
							|  |  |  |         slot_data["free_fly_location_id"] = self.free_fly_location_id | 
					
						
							| 
									
										
										
										
											2024-03-14 05:37:10 -06:00
										 |  |  |         slot_data["hm_requirements"] = self.hm_requirements | 
					
						
							| 
									
										
										
										
											2023-11-12 13:39:34 -08:00
										 |  |  |         return slot_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_item(self, name: str) -> PokemonEmeraldItem: | 
					
						
							|  |  |  |         return self.create_item_by_code(self.item_name_to_id[name]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_item_by_code(self, item_code: int) -> PokemonEmeraldItem: | 
					
						
							|  |  |  |         return PokemonEmeraldItem( | 
					
						
							|  |  |  |             self.item_id_to_name[item_code], | 
					
						
							|  |  |  |             get_item_classification(item_code), | 
					
						
							|  |  |  |             item_code, | 
					
						
							|  |  |  |             self.player | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_event(self, name: str) -> PokemonEmeraldItem: | 
					
						
							|  |  |  |         return PokemonEmeraldItem( | 
					
						
							|  |  |  |             name, | 
					
						
							|  |  |  |             ItemClassification.progression, | 
					
						
							|  |  |  |             None, | 
					
						
							|  |  |  |             self.player | 
					
						
							|  |  |  |         ) |