Archipelago/worlds/pokemon_emerald/regions.py

124 lines
6.2 KiB
Python

"""
Functions related to AP regions for Pokemon Emerald (see ./data/regions for region definitions)
"""
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
from BaseClasses import CollectionState, ItemClassification, Region
from .data import data
from .items import PokemonEmeraldItem
from .locations import PokemonEmeraldLocation
if TYPE_CHECKING:
from . import PokemonEmeraldWorld
def create_regions(world: "PokemonEmeraldWorld") -> Dict[str, Region]:
"""
Iterates through regions created from JSON to create regions and adds them to the multiworld.
Also creates and places events and connects regions via warps and the exits defined in the JSON.
"""
# Used in connect_to_map_encounters. Splits encounter categories into "subcategories" and gives them names
# and rules so the rods can only access their specific slots.
encounter_categories: Dict[str, List[Tuple[Optional[str], range, Optional[Callable[[CollectionState], bool]]]]] = {
"LAND": [(None, range(0, 12), None)],
"WATER": [(None, range(0, 5), None)],
"FISHING": [
("OLD_ROD", range(0, 2), lambda state: state.has("Old Rod", world.player)),
("GOOD_ROD", range(2, 5), lambda state: state.has("Good Rod", world.player)),
("SUPER_ROD", range(5, 10), lambda state: state.has("Super Rod", world.player)),
],
}
def connect_to_map_encounters(region: Region, map_name: str, include_slots: Tuple[bool, bool, bool]):
"""
Connects the provided region to the corresponding wild encounters for the given parent map.
Each in-game map may have a non-physical Region for encountering wild pokemon in each of the three categories
land, water, and fishing. Region data defines whether a given region includes places where those encounters can
be accessed (i.e. whether the region has tall grass, a river bank, is on water, etc.).
These regions are created lazily and dynamically so as not to bother with unused maps.
"""
# For each of land, water, and fishing, connect the region if indicated by include_slots
for i, encounter_category in enumerate(encounter_categories.items()):
if include_slots[i]:
region_name = f"{map_name}_{encounter_category[0]}_ENCOUNTERS"
# If the region hasn't been created yet, create it now
try:
encounter_region = world.multiworld.get_region(region_name, world.player)
except KeyError:
encounter_region = Region(region_name, world.player, world.multiworld)
encounter_slots = getattr(data.maps[map_name], f"{encounter_category[0].lower()}_encounters").slots
# Subcategory is for splitting fishing rods; land and water only have one subcategory
for subcategory in encounter_category[1]:
# Want to create locations per species, not per slot
# encounter_categories includes info on which slots belong to which subcategory
unique_species = []
for j, species_id in enumerate(encounter_slots):
if j in subcategory[1] and not species_id in unique_species:
unique_species.append(species_id)
# Create a location for the species
for j, species_id in enumerate(unique_species):
encounter_location = PokemonEmeraldLocation(
world.player,
f"{region_name}{'_' + subcategory[0] if subcategory[0] is not None else ''}_{j + 1}",
None,
encounter_region
)
encounter_location.show_in_spoiler = False
# Add access rule
if subcategory[2] is not None:
encounter_location.access_rule = subcategory[2]
# Fill the location with an event for catching that species
encounter_location.place_locked_item(PokemonEmeraldItem(
f"CATCH_{data.species[species_id].name}",
ItemClassification.progression_skip_balancing,
None,
world.player
))
encounter_region.locations.append(encounter_location)
# Add the new encounter region to the multiworld
world.multiworld.regions.append(encounter_region)
# Encounter region exists, just connect to it
region.connect(encounter_region, f"{region.name} -> {region_name}")
regions: Dict[str, Region] = {}
connections: List[Tuple[str, str, str]] = []
for region_name, region_data in data.regions.items():
new_region = Region(region_name, world.player, world.multiworld)
for event_data in region_data.events:
event = PokemonEmeraldLocation(world.player, event_data.name, None, new_region)
event.place_locked_item(PokemonEmeraldItem(event_data.name, ItemClassification.progression, None, world.player))
new_region.locations.append(event)
for region_exit in region_data.exits:
connections.append((f"{region_name} -> {region_exit}", region_name, region_exit))
for warp in region_data.warps:
dest_warp = data.warps[data.warp_map[warp]]
if dest_warp.parent_region is None:
continue
connections.append((warp, region_name, dest_warp.parent_region))
regions[region_name] = new_region
connect_to_map_encounters(new_region, region_data.parent_map.name,
(region_data.has_grass, region_data.has_water, region_data.has_fishing))
for name, source, dest in connections:
regions[source].connect(regions[dest], name)
regions["Menu"] = Region("Menu", world.player, world.multiworld)
regions["Menu"].connect(regions["REGION_LITTLEROOT_TOWN/MAIN"], "Start Game")
return regions