""" Classes and functions related to AP locations for Pokemon Emerald """ from typing import TYPE_CHECKING, Dict, Optional, Set from BaseClasses import Location, Region from .data import BASE_OFFSET, NATIONAL_ID_TO_SPECIES_ID, POKEDEX_OFFSET, LocationCategory, data from .items import offset_item_value if TYPE_CHECKING: from . import PokemonEmeraldWorld VISITED_EVENT_NAME_TO_ID = { "EVENT_VISITED_LITTLEROOT_TOWN": 0, "EVENT_VISITED_OLDALE_TOWN": 1, "EVENT_VISITED_PETALBURG_CITY": 2, "EVENT_VISITED_RUSTBORO_CITY": 3, "EVENT_VISITED_DEWFORD_TOWN": 4, "EVENT_VISITED_SLATEPORT_CITY": 5, "EVENT_VISITED_MAUVILLE_CITY": 6, "EVENT_VISITED_VERDANTURF_TOWN": 7, "EVENT_VISITED_FALLARBOR_TOWN": 8, "EVENT_VISITED_LAVARIDGE_TOWN": 9, "EVENT_VISITED_FORTREE_CITY": 10, "EVENT_VISITED_LILYCOVE_CITY": 11, "EVENT_VISITED_MOSSDEEP_CITY": 12, "EVENT_VISITED_SOOTOPOLIS_CITY": 13, "EVENT_VISITED_PACIFIDLOG_TOWN": 14, "EVENT_VISITED_EVER_GRANDE_CITY": 15, "EVENT_VISITED_BATTLE_FRONTIER": 16, "EVENT_VISITED_SOUTHERN_ISLAND": 17, } BLACKLIST_OPTION_TO_VISITED_EVENT = { "Littleroot Town": "EVENT_VISITED_LITTLEROOT_TOWN", "Oldale Town": "EVENT_VISITED_OLDALE_TOWN", "Petalburg City": "EVENT_VISITED_PETALBURG_CITY", "Rustboro City": "EVENT_VISITED_RUSTBORO_CITY", "Dewford Town": "EVENT_VISITED_DEWFORD_TOWN", "Slateport City": "EVENT_VISITED_SLATEPORT_CITY", "Mauville City": "EVENT_VISITED_MAUVILLE_CITY", "Verdanturf Town": "EVENT_VISITED_VERDANTURF_TOWN", "Fallarbor Town": "EVENT_VISITED_FALLARBOR_TOWN", "Lavaridge Town": "EVENT_VISITED_LAVARIDGE_TOWN", "Fortree City": "EVENT_VISITED_FORTREE_CITY", "Lilycove City": "EVENT_VISITED_LILYCOVE_CITY", "Mossdeep City": "EVENT_VISITED_MOSSDEEP_CITY", "Sootopolis City": "EVENT_VISITED_SOOTOPOLIS_CITY", "Ever Grande City": "EVENT_VISITED_EVER_GRANDE_CITY", } VISITED_EVENTS = frozenset(BLACKLIST_OPTION_TO_VISITED_EVENT.values()) class PokemonEmeraldLocation(Location): game: str = "Pokemon Emerald" item_address: Optional[int] default_item_code: Optional[int] key: Optional[str] def __init__( self, player: int, name: str, address: Optional[int], parent: Optional[Region] = None, key: Optional[str] = None, item_address: Optional[int] = None, default_item_value: Optional[int] = None) -> None: super().__init__(player, name, address, parent) self.default_item_code = None if default_item_value is None else offset_item_value(default_item_value) self.item_address = item_address self.key = key def offset_flag(flag: int) -> int: """ Returns the AP location id (address) for a given flag """ if flag is None: return None return flag + BASE_OFFSET def reverse_offset_flag(location_id: int) -> int: """ Returns the flag id for a given AP location id (address) """ if location_id is None: return None return location_id - BASE_OFFSET def create_locations_by_category(world: "PokemonEmeraldWorld", regions: Dict[str, Region], categories: Set[LocationCategory]) -> None: """ Iterates through region data and adds locations to the multiworld if those locations include any of the provided tags. """ for region_name, region_data in data.regions.items(): region = regions[region_name] filtered_locations = [loc for loc in region_data.locations if data.locations[loc].category in categories] for location_name in filtered_locations: location_data = data.locations[location_name] location_id = offset_flag(location_data.flag) if location_data.flag == 0: # Dexsanity location national_dex_id = int(location_name[-3:]) # Location names are formatted POKEDEX_REWARD_### # Don't create this pokedex location if player can't find it in the wild if NATIONAL_ID_TO_SPECIES_ID[national_dex_id] in world.blacklisted_wilds: continue location_id += POKEDEX_OFFSET + national_dex_id location = PokemonEmeraldLocation( world.player, location_data.label, location_id, region, location_name, location_data.address, location_data.default_item ) region.locations.append(location) def create_location_label_to_id_map() -> Dict[str, int]: """ Creates a map from location labels to their AP location id (address) """ label_to_id_map: Dict[str, int] = {} for region_data in data.regions.values(): for location_name in region_data.locations: location_data = data.locations[location_name] if location_data.flag == 0: label_to_id_map[location_data.label] = BASE_OFFSET + POKEDEX_OFFSET + int(location_data.name[15:]) else: label_to_id_map[location_data.label] = offset_flag(location_data.flag) return label_to_id_map def set_free_fly(world: "PokemonEmeraldWorld") -> None: # Set our free fly location # If not enabled, set it to Littleroot Town by default fly_location_name = "EVENT_VISITED_LITTLEROOT_TOWN" if world.options.free_fly_location: blacklisted_locations = set(BLACKLIST_OPTION_TO_VISITED_EVENT[city] for city in world.options.free_fly_blacklist.value) free_fly_locations = sorted(VISITED_EVENTS - blacklisted_locations) if free_fly_locations: fly_location_name = world.random.choice(free_fly_locations) world.free_fly_location_id = VISITED_EVENT_NAME_TO_ID[fly_location_name] free_fly_location_location = world.multiworld.get_location("FREE_FLY_LOCATION", world.player) free_fly_location_location.item = None free_fly_location_location.place_locked_item(world.create_event(fly_location_name)) def set_legendary_cave_entrances(world: "PokemonEmeraldWorld") -> None: # Set Marine Cave and Terra Cave entrances terra_cave_location_name = world.random.choice([ "TERRA_CAVE_ROUTE_114_1", "TERRA_CAVE_ROUTE_114_2", "TERRA_CAVE_ROUTE_115_1", "TERRA_CAVE_ROUTE_115_2", "TERRA_CAVE_ROUTE_116_1", "TERRA_CAVE_ROUTE_116_2", "TERRA_CAVE_ROUTE_118_1", "TERRA_CAVE_ROUTE_118_2", ]) terra_cave_location_location = world.multiworld.get_location("TERRA_CAVE_LOCATION", world.player) terra_cave_location_location.item = None terra_cave_location_location.place_locked_item(world.create_event(terra_cave_location_name)) marine_cave_location_name = world.random.choice([ "MARINE_CAVE_ROUTE_105_1", "MARINE_CAVE_ROUTE_105_2", "MARINE_CAVE_ROUTE_125_1", "MARINE_CAVE_ROUTE_125_2", "MARINE_CAVE_ROUTE_127_1", "MARINE_CAVE_ROUTE_127_2", "MARINE_CAVE_ROUTE_129_1", # "MARINE_CAVE_ROUTE_129_2", # Cave ID too high for internal data type, needs patch update ]) marine_cave_location_location = world.multiworld.get_location("MARINE_CAVE_LOCATION", world.player) marine_cave_location_location.item = None marine_cave_location_location.place_locked_item(world.create_event(marine_cave_location_name))