mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
Pokemon Emerald: Implement New Game (#1813)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
|||||||
*.apmc
|
*.apmc
|
||||||
*.apz5
|
*.apz5
|
||||||
*.aptloz
|
*.aptloz
|
||||||
|
*.apemerald
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyd
|
*.pyd
|
||||||
*.sfc
|
*.sfc
|
||||||
|
@@ -52,6 +52,7 @@ Currently, the following games are supported:
|
|||||||
* DOOM 1993
|
* DOOM 1993
|
||||||
* Terraria
|
* Terraria
|
||||||
* Lingo
|
* Lingo
|
||||||
|
* Pokémon Emerald
|
||||||
|
|
||||||
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
||||||
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
||||||
|
@@ -95,6 +95,9 @@
|
|||||||
# Overcooked! 2
|
# Overcooked! 2
|
||||||
/worlds/overcooked2/ @toasterparty
|
/worlds/overcooked2/ @toasterparty
|
||||||
|
|
||||||
|
# Pokemon Emerald
|
||||||
|
/worlds/pokemon_emerald/ @Zunawe
|
||||||
|
|
||||||
# Pokemon Red and Blue
|
# Pokemon Red and Blue
|
||||||
/worlds/pokemon_rb/ @Alchav
|
/worlds/pokemon_rb/ @Alchav
|
||||||
|
|
||||||
|
@@ -153,6 +153,11 @@ Root: HKCR; Subkey: "{#MyAppName}bn3bpatch"; ValueData: "Arc
|
|||||||
Root: HKCR; Subkey: "{#MyAppName}bn3bpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoMMBN3Client.exe,0"; ValueType: string; ValueName: "";
|
Root: HKCR; Subkey: "{#MyAppName}bn3bpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoMMBN3Client.exe,0"; ValueType: string; ValueName: "";
|
||||||
Root: HKCR; Subkey: "{#MyAppName}bn3bpatch\shell\open\command"; ValueData: """{app}\ArchipelagoMMBN3Client.exe"" ""%1"""; ValueType: string; ValueName: "";
|
Root: HKCR; Subkey: "{#MyAppName}bn3bpatch\shell\open\command"; ValueData: """{app}\ArchipelagoMMBN3Client.exe"" ""%1"""; ValueType: string; ValueName: "";
|
||||||
|
|
||||||
|
Root: HKCR; Subkey: ".apemerald"; ValueData: "{#MyAppName}pkmnepatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/bizhawk
|
||||||
|
Root: HKCR; Subkey: "{#MyAppName}pkmnepatch"; ValueData: "Archipelago Pokemon Emerald Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/bizhawk
|
||||||
|
Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; Components: client/bizhawk
|
||||||
|
Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/bizhawk
|
||||||
|
|
||||||
Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
|
Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
|
||||||
Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
|
Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
|
||||||
Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: "";
|
Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: "";
|
||||||
|
19
worlds/pokemon_emerald/LICENSE
Normal file
19
worlds/pokemon_emerald/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2023 Zunawe
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
58
worlds/pokemon_emerald/README.md
Normal file
58
worlds/pokemon_emerald/README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Pokemon Emerald
|
||||||
|
|
||||||
|
Version 1.2.0
|
||||||
|
|
||||||
|
This README contains general info useful for understanding the world. Pretty much all the long lists of locations,
|
||||||
|
regions, and items are stored in `data/` and (mostly) loaded in by `data.py`. Access rules are in `rules.py`. Check
|
||||||
|
[data/README.md](data/README.md) for more detailed information on the JSON files holding most of the data.
|
||||||
|
|
||||||
|
## Warps
|
||||||
|
|
||||||
|
Quick note to start, you should not be defining or modifying encoded warps from this repository. They're encoded in the
|
||||||
|
source code repository for the mod, and then assigned to regions in `data/regions/`. All warps in the game already exist
|
||||||
|
within `extracted_data.json`, and all relevant warps are already placed in `data/regions/` (unless they were deleted
|
||||||
|
accidentally).
|
||||||
|
|
||||||
|
Many warps are actually two or three events acting as one logical warp. Doorways, for example, are often 2 tiles wide
|
||||||
|
indoors but only 1 tile wide outdoors. Both indoor warps point to the outdoor warp, and the outdoor warp points to only
|
||||||
|
one of the indoor warps. We want to describe warps logically in a way that retains information about individual warp
|
||||||
|
events. That way a 2-tile-wide doorway doesnt look like a one-way warp next to an unrelated two-way warp, but if we want
|
||||||
|
to randomize the destinations of those warps, we can still get back each individual id of the multi-tile warp.
|
||||||
|
|
||||||
|
This is how warps are encoded:
|
||||||
|
|
||||||
|
`{source_map}:{source_warp_ids}/{dest_map}:{dest_warp_ids}[!]`
|
||||||
|
|
||||||
|
- `source_map`: The map the warp events are located in
|
||||||
|
- `source_warp_ids`: The ids of all adjacent warp events in source_map which lead to the same destination (these must be
|
||||||
|
in ascending order)
|
||||||
|
- `dest_map`: The map of the warp event to which this one is connected
|
||||||
|
- `dest_warp_ids`: The ids of the warp events in dest_map
|
||||||
|
- `[!]`: If the warp expects to lead to a destination which doesnot lead back to it, add a ! to the end
|
||||||
|
|
||||||
|
Example: `MAP_LAVARIDGE_TOWN_HOUSE:0,1/MAP_LAVARIDGE_TOWN:4`
|
||||||
|
|
||||||
|
Example 2: `MAP_AQUA_HIDEOUT_B1F:14/MAP_AQUA_HIDEOUT_B1F:12!`
|
||||||
|
|
||||||
|
Note: A warp must have its destination set to another warp event. However, that does not guarantee that the destination
|
||||||
|
warp event will warp back to the source.
|
||||||
|
|
||||||
|
Note 2: Some warps _only_ act as destinations and cannot actually be interacted with by the player as sources. These are
|
||||||
|
usually places you fall from a hole above. At the time of writing, these are actually not accounted for, but there are
|
||||||
|
no instances where it changes logical access.
|
||||||
|
|
||||||
|
Note 3: Some warp destinations go to the map `MAP_DYNAMIC` and have a special warp id. These edge cases are:
|
||||||
|
|
||||||
|
- The Moving Truck
|
||||||
|
- Terra Cave
|
||||||
|
- Marine Cave
|
||||||
|
- The Department Store Elevator
|
||||||
|
- Secret Bases
|
||||||
|
- The Trade Center
|
||||||
|
- The Union Room
|
||||||
|
- The Record Corner
|
||||||
|
- 2P/4P Battle Colosseum
|
||||||
|
|
||||||
|
Note 4: The trick house on Route 110 changes the warp destinations of its entrance and ending room as you progress
|
||||||
|
through the puzzles, but the source code only sets the trick house up for the first puzzle, and I assume the destination
|
||||||
|
gets overwritten at run time when certain flags are set.
|
882
worlds/pokemon_emerald/__init__.py
Normal file
882
worlds/pokemon_emerald/__init__.py
Normal file
@@ -0,0 +1,882 @@
|
|||||||
|
"""
|
||||||
|
Archipelago World definition for Pokemon Emerald Version
|
||||||
|
"""
|
||||||
|
from collections import Counter
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar
|
||||||
|
|
||||||
|
from BaseClasses import ItemClassification, MultiWorld, Tutorial
|
||||||
|
from Fill import FillError, fill_restrictive
|
||||||
|
from Options import Toggle
|
||||||
|
import settings
|
||||||
|
from worlds.AutoWorld import WebWorld, World
|
||||||
|
|
||||||
|
from .client import PokemonEmeraldClient # Unused, but required to register with BizHawkClient
|
||||||
|
from .data import (SpeciesData, MapData, EncounterTableData, LearnsetMove, TrainerPokemonData, StaticEncounterData,
|
||||||
|
TrainerData, data as emerald_data)
|
||||||
|
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,
|
||||||
|
create_locations_with_tags)
|
||||||
|
from .options import (ItemPoolType, RandomizeWildPokemon, RandomizeBadges, RandomizeTrainerParties, RandomizeHms,
|
||||||
|
RandomizeStarters, LevelUpMoves, RandomizeAbilities, RandomizeTypes, TmCompatibility,
|
||||||
|
HmCompatibility, RandomizeStaticEncounters, NormanRequirement, PokemonEmeraldOptions)
|
||||||
|
from .pokemon import get_random_species, get_random_move, get_random_damaging_move, get_random_type
|
||||||
|
from .regions import create_regions
|
||||||
|
from .rom import PokemonEmeraldDeltaPatch, generate_output, location_visited_event_to_id_map
|
||||||
|
from .rules import set_rules
|
||||||
|
from .sanity_check import validate_regions
|
||||||
|
from .util import int_to_bool_array, bool_array_to_int
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldWebWorld(WebWorld):
|
||||||
|
"""
|
||||||
|
Webhost info for Pokemon Emerald
|
||||||
|
"""
|
||||||
|
theme = "ocean"
|
||||||
|
setup_en = Tutorial(
|
||||||
|
"Multiworld Setup Guide",
|
||||||
|
"A guide to playing Pokémon Emerald with Archipelago.",
|
||||||
|
"English",
|
||||||
|
"setup_en.md",
|
||||||
|
"setup/en",
|
||||||
|
["Zunawe"]
|
||||||
|
)
|
||||||
|
|
||||||
|
tutorials = [setup_en]
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
md5s = [PokemonEmeraldDeltaPatch.hash]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
data_version = 1
|
||||||
|
required_client_version = (0, 4, 3)
|
||||||
|
|
||||||
|
badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] = None
|
||||||
|
hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] = None
|
||||||
|
free_fly_location_id: int = 0
|
||||||
|
|
||||||
|
modified_species: List[Optional[SpeciesData]]
|
||||||
|
modified_maps: List[MapData]
|
||||||
|
modified_tmhm_moves: List[int]
|
||||||
|
modified_static_encounters: List[int]
|
||||||
|
modified_starters: Tuple[int, int, int]
|
||||||
|
modified_trainers: List[TrainerData]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
|
||||||
|
if not os.path.exists(cls.settings.rom_file):
|
||||||
|
raise FileNotFoundError(cls.settings.rom_file)
|
||||||
|
|
||||||
|
assert validate_regions()
|
||||||
|
|
||||||
|
def get_filler_item_name(self) -> str:
|
||||||
|
return "Great Ball"
|
||||||
|
|
||||||
|
def generate_early(self) -> None:
|
||||||
|
# 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.
|
||||||
|
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 "
|
||||||
|
"other settings. Reducing to 4.", self.player, self.multiworld.get_player_name(self.player))
|
||||||
|
self.options.norman_count.value = max_norman_count
|
||||||
|
|
||||||
|
def create_regions(self) -> None:
|
||||||
|
regions = create_regions(self)
|
||||||
|
|
||||||
|
tags = {"Badge", "HM", "KeyItem", "Rod", "Bike"}
|
||||||
|
if self.options.overworld_items:
|
||||||
|
tags.add("OverworldItem")
|
||||||
|
if self.options.hidden_items:
|
||||||
|
tags.add("HiddenItem")
|
||||||
|
if self.options.npc_gifts:
|
||||||
|
tags.add("NpcGift")
|
||||||
|
if self.options.enable_ferry:
|
||||||
|
tags.add("Ferry")
|
||||||
|
create_locations_with_tags(self, regions, tags)
|
||||||
|
|
||||||
|
self.multiworld.regions.extend(regions.values())
|
||||||
|
|
||||||
|
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
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filter progression items which shouldn't be shuffled into the itempool. Their locations
|
||||||
|
# still exist, but event items will be placed and locked at their vanilla locations instead.
|
||||||
|
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")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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]
|
||||||
|
]
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
if self.options.item_pool_type == ItemPoolType.option_shuffled:
|
||||||
|
self.multiworld.itempool += default_itempool
|
||||||
|
|
||||||
|
elif self.options.item_pool_type in {ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced}:
|
||||||
|
item_categories = ["Ball", "Heal", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc"]
|
||||||
|
|
||||||
|
# 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"])
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
set_rules(self)
|
||||||
|
|
||||||
|
def generate_basic(self) -> None:
|
||||||
|
locations: List[PokemonEmeraldLocation] = self.multiworld.get_locations(self.player)
|
||||||
|
|
||||||
|
# Set our free fly location
|
||||||
|
# If not enabled, set it to Littleroot Town by default
|
||||||
|
fly_location_name = "EVENT_VISITED_LITTLEROOT_TOWN"
|
||||||
|
if self.options.free_fly_location:
|
||||||
|
fly_location_name = self.random.choice([
|
||||||
|
"EVENT_VISITED_SLATEPORT_CITY",
|
||||||
|
"EVENT_VISITED_MAUVILLE_CITY",
|
||||||
|
"EVENT_VISITED_VERDANTURF_TOWN",
|
||||||
|
"EVENT_VISITED_FALLARBOR_TOWN",
|
||||||
|
"EVENT_VISITED_LAVARIDGE_TOWN",
|
||||||
|
"EVENT_VISITED_FORTREE_CITY",
|
||||||
|
"EVENT_VISITED_LILYCOVE_CITY",
|
||||||
|
"EVENT_VISITED_MOSSDEEP_CITY",
|
||||||
|
"EVENT_VISITED_SOOTOPOLIS_CITY",
|
||||||
|
"EVENT_VISITED_EVER_GRANDE_CITY"
|
||||||
|
])
|
||||||
|
|
||||||
|
self.free_fly_location_id = location_visited_event_to_id_map[fly_location_name]
|
||||||
|
|
||||||
|
free_fly_location_location = self.multiworld.get_location("FREE_FLY_LOCATION", self.player)
|
||||||
|
free_fly_location_location.item = None
|
||||||
|
free_fly_location_location.place_locked_item(self.create_event(fly_location_name))
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
for location in locations:
|
||||||
|
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]))
|
||||||
|
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")
|
||||||
|
if not self.options.key_items:
|
||||||
|
convert_unrandomized_items_to_events("KeyItem")
|
||||||
|
|
||||||
|
def pre_fill(self) -> None:
|
||||||
|
# Items which are shuffled between their own locations
|
||||||
|
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.
|
||||||
|
# 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.
|
||||||
|
badge_locations, badge_items = [list(l) for l in zip(*self.badge_shuffle_info)]
|
||||||
|
badge_priority = {
|
||||||
|
"Knuckle Badge": 0 if (self.options.hms == RandomizeHms.option_vanilla and self.options.require_flash) else 3,
|
||||||
|
"Balance Badge": 1,
|
||||||
|
"Dynamo Badge": 1,
|
||||||
|
"Mind Badge": 2,
|
||||||
|
"Heat Badge": 2,
|
||||||
|
"Rain Badge": 3,
|
||||||
|
"Stone Badge": 4,
|
||||||
|
"Feather Badge": 5
|
||||||
|
}
|
||||||
|
badge_items.sort(key=lambda item: badge_priority.get(item.name, 0))
|
||||||
|
|
||||||
|
collection_state = self.multiworld.get_all_state(False)
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
# 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.
|
||||||
|
hm_locations, hm_items = [list(l) for l in zip(*self.hm_shuffle_info)]
|
||||||
|
hm_priority = {
|
||||||
|
"HM05 Flash": 0 if (self.options.badges == RandomizeBadges.option_vanilla and self.options.require_flash) else 3,
|
||||||
|
"HM03 Surf": 1,
|
||||||
|
"HM06 Rock Smash": 1,
|
||||||
|
"HM08 Dive": 2,
|
||||||
|
"HM04 Strength": 2,
|
||||||
|
"HM07 Waterfall": 3,
|
||||||
|
"HM01 Cut": 4,
|
||||||
|
"HM02 Fly": 5
|
||||||
|
}
|
||||||
|
hm_items.sort(key=lambda item: hm_priority.get(item.name, 0))
|
||||||
|
|
||||||
|
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:
|
||||||
|
def randomize_abilities() -> None:
|
||||||
|
# Creating list of potential abilities
|
||||||
|
ability_label_to_value = {ability.label.lower(): ability.ability_id for ability in emerald_data.abilities}
|
||||||
|
|
||||||
|
ability_blacklist_labels = {"cacophony"}
|
||||||
|
option_ability_blacklist = self.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 emerald_data.abilities if a.ability_id not in ability_blacklist]
|
||||||
|
|
||||||
|
if self.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 self.modified_species:
|
||||||
|
if species is None:
|
||||||
|
continue
|
||||||
|
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
|
||||||
|
new_abilities = (
|
||||||
|
0 if old_abilities[0] == 0 else self.random.choice(ability_whitelist),
|
||||||
|
0 if old_abilities[1] == 0 else self.random.choice(ability_whitelist)
|
||||||
|
)
|
||||||
|
|
||||||
|
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 += [
|
||||||
|
self.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 self.modified_species:
|
||||||
|
if species is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
old_abilities = species.abilities
|
||||||
|
new_abilities = (
|
||||||
|
0 if old_abilities[0] == 0 else self.random.choice(ability_whitelist),
|
||||||
|
0 if old_abilities[1] == 0 else self.random.choice(ability_whitelist)
|
||||||
|
)
|
||||||
|
|
||||||
|
species.abilities = new_abilities
|
||||||
|
|
||||||
|
def randomize_types() -> None:
|
||||||
|
if self.options.types == RandomizeTypes.option_shuffle:
|
||||||
|
type_map = list(range(18))
|
||||||
|
self.random.shuffle(type_map)
|
||||||
|
|
||||||
|
# We never want to map to the ??? type, so swap whatever index maps to ??? with ???
|
||||||
|
# So ??? will always map to itself, and 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 self.modified_species:
|
||||||
|
if species is not None:
|
||||||
|
species.types = (type_map[species.types[0]], type_map[species.types[1]])
|
||||||
|
elif self.options.types == RandomizeTypes.option_completely_random:
|
||||||
|
for species in self.modified_species:
|
||||||
|
if species is not None:
|
||||||
|
new_type_1 = get_random_type(self.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(self.random)
|
||||||
|
|
||||||
|
species.types = (new_type_1, new_type_2)
|
||||||
|
elif self.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 self.modified_species:
|
||||||
|
if species is None:
|
||||||
|
continue
|
||||||
|
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))
|
||||||
|
self.random.shuffle(type_map)
|
||||||
|
|
||||||
|
# We never want to map to the ??? type, so swap whatever index maps to ??? with ???
|
||||||
|
# So ??? will always map to itself, and 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 += [self.modified_species[evo.species_id] for evo in evolution.evolutions]
|
||||||
|
|
||||||
|
def randomize_learnsets() -> None:
|
||||||
|
type_bias = self.options.move_match_type_bias.value
|
||||||
|
normal_bias = self.options.move_normal_type_bias.value
|
||||||
|
|
||||||
|
for species in self.modified_species:
|
||||||
|
if species is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
old_learnset = species.learnset
|
||||||
|
new_learnset: List[LearnsetMove] = []
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
# Replace filler MOVE_NONEs at start of list
|
||||||
|
while old_learnset[i].move_id == 0:
|
||||||
|
if self.options.level_up_moves == LevelUpMoves.option_start_with_four_moves:
|
||||||
|
new_move = get_random_move(self.random, {move.move_id for move in new_learnset}, type_bias,
|
||||||
|
normal_bias, species.types)
|
||||||
|
else:
|
||||||
|
new_move = 0
|
||||||
|
new_learnset.append(LearnsetMove(old_learnset[i].level, new_move))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
while i < len(old_learnset):
|
||||||
|
# Guarantees the starter has a good damaging move
|
||||||
|
if i == 3:
|
||||||
|
new_move = get_random_damaging_move(self.random, {move.move_id for move in new_learnset})
|
||||||
|
else:
|
||||||
|
new_move = get_random_move(self.random, {move.move_id for move in new_learnset}, type_bias,
|
||||||
|
normal_bias, species.types)
|
||||||
|
new_learnset.append(LearnsetMove(old_learnset[i].level, new_move))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
species.learnset = new_learnset
|
||||||
|
|
||||||
|
def randomize_tm_hm_compatibility() -> None:
|
||||||
|
for species in self.modified_species:
|
||||||
|
if species is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
combatibility_array = int_to_bool_array(species.tm_hm_compatibility)
|
||||||
|
|
||||||
|
# TMs
|
||||||
|
for i in range(0, 50):
|
||||||
|
if self.options.tm_compatibility == TmCompatibility.option_fully_compatible:
|
||||||
|
combatibility_array[i] = True
|
||||||
|
elif self.options.tm_compatibility == TmCompatibility.option_completely_random:
|
||||||
|
combatibility_array[i] = self.random.choice([True, False])
|
||||||
|
|
||||||
|
# HMs
|
||||||
|
for i in range(50, 58):
|
||||||
|
if self.options.hm_compatibility == HmCompatibility.option_fully_compatible:
|
||||||
|
combatibility_array[i] = True
|
||||||
|
elif self.options.hm_compatibility == HmCompatibility.option_completely_random:
|
||||||
|
combatibility_array[i] = self.random.choice([True, False])
|
||||||
|
|
||||||
|
species.tm_hm_compatibility = bool_array_to_int(combatibility_array)
|
||||||
|
|
||||||
|
def randomize_tm_moves() -> None:
|
||||||
|
new_moves: Set[int] = set()
|
||||||
|
|
||||||
|
for i in range(50):
|
||||||
|
new_move = get_random_move(self.random, new_moves)
|
||||||
|
new_moves.add(new_move)
|
||||||
|
self.modified_tmhm_moves[i] = new_move
|
||||||
|
|
||||||
|
def randomize_wild_encounters() -> None:
|
||||||
|
should_match_bst = self.options.wild_pokemon in {
|
||||||
|
RandomizeWildPokemon.option_match_base_stats,
|
||||||
|
RandomizeWildPokemon.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
should_match_type = self.options.wild_pokemon in {
|
||||||
|
RandomizeWildPokemon.option_match_type,
|
||||||
|
RandomizeWildPokemon.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
should_allow_legendaries = self.options.allow_wild_legendaries == Toggle.option_true
|
||||||
|
|
||||||
|
for map_data in self.modified_maps:
|
||||||
|
new_encounters: List[Optional[EncounterTableData]] = [None, None, None]
|
||||||
|
old_encounters = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters]
|
||||||
|
|
||||||
|
for i, table in enumerate(old_encounters):
|
||||||
|
if table is not None:
|
||||||
|
species_old_to_new_map: Dict[int, int] = {}
|
||||||
|
for species_id in table.slots:
|
||||||
|
if species_id not in species_old_to_new_map:
|
||||||
|
original_species = emerald_data.species[species_id]
|
||||||
|
target_bst = sum(original_species.base_stats) if should_match_bst else None
|
||||||
|
target_type = self.random.choice(original_species.types) if should_match_type else None
|
||||||
|
|
||||||
|
species_old_to_new_map[species_id] = get_random_species(
|
||||||
|
self.random,
|
||||||
|
self.modified_species,
|
||||||
|
target_bst,
|
||||||
|
target_type,
|
||||||
|
should_allow_legendaries
|
||||||
|
).species_id
|
||||||
|
|
||||||
|
new_slots: List[int] = []
|
||||||
|
for species_id in table.slots:
|
||||||
|
new_slots.append(species_old_to_new_map[species_id])
|
||||||
|
|
||||||
|
new_encounters[i] = EncounterTableData(new_slots, table.rom_address)
|
||||||
|
|
||||||
|
map_data.land_encounters = new_encounters[0]
|
||||||
|
map_data.water_encounters = new_encounters[1]
|
||||||
|
map_data.fishing_encounters = new_encounters[2]
|
||||||
|
|
||||||
|
def randomize_static_encounters() -> None:
|
||||||
|
if self.options.static_encounters == RandomizeStaticEncounters.option_shuffle:
|
||||||
|
shuffled_species = [encounter.species_id for encounter in emerald_data.static_encounters]
|
||||||
|
self.random.shuffle(shuffled_species)
|
||||||
|
|
||||||
|
self.modified_static_encounters = []
|
||||||
|
for i, encounter in enumerate(emerald_data.static_encounters):
|
||||||
|
self.modified_static_encounters.append(StaticEncounterData(
|
||||||
|
shuffled_species[i],
|
||||||
|
encounter.rom_address
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
should_match_bst = self.options.static_encounters in {
|
||||||
|
RandomizeStaticEncounters.option_match_base_stats,
|
||||||
|
RandomizeStaticEncounters.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
should_match_type = self.options.static_encounters in {
|
||||||
|
RandomizeStaticEncounters.option_match_type,
|
||||||
|
RandomizeStaticEncounters.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
|
||||||
|
for encounter in emerald_data.static_encounters:
|
||||||
|
original_species = self.modified_species[encounter.species_id]
|
||||||
|
target_bst = sum(original_species.base_stats) if should_match_bst else None
|
||||||
|
target_type = self.random.choice(original_species.types) if should_match_type else None
|
||||||
|
|
||||||
|
self.modified_static_encounters.append(StaticEncounterData(
|
||||||
|
get_random_species(self.random, self.modified_species, target_bst, target_type).species_id,
|
||||||
|
encounter.rom_address
|
||||||
|
))
|
||||||
|
|
||||||
|
def randomize_opponent_parties() -> None:
|
||||||
|
should_match_bst = self.options.trainer_parties in {
|
||||||
|
RandomizeTrainerParties.option_match_base_stats,
|
||||||
|
RandomizeTrainerParties.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
should_match_type = self.options.trainer_parties in {
|
||||||
|
RandomizeTrainerParties.option_match_type,
|
||||||
|
RandomizeTrainerParties.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
allow_legendaries = self.options.allow_trainer_legendaries == Toggle.option_true
|
||||||
|
|
||||||
|
per_species_tmhm_moves: Dict[int, List[int]] = {}
|
||||||
|
|
||||||
|
for trainer in self.modified_trainers:
|
||||||
|
new_party = []
|
||||||
|
for pokemon in trainer.party.pokemon:
|
||||||
|
original_species = emerald_data.species[pokemon.species_id]
|
||||||
|
target_bst = sum(original_species.base_stats) if should_match_bst else None
|
||||||
|
target_type = self.random.choice(original_species.types) if should_match_type else None
|
||||||
|
|
||||||
|
new_species = get_random_species(
|
||||||
|
self.random,
|
||||||
|
self.modified_species,
|
||||||
|
target_bst,
|
||||||
|
target_type,
|
||||||
|
allow_legendaries
|
||||||
|
)
|
||||||
|
|
||||||
|
if new_species.species_id not in per_species_tmhm_moves:
|
||||||
|
per_species_tmhm_moves[new_species.species_id] = list({
|
||||||
|
self.modified_tmhm_moves[i]
|
||||||
|
for i, is_compatible in enumerate(int_to_bool_array(new_species.tm_hm_compatibility))
|
||||||
|
if is_compatible
|
||||||
|
})
|
||||||
|
|
||||||
|
tm_hm_movepool = per_species_tmhm_moves[new_species.species_id]
|
||||||
|
level_up_movepool = list({
|
||||||
|
move.move_id
|
||||||
|
for move in new_species.learnset
|
||||||
|
if move.level <= pokemon.level
|
||||||
|
})
|
||||||
|
|
||||||
|
new_moves = (
|
||||||
|
self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool),
|
||||||
|
self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool),
|
||||||
|
self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool),
|
||||||
|
self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool)
|
||||||
|
)
|
||||||
|
|
||||||
|
new_party.append(TrainerPokemonData(new_species.species_id, pokemon.level, new_moves))
|
||||||
|
|
||||||
|
trainer.party.pokemon = new_party
|
||||||
|
|
||||||
|
def randomize_starters() -> None:
|
||||||
|
match_bst = self.options.starters in {
|
||||||
|
RandomizeStarters.option_match_base_stats,
|
||||||
|
RandomizeStarters.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
match_type = self.options.starters in {
|
||||||
|
RandomizeStarters.option_match_type,
|
||||||
|
RandomizeStarters.option_match_base_stats_and_type
|
||||||
|
}
|
||||||
|
allow_legendaries = self.options.allow_starter_legendaries == Toggle.option_true
|
||||||
|
|
||||||
|
starter_bsts = (
|
||||||
|
sum(emerald_data.species[emerald_data.starters[0]].base_stats) if match_bst else None,
|
||||||
|
sum(emerald_data.species[emerald_data.starters[1]].base_stats) if match_bst else None,
|
||||||
|
sum(emerald_data.species[emerald_data.starters[2]].base_stats) if match_bst else None
|
||||||
|
)
|
||||||
|
|
||||||
|
starter_types = (
|
||||||
|
self.random.choice(emerald_data.species[emerald_data.starters[0]].types) if match_type else None,
|
||||||
|
self.random.choice(emerald_data.species[emerald_data.starters[1]].types) if match_type else None,
|
||||||
|
self.random.choice(emerald_data.species[emerald_data.starters[2]].types) if match_type else None
|
||||||
|
)
|
||||||
|
|
||||||
|
new_starters = (
|
||||||
|
get_random_species(self.random, self.modified_species,
|
||||||
|
starter_bsts[0], starter_types[0], allow_legendaries),
|
||||||
|
get_random_species(self.random, self.modified_species,
|
||||||
|
starter_bsts[1], starter_types[1], allow_legendaries),
|
||||||
|
get_random_species(self.random, self.modified_species,
|
||||||
|
starter_bsts[2], starter_types[2], allow_legendaries)
|
||||||
|
)
|
||||||
|
|
||||||
|
egg_code = self.options.easter_egg.value
|
||||||
|
egg_check_1 = 0
|
||||||
|
egg_check_2 = 0
|
||||||
|
|
||||||
|
for i in egg_code:
|
||||||
|
egg_check_1 += ord(i)
|
||||||
|
egg_check_2 += egg_check_1 * egg_check_1
|
||||||
|
|
||||||
|
egg = 96 + egg_check_2 - (egg_check_1 * 0x077C)
|
||||||
|
if egg_check_2 == 0x14E03A and egg < 411 and egg > 0 and egg not in range(252, 277):
|
||||||
|
self.modified_starters = (egg, egg, egg)
|
||||||
|
else:
|
||||||
|
self.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
|
||||||
|
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 = self.random.choice(potential_evolutions)
|
||||||
|
|
||||||
|
for trainer_name, starter_position, is_evolved in rival_teams[i]:
|
||||||
|
trainer_data = self.modified_trainers[emerald_data.constants[trainer_name]]
|
||||||
|
trainer_data.party.pokemon[starter_position].species_id = picked_evolution if is_evolved else starter.species_id
|
||||||
|
|
||||||
|
self.modified_species = copy.deepcopy(emerald_data.species)
|
||||||
|
self.modified_trainers = copy.deepcopy(emerald_data.trainers)
|
||||||
|
self.modified_maps = copy.deepcopy(emerald_data.maps)
|
||||||
|
self.modified_tmhm_moves = copy.deepcopy(emerald_data.tmhm_moves)
|
||||||
|
self.modified_static_encounters = copy.deepcopy(emerald_data.static_encounters)
|
||||||
|
self.modified_starters = copy.deepcopy(emerald_data.starters)
|
||||||
|
|
||||||
|
# Randomize species data
|
||||||
|
if self.options.abilities != RandomizeAbilities.option_vanilla:
|
||||||
|
randomize_abilities()
|
||||||
|
|
||||||
|
if self.options.types != RandomizeTypes.option_vanilla:
|
||||||
|
randomize_types()
|
||||||
|
|
||||||
|
if self.options.level_up_moves != LevelUpMoves.option_vanilla:
|
||||||
|
randomize_learnsets()
|
||||||
|
|
||||||
|
randomize_tm_hm_compatibility() # Options are checked within this function
|
||||||
|
|
||||||
|
min_catch_rate = min(self.options.min_catch_rate.value, 255)
|
||||||
|
for species in self.modified_species:
|
||||||
|
if species is not None:
|
||||||
|
species.catch_rate = max(species.catch_rate, min_catch_rate)
|
||||||
|
|
||||||
|
if self.options.tm_moves:
|
||||||
|
randomize_tm_moves()
|
||||||
|
|
||||||
|
# Randomize wild encounters
|
||||||
|
if self.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
|
||||||
|
randomize_wild_encounters()
|
||||||
|
|
||||||
|
# Randomize static encounters
|
||||||
|
if self.options.static_encounters != RandomizeStaticEncounters.option_vanilla:
|
||||||
|
randomize_static_encounters()
|
||||||
|
|
||||||
|
# Randomize opponents
|
||||||
|
if self.options.trainer_parties != RandomizeTrainerParties.option_vanilla:
|
||||||
|
randomize_opponent_parties()
|
||||||
|
|
||||||
|
# Randomize starters
|
||||||
|
if self.options.starters != RandomizeStarters.option_vanilla:
|
||||||
|
randomize_starters()
|
||||||
|
|
||||||
|
generate_output(self, output_directory)
|
||||||
|
|
||||||
|
def fill_slot_data(self) -> Dict[str, Any]:
|
||||||
|
slot_data = self.options.as_dict(
|
||||||
|
"goal",
|
||||||
|
"badges",
|
||||||
|
"hms",
|
||||||
|
"key_items",
|
||||||
|
"bikes",
|
||||||
|
"rods",
|
||||||
|
"overworld_items",
|
||||||
|
"hidden_items",
|
||||||
|
"npc_gifts",
|
||||||
|
"require_itemfinder",
|
||||||
|
"require_flash",
|
||||||
|
"enable_ferry",
|
||||||
|
"elite_four_requirement",
|
||||||
|
"elite_four_count",
|
||||||
|
"norman_requirement",
|
||||||
|
"norman_count",
|
||||||
|
"extra_boulders",
|
||||||
|
"remove_roadblocks",
|
||||||
|
"free_fly_location",
|
||||||
|
"fly_without_badge",
|
||||||
|
)
|
||||||
|
slot_data["free_fly_location_id"] = self.free_fly_location_id
|
||||||
|
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
|
||||||
|
)
|
277
worlds/pokemon_emerald/client.py
Normal file
277
worlds/pokemon_emerald/client.py
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
from typing import TYPE_CHECKING, Dict, Set
|
||||||
|
|
||||||
|
from NetUtils import ClientStatus
|
||||||
|
import worlds._bizhawk as bizhawk
|
||||||
|
from worlds._bizhawk.client import BizHawkClient
|
||||||
|
|
||||||
|
from .data import BASE_OFFSET, data
|
||||||
|
from .options import Goal
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from worlds._bizhawk.context import BizHawkClientContext
|
||||||
|
|
||||||
|
|
||||||
|
EXPECTED_ROM_NAME = "pokemon emerald version / AP 2"
|
||||||
|
|
||||||
|
IS_CHAMPION_FLAG = data.constants["FLAG_IS_CHAMPION"]
|
||||||
|
DEFEATED_STEVEN_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_STEVEN"]
|
||||||
|
DEFEATED_NORMAN_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_NORMAN_1"]
|
||||||
|
|
||||||
|
# These flags are communicated to the tracker as a bitfield using this order.
|
||||||
|
# Modifying the order will cause undetectable autotracking issues.
|
||||||
|
TRACKER_EVENT_FLAGS = [
|
||||||
|
"FLAG_DEFEATED_RUSTBORO_GYM",
|
||||||
|
"FLAG_DEFEATED_DEWFORD_GYM",
|
||||||
|
"FLAG_DEFEATED_MAUVILLE_GYM",
|
||||||
|
"FLAG_DEFEATED_LAVARIDGE_GYM",
|
||||||
|
"FLAG_DEFEATED_PETALBURG_GYM",
|
||||||
|
"FLAG_DEFEATED_FORTREE_GYM",
|
||||||
|
"FLAG_DEFEATED_MOSSDEEP_GYM",
|
||||||
|
"FLAG_DEFEATED_SOOTOPOLIS_GYM",
|
||||||
|
"FLAG_RECEIVED_POKENAV", # Talk to Mr. Stone
|
||||||
|
"FLAG_DELIVERED_STEVEN_LETTER",
|
||||||
|
"FLAG_DELIVERED_DEVON_GOODS",
|
||||||
|
"FLAG_HIDE_ROUTE_119_TEAM_AQUA", # Clear Weather Institute
|
||||||
|
"FLAG_MET_ARCHIE_METEOR_FALLS", # Magma steals meteorite
|
||||||
|
"FLAG_GROUDON_AWAKENED_MAGMA_HIDEOUT", # Clear Magma Hideout
|
||||||
|
"FLAG_MET_TEAM_AQUA_HARBOR", # Aqua steals submarine
|
||||||
|
"FLAG_TEAM_AQUA_ESCAPED_IN_SUBMARINE", # Clear Aqua Hideout
|
||||||
|
"FLAG_HIDE_MOSSDEEP_CITY_SPACE_CENTER_MAGMA_NOTE", # Clear Space Center
|
||||||
|
"FLAG_KYOGRE_ESCAPED_SEAFLOOR_CAVERN",
|
||||||
|
"FLAG_HIDE_SKY_PILLAR_TOP_RAYQUAZA", # Rayquaza departs for Sootopolis
|
||||||
|
"FLAG_OMIT_DIVE_FROM_STEVEN_LETTER", # Steven gives Dive HM (clears seafloor cavern grunt)
|
||||||
|
"FLAG_IS_CHAMPION",
|
||||||
|
"FLAG_PURCHASED_HARBOR_MAIL"
|
||||||
|
]
|
||||||
|
EVENT_FLAG_MAP = {data.constants[flag_name]: flag_name for flag_name in TRACKER_EVENT_FLAGS}
|
||||||
|
|
||||||
|
KEY_LOCATION_FLAGS = [
|
||||||
|
"NPC_GIFT_RECEIVED_HM01",
|
||||||
|
"NPC_GIFT_RECEIVED_HM02",
|
||||||
|
"NPC_GIFT_RECEIVED_HM03",
|
||||||
|
"NPC_GIFT_RECEIVED_HM04",
|
||||||
|
"NPC_GIFT_RECEIVED_HM05",
|
||||||
|
"NPC_GIFT_RECEIVED_HM06",
|
||||||
|
"NPC_GIFT_RECEIVED_HM07",
|
||||||
|
"NPC_GIFT_RECEIVED_HM08",
|
||||||
|
"NPC_GIFT_RECEIVED_ACRO_BIKE",
|
||||||
|
"NPC_GIFT_RECEIVED_WAILMER_PAIL",
|
||||||
|
"NPC_GIFT_RECEIVED_DEVON_GOODS_RUSTURF_TUNNEL",
|
||||||
|
"NPC_GIFT_RECEIVED_LETTER",
|
||||||
|
"NPC_GIFT_RECEIVED_METEORITE",
|
||||||
|
"NPC_GIFT_RECEIVED_GO_GOGGLES",
|
||||||
|
"NPC_GIFT_GOT_BASEMENT_KEY_FROM_WATTSON",
|
||||||
|
"NPC_GIFT_RECEIVED_ITEMFINDER",
|
||||||
|
"NPC_GIFT_RECEIVED_DEVON_SCOPE",
|
||||||
|
"NPC_GIFT_RECEIVED_MAGMA_EMBLEM",
|
||||||
|
"NPC_GIFT_RECEIVED_POKEBLOCK_CASE",
|
||||||
|
"NPC_GIFT_RECEIVED_SS_TICKET",
|
||||||
|
"HIDDEN_ITEM_ABANDONED_SHIP_RM_2_KEY",
|
||||||
|
"HIDDEN_ITEM_ABANDONED_SHIP_RM_1_KEY",
|
||||||
|
"HIDDEN_ITEM_ABANDONED_SHIP_RM_4_KEY",
|
||||||
|
"HIDDEN_ITEM_ABANDONED_SHIP_RM_6_KEY",
|
||||||
|
"ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_4_SCANNER",
|
||||||
|
"ITEM_ABANDONED_SHIP_CAPTAINS_OFFICE_STORAGE_KEY",
|
||||||
|
"NPC_GIFT_RECEIVED_OLD_ROD",
|
||||||
|
"NPC_GIFT_RECEIVED_GOOD_ROD",
|
||||||
|
"NPC_GIFT_RECEIVED_SUPER_ROD",
|
||||||
|
]
|
||||||
|
KEY_LOCATION_FLAG_MAP = {data.locations[location_name].flag: location_name for location_name in KEY_LOCATION_FLAGS}
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldClient(BizHawkClient):
|
||||||
|
game = "Pokemon Emerald"
|
||||||
|
system = "GBA"
|
||||||
|
patch_suffix = ".apemerald"
|
||||||
|
local_checked_locations: Set[int]
|
||||||
|
local_set_events: Dict[str, bool]
|
||||||
|
local_found_key_items: Dict[str, bool]
|
||||||
|
goal_flag: int
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.local_checked_locations = set()
|
||||||
|
self.local_set_events = {}
|
||||||
|
self.local_found_key_items = {}
|
||||||
|
self.goal_flag = IS_CHAMPION_FLAG
|
||||||
|
|
||||||
|
async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
|
||||||
|
from CommonClient import logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check ROM name/patch version
|
||||||
|
rom_name_bytes = ((await bizhawk.read(ctx.bizhawk_ctx, [(0x108, 32, "ROM")]))[0])
|
||||||
|
rom_name = bytes([byte for byte in rom_name_bytes if byte != 0]).decode("ascii")
|
||||||
|
if not rom_name.startswith("pokemon emerald version"):
|
||||||
|
return False
|
||||||
|
if rom_name == "pokemon emerald version":
|
||||||
|
logger.info("ERROR: You appear to be running an unpatched version of Pokemon Emerald. "
|
||||||
|
"You need to generate a patch file and use it to create a patched ROM.")
|
||||||
|
return False
|
||||||
|
if rom_name != EXPECTED_ROM_NAME:
|
||||||
|
logger.info("ERROR: The patch file used to create this ROM is not compatible with "
|
||||||
|
"this client. Double check your client version against the version being "
|
||||||
|
"used by the generator.")
|
||||||
|
return False
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return False
|
||||||
|
except bizhawk.RequestFailedError:
|
||||||
|
return False # Should verify on the next pass
|
||||||
|
|
||||||
|
ctx.game = self.game
|
||||||
|
ctx.items_handling = 0b001
|
||||||
|
ctx.want_slot_data = True
|
||||||
|
ctx.watcher_timeout = 0.125
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def set_auth(self, ctx: "BizHawkClientContext") -> None:
|
||||||
|
slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(data.rom_addresses["gArchipelagoInfo"], 64, "ROM")]))[0]
|
||||||
|
ctx.auth = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8")
|
||||||
|
|
||||||
|
async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
|
||||||
|
if ctx.slot_data is not None:
|
||||||
|
if ctx.slot_data["goal"] == Goal.option_champion:
|
||||||
|
self.goal_flag = IS_CHAMPION_FLAG
|
||||||
|
elif ctx.slot_data["goal"] == Goal.option_steven:
|
||||||
|
self.goal_flag = DEFEATED_STEVEN_FLAG
|
||||||
|
elif ctx.slot_data["goal"] == Goal.option_norman:
|
||||||
|
self.goal_flag = DEFEATED_NORMAN_FLAG
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Checks that the player is in the overworld
|
||||||
|
overworld_guard = (data.ram_addresses["gMain"] + 4, (data.ram_addresses["CB2_Overworld"] + 1).to_bytes(4, "little"), "System Bus")
|
||||||
|
|
||||||
|
# Read save block address
|
||||||
|
read_result = await bizhawk.guarded_read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[(data.ram_addresses["gSaveBlock1Ptr"], 4, "System Bus")],
|
||||||
|
[overworld_guard]
|
||||||
|
)
|
||||||
|
if read_result is None: # Not in overworld
|
||||||
|
return
|
||||||
|
|
||||||
|
# Checks that the save block hasn't moved
|
||||||
|
save_block_address_guard = (data.ram_addresses["gSaveBlock1Ptr"], read_result[0], "System Bus")
|
||||||
|
|
||||||
|
save_block_address = int.from_bytes(read_result[0], "little")
|
||||||
|
|
||||||
|
# Handle giving the player items
|
||||||
|
read_result = await bizhawk.guarded_read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[
|
||||||
|
(save_block_address + 0x3778, 2, "System Bus"), # Number of received items
|
||||||
|
(data.ram_addresses["gArchipelagoReceivedItem"] + 4, 1, "System Bus") # Received item struct full?
|
||||||
|
],
|
||||||
|
[overworld_guard, save_block_address_guard]
|
||||||
|
)
|
||||||
|
if read_result is None: # Not in overworld, or save block moved
|
||||||
|
return
|
||||||
|
|
||||||
|
num_received_items = int.from_bytes(read_result[0], "little")
|
||||||
|
received_item_is_empty = read_result[1][0] == 0
|
||||||
|
|
||||||
|
# If the game hasn't received all items yet and the received item struct doesn't contain an item, then
|
||||||
|
# fill it with the next item
|
||||||
|
if num_received_items < len(ctx.items_received) and received_item_is_empty:
|
||||||
|
next_item = ctx.items_received[num_received_items]
|
||||||
|
await bizhawk.write(ctx.bizhawk_ctx, [
|
||||||
|
(data.ram_addresses["gArchipelagoReceivedItem"] + 0, (next_item.item - BASE_OFFSET).to_bytes(2, "little"), "System Bus"),
|
||||||
|
(data.ram_addresses["gArchipelagoReceivedItem"] + 2, (num_received_items + 1).to_bytes(2, "little"), "System Bus"),
|
||||||
|
(data.ram_addresses["gArchipelagoReceivedItem"] + 4, [1], "System Bus"), # Mark struct full
|
||||||
|
(data.ram_addresses["gArchipelagoReceivedItem"] + 5, [next_item.flags & 1], "System Bus"),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Read flags in 2 chunks
|
||||||
|
read_result = await bizhawk.guarded_read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[(save_block_address + 0x1450, 0x96, "System Bus")], # Flags
|
||||||
|
[overworld_guard, save_block_address_guard]
|
||||||
|
)
|
||||||
|
if read_result is None: # Not in overworld, or save block moved
|
||||||
|
return
|
||||||
|
|
||||||
|
flag_bytes = read_result[0]
|
||||||
|
|
||||||
|
read_result = await bizhawk.guarded_read(
|
||||||
|
ctx.bizhawk_ctx,
|
||||||
|
[(save_block_address + 0x14E6, 0x96, "System Bus")], # Flags
|
||||||
|
[overworld_guard, save_block_address_guard]
|
||||||
|
)
|
||||||
|
if read_result is not None:
|
||||||
|
flag_bytes += read_result[0]
|
||||||
|
|
||||||
|
game_clear = False
|
||||||
|
local_checked_locations = set()
|
||||||
|
local_set_events = {flag_name: False for flag_name in TRACKER_EVENT_FLAGS}
|
||||||
|
local_found_key_items = {location_name: False for location_name in KEY_LOCATION_FLAGS}
|
||||||
|
|
||||||
|
# Check set flags
|
||||||
|
for byte_i, byte in enumerate(flag_bytes):
|
||||||
|
for i in range(8):
|
||||||
|
if byte & (1 << i) != 0:
|
||||||
|
flag_id = byte_i * 8 + i
|
||||||
|
|
||||||
|
location_id = flag_id + BASE_OFFSET
|
||||||
|
if location_id in ctx.server_locations:
|
||||||
|
local_checked_locations.add(location_id)
|
||||||
|
|
||||||
|
if flag_id == self.goal_flag:
|
||||||
|
game_clear = True
|
||||||
|
|
||||||
|
if flag_id in EVENT_FLAG_MAP:
|
||||||
|
local_set_events[EVENT_FLAG_MAP[flag_id]] = True
|
||||||
|
|
||||||
|
if flag_id in KEY_LOCATION_FLAG_MAP:
|
||||||
|
local_found_key_items[KEY_LOCATION_FLAG_MAP[flag_id]] = True
|
||||||
|
|
||||||
|
# Send locations
|
||||||
|
if local_checked_locations != self.local_checked_locations:
|
||||||
|
self.local_checked_locations = local_checked_locations
|
||||||
|
|
||||||
|
if local_checked_locations is not None:
|
||||||
|
await ctx.send_msgs([{
|
||||||
|
"cmd": "LocationChecks",
|
||||||
|
"locations": list(local_checked_locations)
|
||||||
|
}])
|
||||||
|
|
||||||
|
# Send game clear
|
||||||
|
if not ctx.finished_game and game_clear:
|
||||||
|
await ctx.send_msgs([{
|
||||||
|
"cmd": "StatusUpdate",
|
||||||
|
"status": ClientStatus.CLIENT_GOAL
|
||||||
|
}])
|
||||||
|
|
||||||
|
# Send tracker event flags
|
||||||
|
if local_set_events != self.local_set_events and ctx.slot is not None:
|
||||||
|
event_bitfield = 0
|
||||||
|
for i, flag_name in enumerate(TRACKER_EVENT_FLAGS):
|
||||||
|
if local_set_events[flag_name]:
|
||||||
|
event_bitfield |= 1 << i
|
||||||
|
|
||||||
|
await ctx.send_msgs([{
|
||||||
|
"cmd": "Set",
|
||||||
|
"key": f"pokemon_emerald_events_{ctx.team}_{ctx.slot}",
|
||||||
|
"default": 0,
|
||||||
|
"want_reply": False,
|
||||||
|
"operations": [{"operation": "replace", "value": event_bitfield}]
|
||||||
|
}])
|
||||||
|
self.local_set_events = local_set_events
|
||||||
|
|
||||||
|
if local_found_key_items != self.local_found_key_items:
|
||||||
|
key_bitfield = 0
|
||||||
|
for i, location_name in enumerate(KEY_LOCATION_FLAGS):
|
||||||
|
if local_found_key_items[location_name]:
|
||||||
|
key_bitfield |= 1 << i
|
||||||
|
|
||||||
|
await ctx.send_msgs([{
|
||||||
|
"cmd": "Set",
|
||||||
|
"key": f"pokemon_emerald_keys_{ctx.team}_{ctx.slot}",
|
||||||
|
"default": 0,
|
||||||
|
"want_reply": False,
|
||||||
|
"operations": [{"operation": "replace", "value": key_bitfield}]
|
||||||
|
}])
|
||||||
|
self.local_found_key_items = local_found_key_items
|
||||||
|
except bizhawk.RequestFailedError:
|
||||||
|
# Exit handler and return to main loop to reconnect
|
||||||
|
pass
|
995
worlds/pokemon_emerald/data.py
Normal file
995
worlds/pokemon_emerald/data.py
Normal file
@@ -0,0 +1,995 @@
|
|||||||
|
"""
|
||||||
|
Pulls data from JSON files in worlds/pokemon_emerald/data/ into classes.
|
||||||
|
This also includes marrying automatically extracted data with manually
|
||||||
|
defined data (like location labels or usable pokemon species), some cleanup
|
||||||
|
and sorting, and Warp methods.
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import copy
|
||||||
|
from enum import IntEnum
|
||||||
|
import orjson
|
||||||
|
from typing import Dict, List, NamedTuple, Optional, Set, FrozenSet, Tuple, Any, Union
|
||||||
|
import pkgutil
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
from BaseClasses import ItemClassification
|
||||||
|
|
||||||
|
|
||||||
|
BASE_OFFSET = 3860000
|
||||||
|
|
||||||
|
|
||||||
|
class Warp:
|
||||||
|
"""
|
||||||
|
Represents warp events in the game like doorways or warp pads
|
||||||
|
"""
|
||||||
|
is_one_way: bool
|
||||||
|
source_map: str
|
||||||
|
source_ids: List[int]
|
||||||
|
dest_map: str
|
||||||
|
dest_ids: List[int]
|
||||||
|
parent_region: Optional[str]
|
||||||
|
|
||||||
|
def __init__(self, encoded_string: Optional[str] = None, parent_region: Optional[str] = None) -> None:
|
||||||
|
if encoded_string is not None:
|
||||||
|
decoded_warp = Warp.decode(encoded_string)
|
||||||
|
self.is_one_way = decoded_warp.is_one_way
|
||||||
|
self.source_map = decoded_warp.source_map
|
||||||
|
self.source_ids = decoded_warp.source_ids
|
||||||
|
self.dest_map = decoded_warp.dest_map
|
||||||
|
self.dest_ids = decoded_warp.dest_ids
|
||||||
|
self.parent_region = parent_region
|
||||||
|
|
||||||
|
def encode(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns a string encoding of this warp
|
||||||
|
"""
|
||||||
|
source_ids_string = ""
|
||||||
|
for source_id in self.source_ids:
|
||||||
|
source_ids_string += str(source_id) + ","
|
||||||
|
source_ids_string = source_ids_string[:-1] # Remove last ","
|
||||||
|
|
||||||
|
dest_ids_string = ""
|
||||||
|
for dest_id in self.dest_ids:
|
||||||
|
dest_ids_string += str(dest_id) + ","
|
||||||
|
dest_ids_string = dest_ids_string[:-1] # Remove last ","
|
||||||
|
|
||||||
|
return f"{self.source_map}:{source_ids_string}/{self.dest_map}:{dest_ids_string}{'!' if self.is_one_way else ''}"
|
||||||
|
|
||||||
|
def connects_to(self, other: 'Warp') -> bool:
|
||||||
|
"""
|
||||||
|
Returns true if this warp sends the player to `other`
|
||||||
|
"""
|
||||||
|
return self.dest_map == other.source_map and set(self.dest_ids) <= set(other.source_ids)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode(encoded_string: str) -> 'Warp':
|
||||||
|
"""
|
||||||
|
Create a Warp object from an encoded string
|
||||||
|
"""
|
||||||
|
warp = Warp()
|
||||||
|
warp.is_one_way = encoded_string.endswith("!")
|
||||||
|
if warp.is_one_way:
|
||||||
|
encoded_string = encoded_string[:-1]
|
||||||
|
|
||||||
|
warp_source, warp_dest = encoded_string.split("/")
|
||||||
|
warp_source_map, warp_source_indices = warp_source.split(":")
|
||||||
|
warp_dest_map, warp_dest_indices = warp_dest.split(":")
|
||||||
|
|
||||||
|
warp.source_map = warp_source_map
|
||||||
|
warp.dest_map = warp_dest_map
|
||||||
|
|
||||||
|
warp.source_ids = [int(index) for index in warp_source_indices.split(",")]
|
||||||
|
warp.dest_ids = [int(index) for index in warp_dest_indices.split(",")]
|
||||||
|
|
||||||
|
return warp
|
||||||
|
|
||||||
|
|
||||||
|
class ItemData(NamedTuple):
|
||||||
|
label: str
|
||||||
|
item_id: int
|
||||||
|
classification: ItemClassification
|
||||||
|
tags: FrozenSet[str]
|
||||||
|
|
||||||
|
|
||||||
|
class LocationData(NamedTuple):
|
||||||
|
name: str
|
||||||
|
label: str
|
||||||
|
parent_region: str
|
||||||
|
default_item: int
|
||||||
|
rom_address: int
|
||||||
|
flag: int
|
||||||
|
tags: FrozenSet[str]
|
||||||
|
|
||||||
|
|
||||||
|
class EventData(NamedTuple):
|
||||||
|
name: str
|
||||||
|
parent_region: str
|
||||||
|
|
||||||
|
|
||||||
|
class RegionData:
|
||||||
|
name: str
|
||||||
|
exits: List[str]
|
||||||
|
warps: List[str]
|
||||||
|
locations: List[str]
|
||||||
|
events: List[EventData]
|
||||||
|
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.exits = []
|
||||||
|
self.warps = []
|
||||||
|
self.locations = []
|
||||||
|
self.events = []
|
||||||
|
|
||||||
|
|
||||||
|
class BaseStats(NamedTuple):
|
||||||
|
hp: int
|
||||||
|
attack: int
|
||||||
|
defense: int
|
||||||
|
speed: int
|
||||||
|
special_attack: int
|
||||||
|
special_defense: int
|
||||||
|
|
||||||
|
|
||||||
|
class LearnsetMove(NamedTuple):
|
||||||
|
level: int
|
||||||
|
move_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class EvolutionMethodEnum(IntEnum):
|
||||||
|
LEVEL = 0
|
||||||
|
LEVEL_ATK_LT_DEF = 1
|
||||||
|
LEVEL_ATK_EQ_DEF = 2
|
||||||
|
LEVEL_ATK_GT_DEF = 3
|
||||||
|
LEVEL_SILCOON = 4
|
||||||
|
LEVEL_CASCOON = 5
|
||||||
|
LEVEL_NINJASK = 6
|
||||||
|
LEVEL_SHEDINJA = 7
|
||||||
|
ITEM = 8
|
||||||
|
FRIENDSHIP = 9
|
||||||
|
FRIENDSHIP_DAY = 10
|
||||||
|
FRIENDSHIP_NIGHT = 11
|
||||||
|
|
||||||
|
|
||||||
|
def _str_to_evolution_method(string: str) -> EvolutionMethodEnum:
|
||||||
|
if string == "LEVEL":
|
||||||
|
return EvolutionMethodEnum.LEVEL
|
||||||
|
if string == "LEVEL_ATK_LT_DEF":
|
||||||
|
return EvolutionMethodEnum.LEVEL_ATK_LT_DEF
|
||||||
|
if string == "LEVEL_ATK_EQ_DEF":
|
||||||
|
return EvolutionMethodEnum.LEVEL_ATK_EQ_DEF
|
||||||
|
if string == "LEVEL_ATK_GT_DEF":
|
||||||
|
return EvolutionMethodEnum.LEVEL_ATK_GT_DEF
|
||||||
|
if string == "LEVEL_SILCOON":
|
||||||
|
return EvolutionMethodEnum.LEVEL_SILCOON
|
||||||
|
if string == "LEVEL_CASCOON":
|
||||||
|
return EvolutionMethodEnum.LEVEL_CASCOON
|
||||||
|
if string == "LEVEL_NINJASK":
|
||||||
|
return EvolutionMethodEnum.LEVEL_NINJASK
|
||||||
|
if string == "LEVEL_SHEDINJA":
|
||||||
|
return EvolutionMethodEnum.LEVEL_SHEDINJA
|
||||||
|
if string == "FRIENDSHIP":
|
||||||
|
return EvolutionMethodEnum.FRIENDSHIP
|
||||||
|
if string == "FRIENDSHIP_DAY":
|
||||||
|
return EvolutionMethodEnum.FRIENDSHIP_DAY
|
||||||
|
if string == "FRIENDSHIP_NIGHT":
|
||||||
|
return EvolutionMethodEnum.FRIENDSHIP_NIGHT
|
||||||
|
|
||||||
|
|
||||||
|
class EvolutionData(NamedTuple):
|
||||||
|
method: EvolutionMethodEnum
|
||||||
|
param: int
|
||||||
|
species_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class StaticEncounterData(NamedTuple):
|
||||||
|
species_id: int
|
||||||
|
rom_address: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SpeciesData:
|
||||||
|
name: str
|
||||||
|
label: str
|
||||||
|
species_id: int
|
||||||
|
base_stats: BaseStats
|
||||||
|
types: Tuple[int, int]
|
||||||
|
abilities: Tuple[int, int]
|
||||||
|
evolutions: List[EvolutionData]
|
||||||
|
pre_evolution: Optional[int]
|
||||||
|
catch_rate: int
|
||||||
|
learnset: List[LearnsetMove]
|
||||||
|
tm_hm_compatibility: int
|
||||||
|
learnset_rom_address: int
|
||||||
|
rom_address: int
|
||||||
|
|
||||||
|
|
||||||
|
class AbilityData(NamedTuple):
|
||||||
|
ability_id: int
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
|
class EncounterTableData(NamedTuple):
|
||||||
|
slots: List[int]
|
||||||
|
rom_address: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MapData:
|
||||||
|
name: str
|
||||||
|
land_encounters: Optional[EncounterTableData]
|
||||||
|
water_encounters: Optional[EncounterTableData]
|
||||||
|
fishing_encounters: Optional[EncounterTableData]
|
||||||
|
|
||||||
|
|
||||||
|
class TrainerPokemonDataTypeEnum(IntEnum):
|
||||||
|
NO_ITEM_DEFAULT_MOVES = 0
|
||||||
|
ITEM_DEFAULT_MOVES = 1
|
||||||
|
NO_ITEM_CUSTOM_MOVES = 2
|
||||||
|
ITEM_CUSTOM_MOVES = 3
|
||||||
|
|
||||||
|
|
||||||
|
def _str_to_pokemon_data_type(string: str) -> TrainerPokemonDataTypeEnum:
|
||||||
|
if string == "NO_ITEM_DEFAULT_MOVES":
|
||||||
|
return TrainerPokemonDataTypeEnum.NO_ITEM_DEFAULT_MOVES
|
||||||
|
if string == "ITEM_DEFAULT_MOVES":
|
||||||
|
return TrainerPokemonDataTypeEnum.ITEM_DEFAULT_MOVES
|
||||||
|
if string == "NO_ITEM_CUSTOM_MOVES":
|
||||||
|
return TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES
|
||||||
|
if string == "ITEM_CUSTOM_MOVES":
|
||||||
|
return TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrainerPokemonData:
|
||||||
|
species_id: int
|
||||||
|
level: int
|
||||||
|
moves: Optional[Tuple[int, int, int, int]]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrainerPartyData:
|
||||||
|
pokemon: List[TrainerPokemonData]
|
||||||
|
pokemon_data_type: TrainerPokemonDataTypeEnum
|
||||||
|
rom_address: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrainerData:
|
||||||
|
trainer_id: int
|
||||||
|
party: TrainerPartyData
|
||||||
|
rom_address: int
|
||||||
|
battle_script_rom_address: int
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldData:
|
||||||
|
starters: Tuple[int, int, int]
|
||||||
|
constants: Dict[str, int]
|
||||||
|
ram_addresses: Dict[str, int]
|
||||||
|
rom_addresses: Dict[str, int]
|
||||||
|
regions: Dict[str, RegionData]
|
||||||
|
locations: Dict[str, LocationData]
|
||||||
|
items: Dict[int, ItemData]
|
||||||
|
species: List[Optional[SpeciesData]]
|
||||||
|
static_encounters: List[StaticEncounterData]
|
||||||
|
tmhm_moves: List[int]
|
||||||
|
abilities: List[AbilityData]
|
||||||
|
maps: List[MapData]
|
||||||
|
warps: Dict[str, Warp]
|
||||||
|
warp_map: Dict[str, Optional[str]]
|
||||||
|
trainers: List[TrainerData]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.starters = (277, 280, 283)
|
||||||
|
self.constants = {}
|
||||||
|
self.ram_addresses = {}
|
||||||
|
self.rom_addresses = {}
|
||||||
|
self.regions = {}
|
||||||
|
self.locations = {}
|
||||||
|
self.items = {}
|
||||||
|
self.species = []
|
||||||
|
self.static_encounters = []
|
||||||
|
self.tmhm_moves = []
|
||||||
|
self.abilities = []
|
||||||
|
self.maps = []
|
||||||
|
self.warps = {}
|
||||||
|
self.warp_map = {}
|
||||||
|
self.trainers = []
|
||||||
|
|
||||||
|
|
||||||
|
def load_json_data(data_name: str) -> Union[List[Any], Dict[str, Any]]:
|
||||||
|
return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode('utf-8-sig'))
|
||||||
|
|
||||||
|
|
||||||
|
data = PokemonEmeraldData()
|
||||||
|
|
||||||
|
def create_data_copy() -> PokemonEmeraldData:
|
||||||
|
new_copy = PokemonEmeraldData()
|
||||||
|
new_copy.species = copy.deepcopy(data.species)
|
||||||
|
new_copy.tmhm_moves = copy.deepcopy(data.tmhm_moves)
|
||||||
|
new_copy.maps = copy.deepcopy(data.maps)
|
||||||
|
new_copy.static_encounters = copy.deepcopy(data.static_encounters)
|
||||||
|
new_copy.trainers = copy.deepcopy(data.trainers)
|
||||||
|
|
||||||
|
|
||||||
|
def _init() -> None:
|
||||||
|
extracted_data: Dict[str, Any] = load_json_data("extracted_data.json")
|
||||||
|
data.constants = extracted_data["constants"]
|
||||||
|
data.ram_addresses = extracted_data["misc_ram_addresses"]
|
||||||
|
data.rom_addresses = extracted_data["misc_rom_addresses"]
|
||||||
|
|
||||||
|
location_attributes_json = load_json_data("locations.json")
|
||||||
|
|
||||||
|
# Load/merge region json files
|
||||||
|
region_json_list = []
|
||||||
|
for file in pkg_resources.resource_listdir(__name__, "data/regions"):
|
||||||
|
if not pkg_resources.resource_isdir(__name__, "data/regions/" + file):
|
||||||
|
region_json_list.append(load_json_data("regions/" + file))
|
||||||
|
|
||||||
|
regions_json = {}
|
||||||
|
for region_subset in region_json_list:
|
||||||
|
for region_name, region_json in region_subset.items():
|
||||||
|
if region_name in regions_json:
|
||||||
|
raise AssertionError("Region [{region_name}] was defined multiple times")
|
||||||
|
regions_json[region_name] = region_json
|
||||||
|
|
||||||
|
# Create region data
|
||||||
|
claimed_locations: Set[str] = set()
|
||||||
|
claimed_warps: Set[str] = set()
|
||||||
|
|
||||||
|
data.regions = {}
|
||||||
|
for region_name, region_json in regions_json.items():
|
||||||
|
new_region = RegionData(region_name)
|
||||||
|
|
||||||
|
# Locations
|
||||||
|
for location_name in region_json["locations"]:
|
||||||
|
if location_name in claimed_locations:
|
||||||
|
raise AssertionError(f"Location [{location_name}] was claimed by multiple regions")
|
||||||
|
|
||||||
|
location_json = extracted_data["locations"][location_name]
|
||||||
|
new_location = LocationData(
|
||||||
|
location_name,
|
||||||
|
location_attributes_json[location_name]["label"],
|
||||||
|
region_name,
|
||||||
|
location_json["default_item"],
|
||||||
|
location_json["rom_address"],
|
||||||
|
location_json["flag"],
|
||||||
|
frozenset(location_attributes_json[location_name]["tags"])
|
||||||
|
)
|
||||||
|
new_region.locations.append(location_name)
|
||||||
|
data.locations[location_name] = new_location
|
||||||
|
claimed_locations.add(location_name)
|
||||||
|
|
||||||
|
new_region.locations.sort()
|
||||||
|
|
||||||
|
# Events
|
||||||
|
for event in region_json["events"]:
|
||||||
|
new_region.events.append(EventData(event, region_name))
|
||||||
|
|
||||||
|
# Exits
|
||||||
|
for region_exit in region_json["exits"]:
|
||||||
|
new_region.exits.append(region_exit)
|
||||||
|
|
||||||
|
# Warps
|
||||||
|
for encoded_warp in region_json["warps"]:
|
||||||
|
if encoded_warp in claimed_warps:
|
||||||
|
raise AssertionError(f"Warp [{encoded_warp}] was claimed by multiple regions")
|
||||||
|
new_region.warps.append(encoded_warp)
|
||||||
|
data.warps[encoded_warp] = Warp(encoded_warp, region_name)
|
||||||
|
claimed_warps.add(encoded_warp)
|
||||||
|
|
||||||
|
new_region.warps.sort()
|
||||||
|
|
||||||
|
data.regions[region_name] = new_region
|
||||||
|
|
||||||
|
# Create item data
|
||||||
|
items_json = load_json_data("items.json")
|
||||||
|
|
||||||
|
data.items = {}
|
||||||
|
for item_constant_name, attributes in items_json.items():
|
||||||
|
item_classification = None
|
||||||
|
if attributes["classification"] == "PROGRESSION":
|
||||||
|
item_classification = ItemClassification.progression
|
||||||
|
elif attributes["classification"] == "USEFUL":
|
||||||
|
item_classification = ItemClassification.useful
|
||||||
|
elif attributes["classification"] == "FILLER":
|
||||||
|
item_classification = ItemClassification.filler
|
||||||
|
elif attributes["classification"] == "TRAP":
|
||||||
|
item_classification = ItemClassification.trap
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown classification {attributes['classification']} for item {item_constant_name}")
|
||||||
|
|
||||||
|
data.items[data.constants[item_constant_name]] = ItemData(
|
||||||
|
attributes["label"],
|
||||||
|
data.constants[item_constant_name],
|
||||||
|
item_classification,
|
||||||
|
frozenset(attributes["tags"])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create species data
|
||||||
|
|
||||||
|
# Excludes extras like copies of Unown and special species values like SPECIES_EGG.
|
||||||
|
all_species: List[Tuple[str, str]] = [
|
||||||
|
("SPECIES_BULBASAUR", "Bulbasaur"),
|
||||||
|
("SPECIES_IVYSAUR", "Ivysaur"),
|
||||||
|
("SPECIES_VENUSAUR", "Venusaur"),
|
||||||
|
("SPECIES_CHARMANDER", "Charmander"),
|
||||||
|
("SPECIES_CHARMELEON", "Charmeleon"),
|
||||||
|
("SPECIES_CHARIZARD", "Charizard"),
|
||||||
|
("SPECIES_SQUIRTLE", "Squirtle"),
|
||||||
|
("SPECIES_WARTORTLE", "Wartortle"),
|
||||||
|
("SPECIES_BLASTOISE", "Blastoise"),
|
||||||
|
("SPECIES_CATERPIE", "Caterpie"),
|
||||||
|
("SPECIES_METAPOD", "Metapod"),
|
||||||
|
("SPECIES_BUTTERFREE", "Butterfree"),
|
||||||
|
("SPECIES_WEEDLE", "Weedle"),
|
||||||
|
("SPECIES_KAKUNA", "Kakuna"),
|
||||||
|
("SPECIES_BEEDRILL", "Beedrill"),
|
||||||
|
("SPECIES_PIDGEY", "Pidgey"),
|
||||||
|
("SPECIES_PIDGEOTTO", "Pidgeotto"),
|
||||||
|
("SPECIES_PIDGEOT", "Pidgeot"),
|
||||||
|
("SPECIES_RATTATA", "Rattata"),
|
||||||
|
("SPECIES_RATICATE", "Raticate"),
|
||||||
|
("SPECIES_SPEAROW", "Spearow"),
|
||||||
|
("SPECIES_FEAROW", "Fearow"),
|
||||||
|
("SPECIES_EKANS", "Ekans"),
|
||||||
|
("SPECIES_ARBOK", "Arbok"),
|
||||||
|
("SPECIES_PIKACHU", "Pikachu"),
|
||||||
|
("SPECIES_RAICHU", "Raichu"),
|
||||||
|
("SPECIES_SANDSHREW", "Sandshrew"),
|
||||||
|
("SPECIES_SANDSLASH", "Sandslash"),
|
||||||
|
("SPECIES_NIDORAN_F", "Nidoran Female"),
|
||||||
|
("SPECIES_NIDORINA", "Nidorina"),
|
||||||
|
("SPECIES_NIDOQUEEN", "Nidoqueen"),
|
||||||
|
("SPECIES_NIDORAN_M", "Nidoran Male"),
|
||||||
|
("SPECIES_NIDORINO", "Nidorino"),
|
||||||
|
("SPECIES_NIDOKING", "Nidoking"),
|
||||||
|
("SPECIES_CLEFAIRY", "Clefairy"),
|
||||||
|
("SPECIES_CLEFABLE", "Clefable"),
|
||||||
|
("SPECIES_VULPIX", "Vulpix"),
|
||||||
|
("SPECIES_NINETALES", "Ninetales"),
|
||||||
|
("SPECIES_JIGGLYPUFF", "Jigglypuff"),
|
||||||
|
("SPECIES_WIGGLYTUFF", "Wigglytuff"),
|
||||||
|
("SPECIES_ZUBAT", "Zubat"),
|
||||||
|
("SPECIES_GOLBAT", "Golbat"),
|
||||||
|
("SPECIES_ODDISH", "Oddish"),
|
||||||
|
("SPECIES_GLOOM", "Gloom"),
|
||||||
|
("SPECIES_VILEPLUME", "Vileplume"),
|
||||||
|
("SPECIES_PARAS", "Paras"),
|
||||||
|
("SPECIES_PARASECT", "Parasect"),
|
||||||
|
("SPECIES_VENONAT", "Venonat"),
|
||||||
|
("SPECIES_VENOMOTH", "Venomoth"),
|
||||||
|
("SPECIES_DIGLETT", "Diglett"),
|
||||||
|
("SPECIES_DUGTRIO", "Dugtrio"),
|
||||||
|
("SPECIES_MEOWTH", "Meowth"),
|
||||||
|
("SPECIES_PERSIAN", "Persian"),
|
||||||
|
("SPECIES_PSYDUCK", "Psyduck"),
|
||||||
|
("SPECIES_GOLDUCK", "Golduck"),
|
||||||
|
("SPECIES_MANKEY", "Mankey"),
|
||||||
|
("SPECIES_PRIMEAPE", "Primeape"),
|
||||||
|
("SPECIES_GROWLITHE", "Growlithe"),
|
||||||
|
("SPECIES_ARCANINE", "Arcanine"),
|
||||||
|
("SPECIES_POLIWAG", "Poliwag"),
|
||||||
|
("SPECIES_POLIWHIRL", "Poliwhirl"),
|
||||||
|
("SPECIES_POLIWRATH", "Poliwrath"),
|
||||||
|
("SPECIES_ABRA", "Abra"),
|
||||||
|
("SPECIES_KADABRA", "Kadabra"),
|
||||||
|
("SPECIES_ALAKAZAM", "Alakazam"),
|
||||||
|
("SPECIES_MACHOP", "Machop"),
|
||||||
|
("SPECIES_MACHOKE", "Machoke"),
|
||||||
|
("SPECIES_MACHAMP", "Machamp"),
|
||||||
|
("SPECIES_BELLSPROUT", "Bellsprout"),
|
||||||
|
("SPECIES_WEEPINBELL", "Weepinbell"),
|
||||||
|
("SPECIES_VICTREEBEL", "Victreebel"),
|
||||||
|
("SPECIES_TENTACOOL", "Tentacool"),
|
||||||
|
("SPECIES_TENTACRUEL", "Tentacruel"),
|
||||||
|
("SPECIES_GEODUDE", "Geodude"),
|
||||||
|
("SPECIES_GRAVELER", "Graveler"),
|
||||||
|
("SPECIES_GOLEM", "Golem"),
|
||||||
|
("SPECIES_PONYTA", "Ponyta"),
|
||||||
|
("SPECIES_RAPIDASH", "Rapidash"),
|
||||||
|
("SPECIES_SLOWPOKE", "Slowpoke"),
|
||||||
|
("SPECIES_SLOWBRO", "Slowbro"),
|
||||||
|
("SPECIES_MAGNEMITE", "Magnemite"),
|
||||||
|
("SPECIES_MAGNETON", "Magneton"),
|
||||||
|
("SPECIES_FARFETCHD", "Farfetch'd"),
|
||||||
|
("SPECIES_DODUO", "Doduo"),
|
||||||
|
("SPECIES_DODRIO", "Dodrio"),
|
||||||
|
("SPECIES_SEEL", "Seel"),
|
||||||
|
("SPECIES_DEWGONG", "Dewgong"),
|
||||||
|
("SPECIES_GRIMER", "Grimer"),
|
||||||
|
("SPECIES_MUK", "Muk"),
|
||||||
|
("SPECIES_SHELLDER", "Shellder"),
|
||||||
|
("SPECIES_CLOYSTER", "Cloyster"),
|
||||||
|
("SPECIES_GASTLY", "Gastly"),
|
||||||
|
("SPECIES_HAUNTER", "Haunter"),
|
||||||
|
("SPECIES_GENGAR", "Gengar"),
|
||||||
|
("SPECIES_ONIX", "Onix"),
|
||||||
|
("SPECIES_DROWZEE", "Drowzee"),
|
||||||
|
("SPECIES_HYPNO", "Hypno"),
|
||||||
|
("SPECIES_KRABBY", "Krabby"),
|
||||||
|
("SPECIES_KINGLER", "Kingler"),
|
||||||
|
("SPECIES_VOLTORB", "Voltorb"),
|
||||||
|
("SPECIES_ELECTRODE", "Electrode"),
|
||||||
|
("SPECIES_EXEGGCUTE", "Exeggcute"),
|
||||||
|
("SPECIES_EXEGGUTOR", "Exeggutor"),
|
||||||
|
("SPECIES_CUBONE", "Cubone"),
|
||||||
|
("SPECIES_MAROWAK", "Marowak"),
|
||||||
|
("SPECIES_HITMONLEE", "Hitmonlee"),
|
||||||
|
("SPECIES_HITMONCHAN", "Hitmonchan"),
|
||||||
|
("SPECIES_LICKITUNG", "Lickitung"),
|
||||||
|
("SPECIES_KOFFING", "Koffing"),
|
||||||
|
("SPECIES_WEEZING", "Weezing"),
|
||||||
|
("SPECIES_RHYHORN", "Rhyhorn"),
|
||||||
|
("SPECIES_RHYDON", "Rhydon"),
|
||||||
|
("SPECIES_CHANSEY", "Chansey"),
|
||||||
|
("SPECIES_TANGELA", "Tangela"),
|
||||||
|
("SPECIES_KANGASKHAN", "Kangaskhan"),
|
||||||
|
("SPECIES_HORSEA", "Horsea"),
|
||||||
|
("SPECIES_SEADRA", "Seadra"),
|
||||||
|
("SPECIES_GOLDEEN", "Goldeen"),
|
||||||
|
("SPECIES_SEAKING", "Seaking"),
|
||||||
|
("SPECIES_STARYU", "Staryu"),
|
||||||
|
("SPECIES_STARMIE", "Starmie"),
|
||||||
|
("SPECIES_MR_MIME", "Mr. Mime"),
|
||||||
|
("SPECIES_SCYTHER", "Scyther"),
|
||||||
|
("SPECIES_JYNX", "Jynx"),
|
||||||
|
("SPECIES_ELECTABUZZ", "Electabuzz"),
|
||||||
|
("SPECIES_MAGMAR", "Magmar"),
|
||||||
|
("SPECIES_PINSIR", "Pinsir"),
|
||||||
|
("SPECIES_TAUROS", "Tauros"),
|
||||||
|
("SPECIES_MAGIKARP", "Magikarp"),
|
||||||
|
("SPECIES_GYARADOS", "Gyarados"),
|
||||||
|
("SPECIES_LAPRAS", "Lapras"),
|
||||||
|
("SPECIES_DITTO", "Ditto"),
|
||||||
|
("SPECIES_EEVEE", "Eevee"),
|
||||||
|
("SPECIES_VAPOREON", "Vaporeon"),
|
||||||
|
("SPECIES_JOLTEON", "Jolteon"),
|
||||||
|
("SPECIES_FLAREON", "Flareon"),
|
||||||
|
("SPECIES_PORYGON", "Porygon"),
|
||||||
|
("SPECIES_OMANYTE", "Omanyte"),
|
||||||
|
("SPECIES_OMASTAR", "Omastar"),
|
||||||
|
("SPECIES_KABUTO", "Kabuto"),
|
||||||
|
("SPECIES_KABUTOPS", "Kabutops"),
|
||||||
|
("SPECIES_AERODACTYL", "Aerodactyl"),
|
||||||
|
("SPECIES_SNORLAX", "Snorlax"),
|
||||||
|
("SPECIES_ARTICUNO", "Articuno"),
|
||||||
|
("SPECIES_ZAPDOS", "Zapdos"),
|
||||||
|
("SPECIES_MOLTRES", "Moltres"),
|
||||||
|
("SPECIES_DRATINI", "Dratini"),
|
||||||
|
("SPECIES_DRAGONAIR", "Dragonair"),
|
||||||
|
("SPECIES_DRAGONITE", "Dragonite"),
|
||||||
|
("SPECIES_MEWTWO", "Mewtwo"),
|
||||||
|
("SPECIES_MEW", "Mew"),
|
||||||
|
("SPECIES_CHIKORITA", "Chikorita"),
|
||||||
|
("SPECIES_BAYLEEF", "Bayleaf"),
|
||||||
|
("SPECIES_MEGANIUM", "Meganium"),
|
||||||
|
("SPECIES_CYNDAQUIL", "Cindaquil"),
|
||||||
|
("SPECIES_QUILAVA", "Quilava"),
|
||||||
|
("SPECIES_TYPHLOSION", "Typhlosion"),
|
||||||
|
("SPECIES_TOTODILE", "Totodile"),
|
||||||
|
("SPECIES_CROCONAW", "Croconaw"),
|
||||||
|
("SPECIES_FERALIGATR", "Feraligatr"),
|
||||||
|
("SPECIES_SENTRET", "Sentret"),
|
||||||
|
("SPECIES_FURRET", "Furret"),
|
||||||
|
("SPECIES_HOOTHOOT", "Hoothoot"),
|
||||||
|
("SPECIES_NOCTOWL", "Noctowl"),
|
||||||
|
("SPECIES_LEDYBA", "Ledyba"),
|
||||||
|
("SPECIES_LEDIAN", "Ledian"),
|
||||||
|
("SPECIES_SPINARAK", "Spinarak"),
|
||||||
|
("SPECIES_ARIADOS", "Ariados"),
|
||||||
|
("SPECIES_CROBAT", "Crobat"),
|
||||||
|
("SPECIES_CHINCHOU", "Chinchou"),
|
||||||
|
("SPECIES_LANTURN", "Lanturn"),
|
||||||
|
("SPECIES_PICHU", "Pichu"),
|
||||||
|
("SPECIES_CLEFFA", "Cleffa"),
|
||||||
|
("SPECIES_IGGLYBUFF", "Igglybuff"),
|
||||||
|
("SPECIES_TOGEPI", "Togepi"),
|
||||||
|
("SPECIES_TOGETIC", "Togetic"),
|
||||||
|
("SPECIES_NATU", "Natu"),
|
||||||
|
("SPECIES_XATU", "Xatu"),
|
||||||
|
("SPECIES_MAREEP", "Mareep"),
|
||||||
|
("SPECIES_FLAAFFY", "Flaafy"),
|
||||||
|
("SPECIES_AMPHAROS", "Ampharos"),
|
||||||
|
("SPECIES_BELLOSSOM", "Bellossom"),
|
||||||
|
("SPECIES_MARILL", "Marill"),
|
||||||
|
("SPECIES_AZUMARILL", "Azumarill"),
|
||||||
|
("SPECIES_SUDOWOODO", "Sudowoodo"),
|
||||||
|
("SPECIES_POLITOED", "Politoed"),
|
||||||
|
("SPECIES_HOPPIP", "Hoppip"),
|
||||||
|
("SPECIES_SKIPLOOM", "Skiploom"),
|
||||||
|
("SPECIES_JUMPLUFF", "Jumpluff"),
|
||||||
|
("SPECIES_AIPOM", "Aipom"),
|
||||||
|
("SPECIES_SUNKERN", "Sunkern"),
|
||||||
|
("SPECIES_SUNFLORA", "Sunflora"),
|
||||||
|
("SPECIES_YANMA", "Yanma"),
|
||||||
|
("SPECIES_WOOPER", "Wooper"),
|
||||||
|
("SPECIES_QUAGSIRE", "Quagsire"),
|
||||||
|
("SPECIES_ESPEON", "Espeon"),
|
||||||
|
("SPECIES_UMBREON", "Umbreon"),
|
||||||
|
("SPECIES_MURKROW", "Murkrow"),
|
||||||
|
("SPECIES_SLOWKING", "Slowking"),
|
||||||
|
("SPECIES_MISDREAVUS", "Misdreavus"),
|
||||||
|
("SPECIES_UNOWN", "Unown"),
|
||||||
|
("SPECIES_WOBBUFFET", "Wobbuffet"),
|
||||||
|
("SPECIES_GIRAFARIG", "Girafarig"),
|
||||||
|
("SPECIES_PINECO", "Pineco"),
|
||||||
|
("SPECIES_FORRETRESS", "Forretress"),
|
||||||
|
("SPECIES_DUNSPARCE", "Dunsparce"),
|
||||||
|
("SPECIES_GLIGAR", "Gligar"),
|
||||||
|
("SPECIES_STEELIX", "Steelix"),
|
||||||
|
("SPECIES_SNUBBULL", "Snubbull"),
|
||||||
|
("SPECIES_GRANBULL", "Granbull"),
|
||||||
|
("SPECIES_QWILFISH", "Qwilfish"),
|
||||||
|
("SPECIES_SCIZOR", "Scizor"),
|
||||||
|
("SPECIES_SHUCKLE", "Shuckle"),
|
||||||
|
("SPECIES_HERACROSS", "Heracross"),
|
||||||
|
("SPECIES_SNEASEL", "Sneasel"),
|
||||||
|
("SPECIES_TEDDIURSA", "Teddiursa"),
|
||||||
|
("SPECIES_URSARING", "Ursaring"),
|
||||||
|
("SPECIES_SLUGMA", "Slugma"),
|
||||||
|
("SPECIES_MAGCARGO", "Magcargo"),
|
||||||
|
("SPECIES_SWINUB", "Swinub"),
|
||||||
|
("SPECIES_PILOSWINE", "Piloswine"),
|
||||||
|
("SPECIES_CORSOLA", "Corsola"),
|
||||||
|
("SPECIES_REMORAID", "Remoraid"),
|
||||||
|
("SPECIES_OCTILLERY", "Octillery"),
|
||||||
|
("SPECIES_DELIBIRD", "Delibird"),
|
||||||
|
("SPECIES_MANTINE", "Mantine"),
|
||||||
|
("SPECIES_SKARMORY", "Skarmory"),
|
||||||
|
("SPECIES_HOUNDOUR", "Houndour"),
|
||||||
|
("SPECIES_HOUNDOOM", "Houndoom"),
|
||||||
|
("SPECIES_KINGDRA", "Kingdra"),
|
||||||
|
("SPECIES_PHANPY", "Phanpy"),
|
||||||
|
("SPECIES_DONPHAN", "Donphan"),
|
||||||
|
("SPECIES_PORYGON2", "Porygon2"),
|
||||||
|
("SPECIES_STANTLER", "Stantler"),
|
||||||
|
("SPECIES_SMEARGLE", "Smeargle"),
|
||||||
|
("SPECIES_TYROGUE", "Tyrogue"),
|
||||||
|
("SPECIES_HITMONTOP", "Hitmontop"),
|
||||||
|
("SPECIES_SMOOCHUM", "Smoochum"),
|
||||||
|
("SPECIES_ELEKID", "Elekid"),
|
||||||
|
("SPECIES_MAGBY", "Magby"),
|
||||||
|
("SPECIES_MILTANK", "Miltank"),
|
||||||
|
("SPECIES_BLISSEY", "Blissey"),
|
||||||
|
("SPECIES_RAIKOU", "Raikou"),
|
||||||
|
("SPECIES_ENTEI", "Entei"),
|
||||||
|
("SPECIES_SUICUNE", "Suicune"),
|
||||||
|
("SPECIES_LARVITAR", "Larvitar"),
|
||||||
|
("SPECIES_PUPITAR", "Pupitar"),
|
||||||
|
("SPECIES_TYRANITAR", "Tyranitar"),
|
||||||
|
("SPECIES_LUGIA", "Lugia"),
|
||||||
|
("SPECIES_HO_OH", "Ho-oh"),
|
||||||
|
("SPECIES_CELEBI", "Celebi"),
|
||||||
|
("SPECIES_TREECKO", "Treecko"),
|
||||||
|
("SPECIES_GROVYLE", "Grovyle"),
|
||||||
|
("SPECIES_SCEPTILE", "Sceptile"),
|
||||||
|
("SPECIES_TORCHIC", "Torchic"),
|
||||||
|
("SPECIES_COMBUSKEN", "Combusken"),
|
||||||
|
("SPECIES_BLAZIKEN", "Blaziken"),
|
||||||
|
("SPECIES_MUDKIP", "Mudkip"),
|
||||||
|
("SPECIES_MARSHTOMP", "Marshtomp"),
|
||||||
|
("SPECIES_SWAMPERT", "Swampert"),
|
||||||
|
("SPECIES_POOCHYENA", "Poochyena"),
|
||||||
|
("SPECIES_MIGHTYENA", "Mightyena"),
|
||||||
|
("SPECIES_ZIGZAGOON", "Zigzagoon"),
|
||||||
|
("SPECIES_LINOONE", "Linoon"),
|
||||||
|
("SPECIES_WURMPLE", "Wurmple"),
|
||||||
|
("SPECIES_SILCOON", "Silcoon"),
|
||||||
|
("SPECIES_BEAUTIFLY", "Beautifly"),
|
||||||
|
("SPECIES_CASCOON", "Cascoon"),
|
||||||
|
("SPECIES_DUSTOX", "Dustox"),
|
||||||
|
("SPECIES_LOTAD", "Lotad"),
|
||||||
|
("SPECIES_LOMBRE", "Lombre"),
|
||||||
|
("SPECIES_LUDICOLO", "Ludicolo"),
|
||||||
|
("SPECIES_SEEDOT", "Seedot"),
|
||||||
|
("SPECIES_NUZLEAF", "Nuzleaf"),
|
||||||
|
("SPECIES_SHIFTRY", "Shiftry"),
|
||||||
|
("SPECIES_NINCADA", "Nincada"),
|
||||||
|
("SPECIES_NINJASK", "Ninjask"),
|
||||||
|
("SPECIES_SHEDINJA", "Shedinja"),
|
||||||
|
("SPECIES_TAILLOW", "Taillow"),
|
||||||
|
("SPECIES_SWELLOW", "Swellow"),
|
||||||
|
("SPECIES_SHROOMISH", "Shroomish"),
|
||||||
|
("SPECIES_BRELOOM", "Breloom"),
|
||||||
|
("SPECIES_SPINDA", "Spinda"),
|
||||||
|
("SPECIES_WINGULL", "Wingull"),
|
||||||
|
("SPECIES_PELIPPER", "Pelipper"),
|
||||||
|
("SPECIES_SURSKIT", "Surskit"),
|
||||||
|
("SPECIES_MASQUERAIN", "Masquerain"),
|
||||||
|
("SPECIES_WAILMER", "Wailmer"),
|
||||||
|
("SPECIES_WAILORD", "Wailord"),
|
||||||
|
("SPECIES_SKITTY", "Skitty"),
|
||||||
|
("SPECIES_DELCATTY", "Delcatty"),
|
||||||
|
("SPECIES_KECLEON", "Kecleon"),
|
||||||
|
("SPECIES_BALTOY", "Baltoy"),
|
||||||
|
("SPECIES_CLAYDOL", "Claydol"),
|
||||||
|
("SPECIES_NOSEPASS", "Nosepass"),
|
||||||
|
("SPECIES_TORKOAL", "Torkoal"),
|
||||||
|
("SPECIES_SABLEYE", "Sableye"),
|
||||||
|
("SPECIES_BARBOACH", "Barboach"),
|
||||||
|
("SPECIES_WHISCASH", "Whiscash"),
|
||||||
|
("SPECIES_LUVDISC", "Luvdisc"),
|
||||||
|
("SPECIES_CORPHISH", "Corphish"),
|
||||||
|
("SPECIES_CRAWDAUNT", "Crawdaunt"),
|
||||||
|
("SPECIES_FEEBAS", "Feebas"),
|
||||||
|
("SPECIES_MILOTIC", "Milotic"),
|
||||||
|
("SPECIES_CARVANHA", "Carvanha"),
|
||||||
|
("SPECIES_SHARPEDO", "Sharpedo"),
|
||||||
|
("SPECIES_TRAPINCH", "Trapinch"),
|
||||||
|
("SPECIES_VIBRAVA", "Vibrava"),
|
||||||
|
("SPECIES_FLYGON", "Flygon"),
|
||||||
|
("SPECIES_MAKUHITA", "Makuhita"),
|
||||||
|
("SPECIES_HARIYAMA", "Hariyama"),
|
||||||
|
("SPECIES_ELECTRIKE", "Electrike"),
|
||||||
|
("SPECIES_MANECTRIC", "Manectric"),
|
||||||
|
("SPECIES_NUMEL", "Numel"),
|
||||||
|
("SPECIES_CAMERUPT", "Camerupt"),
|
||||||
|
("SPECIES_SPHEAL", "Spheal"),
|
||||||
|
("SPECIES_SEALEO", "Sealeo"),
|
||||||
|
("SPECIES_WALREIN", "Walrein"),
|
||||||
|
("SPECIES_CACNEA", "Cacnea"),
|
||||||
|
("SPECIES_CACTURNE", "Cacturne"),
|
||||||
|
("SPECIES_SNORUNT", "Snorunt"),
|
||||||
|
("SPECIES_GLALIE", "Glalie"),
|
||||||
|
("SPECIES_LUNATONE", "Lunatone"),
|
||||||
|
("SPECIES_SOLROCK", "Solrock"),
|
||||||
|
("SPECIES_AZURILL", "Azurill"),
|
||||||
|
("SPECIES_SPOINK", "Spoink"),
|
||||||
|
("SPECIES_GRUMPIG", "Grumpig"),
|
||||||
|
("SPECIES_PLUSLE", "Plusle"),
|
||||||
|
("SPECIES_MINUN", "Minun"),
|
||||||
|
("SPECIES_MAWILE", "Mawile"),
|
||||||
|
("SPECIES_MEDITITE", "Meditite"),
|
||||||
|
("SPECIES_MEDICHAM", "Medicham"),
|
||||||
|
("SPECIES_SWABLU", "Swablu"),
|
||||||
|
("SPECIES_ALTARIA", "Altaria"),
|
||||||
|
("SPECIES_WYNAUT", "Wynaut"),
|
||||||
|
("SPECIES_DUSKULL", "Duskull"),
|
||||||
|
("SPECIES_DUSCLOPS", "Dusclops"),
|
||||||
|
("SPECIES_ROSELIA", "Roselia"),
|
||||||
|
("SPECIES_SLAKOTH", "Slakoth"),
|
||||||
|
("SPECIES_VIGOROTH", "Vigoroth"),
|
||||||
|
("SPECIES_SLAKING", "Slaking"),
|
||||||
|
("SPECIES_GULPIN", "Gulpin"),
|
||||||
|
("SPECIES_SWALOT", "Swalot"),
|
||||||
|
("SPECIES_TROPIUS", "Tropius"),
|
||||||
|
("SPECIES_WHISMUR", "Whismur"),
|
||||||
|
("SPECIES_LOUDRED", "Loudred"),
|
||||||
|
("SPECIES_EXPLOUD", "Exploud"),
|
||||||
|
("SPECIES_CLAMPERL", "Clamperl"),
|
||||||
|
("SPECIES_HUNTAIL", "Huntail"),
|
||||||
|
("SPECIES_GOREBYSS", "Gorebyss"),
|
||||||
|
("SPECIES_ABSOL", "Absol"),
|
||||||
|
("SPECIES_SHUPPET", "Shuppet"),
|
||||||
|
("SPECIES_BANETTE", "Banette"),
|
||||||
|
("SPECIES_SEVIPER", "Seviper"),
|
||||||
|
("SPECIES_ZANGOOSE", "Zangoose"),
|
||||||
|
("SPECIES_RELICANTH", "Relicanth"),
|
||||||
|
("SPECIES_ARON", "Aron"),
|
||||||
|
("SPECIES_LAIRON", "Lairon"),
|
||||||
|
("SPECIES_AGGRON", "Aggron"),
|
||||||
|
("SPECIES_CASTFORM", "Castform"),
|
||||||
|
("SPECIES_VOLBEAT", "Volbeat"),
|
||||||
|
("SPECIES_ILLUMISE", "Illumise"),
|
||||||
|
("SPECIES_LILEEP", "Lileep"),
|
||||||
|
("SPECIES_CRADILY", "Cradily"),
|
||||||
|
("SPECIES_ANORITH", "Anorith"),
|
||||||
|
("SPECIES_ARMALDO", "Armaldo"),
|
||||||
|
("SPECIES_RALTS", "Ralts"),
|
||||||
|
("SPECIES_KIRLIA", "Kirlia"),
|
||||||
|
("SPECIES_GARDEVOIR", "Gardevoir"),
|
||||||
|
("SPECIES_BAGON", "Bagon"),
|
||||||
|
("SPECIES_SHELGON", "Shelgon"),
|
||||||
|
("SPECIES_SALAMENCE", "Salamence"),
|
||||||
|
("SPECIES_BELDUM", "Beldum"),
|
||||||
|
("SPECIES_METANG", "Metang"),
|
||||||
|
("SPECIES_METAGROSS", "Metagross"),
|
||||||
|
("SPECIES_REGIROCK", "Regirock"),
|
||||||
|
("SPECIES_REGICE", "Regice"),
|
||||||
|
("SPECIES_REGISTEEL", "Registeel"),
|
||||||
|
("SPECIES_KYOGRE", "Kyogre"),
|
||||||
|
("SPECIES_GROUDON", "Groudon"),
|
||||||
|
("SPECIES_RAYQUAZA", "Rayquaza"),
|
||||||
|
("SPECIES_LATIAS", "Latias"),
|
||||||
|
("SPECIES_LATIOS", "Latios"),
|
||||||
|
("SPECIES_JIRACHI", "Jirachi"),
|
||||||
|
("SPECIES_DEOXYS", "Deoxys"),
|
||||||
|
("SPECIES_CHIMECHO", "Chimecho")
|
||||||
|
]
|
||||||
|
|
||||||
|
species_list: List[SpeciesData] = []
|
||||||
|
max_species_id = 0
|
||||||
|
for species_name, species_label in all_species:
|
||||||
|
species_id = data.constants[species_name]
|
||||||
|
max_species_id = max(species_id, max_species_id)
|
||||||
|
species_data = extracted_data["species"][species_id]
|
||||||
|
|
||||||
|
learnset = [LearnsetMove(item["level"], item["move_id"]) for item in species_data["learnset"]["moves"]]
|
||||||
|
|
||||||
|
species_list.append(SpeciesData(
|
||||||
|
species_name,
|
||||||
|
species_label,
|
||||||
|
species_id,
|
||||||
|
BaseStats(
|
||||||
|
species_data["base_stats"][0],
|
||||||
|
species_data["base_stats"][1],
|
||||||
|
species_data["base_stats"][2],
|
||||||
|
species_data["base_stats"][3],
|
||||||
|
species_data["base_stats"][4],
|
||||||
|
species_data["base_stats"][5]
|
||||||
|
),
|
||||||
|
(species_data["types"][0], species_data["types"][1]),
|
||||||
|
(species_data["abilities"][0], species_data["abilities"][1]),
|
||||||
|
[EvolutionData(
|
||||||
|
_str_to_evolution_method(evolution_json["method"]),
|
||||||
|
evolution_json["param"],
|
||||||
|
evolution_json["species"],
|
||||||
|
) for evolution_json in species_data["evolutions"]],
|
||||||
|
None,
|
||||||
|
species_data["catch_rate"],
|
||||||
|
learnset,
|
||||||
|
int(species_data["tmhm_learnset"], 16),
|
||||||
|
species_data["learnset"]["rom_address"],
|
||||||
|
species_data["rom_address"]
|
||||||
|
))
|
||||||
|
|
||||||
|
data.species = [None for i in range(max_species_id + 1)]
|
||||||
|
|
||||||
|
for species_data in species_list:
|
||||||
|
data.species[species_data.species_id] = species_data
|
||||||
|
|
||||||
|
for species in data.species:
|
||||||
|
if species is not None:
|
||||||
|
for evolution in species.evolutions:
|
||||||
|
data.species[evolution.species_id].pre_evolution = species.species_id
|
||||||
|
|
||||||
|
# Create static encounter data
|
||||||
|
for static_encounter_json in extracted_data["static_encounters"]:
|
||||||
|
data.static_encounters.append(StaticEncounterData(
|
||||||
|
static_encounter_json["species"],
|
||||||
|
static_encounter_json["rom_address"]
|
||||||
|
))
|
||||||
|
|
||||||
|
# TM moves
|
||||||
|
data.tmhm_moves = extracted_data["tmhm_moves"]
|
||||||
|
|
||||||
|
# Create ability data
|
||||||
|
data.abilities = [AbilityData(data.constants[ability_data[0]], ability_data[1]) for ability_data in [
|
||||||
|
("ABILITY_STENCH", "Stench"),
|
||||||
|
("ABILITY_DRIZZLE", "Drizzle"),
|
||||||
|
("ABILITY_SPEED_BOOST", "Speed Boost"),
|
||||||
|
("ABILITY_BATTLE_ARMOR", "Battle Armor"),
|
||||||
|
("ABILITY_STURDY", "Sturdy"),
|
||||||
|
("ABILITY_DAMP", "Damp"),
|
||||||
|
("ABILITY_LIMBER", "Limber"),
|
||||||
|
("ABILITY_SAND_VEIL", "Sand Veil"),
|
||||||
|
("ABILITY_STATIC", "Static"),
|
||||||
|
("ABILITY_VOLT_ABSORB", "Volt Absorb"),
|
||||||
|
("ABILITY_WATER_ABSORB", "Water Absorb"),
|
||||||
|
("ABILITY_OBLIVIOUS", "Oblivious"),
|
||||||
|
("ABILITY_CLOUD_NINE", "Cloud Nine"),
|
||||||
|
("ABILITY_COMPOUND_EYES", "Compound Eyes"),
|
||||||
|
("ABILITY_INSOMNIA", "Insomnia"),
|
||||||
|
("ABILITY_COLOR_CHANGE", "Color Change"),
|
||||||
|
("ABILITY_IMMUNITY", "Immunity"),
|
||||||
|
("ABILITY_FLASH_FIRE", "Flash Fire"),
|
||||||
|
("ABILITY_SHIELD_DUST", "Shield Dust"),
|
||||||
|
("ABILITY_OWN_TEMPO", "Own Tempo"),
|
||||||
|
("ABILITY_SUCTION_CUPS", "Suction Cups"),
|
||||||
|
("ABILITY_INTIMIDATE", "Intimidate"),
|
||||||
|
("ABILITY_SHADOW_TAG", "Shadow Tag"),
|
||||||
|
("ABILITY_ROUGH_SKIN", "Rough Skin"),
|
||||||
|
("ABILITY_WONDER_GUARD", "Wonder Guard"),
|
||||||
|
("ABILITY_LEVITATE", "Levitate"),
|
||||||
|
("ABILITY_EFFECT_SPORE", "Effect Spore"),
|
||||||
|
("ABILITY_SYNCHRONIZE", "Synchronize"),
|
||||||
|
("ABILITY_CLEAR_BODY", "Clear Body"),
|
||||||
|
("ABILITY_NATURAL_CURE", "Natural Cure"),
|
||||||
|
("ABILITY_LIGHTNING_ROD", "Lightning Rod"),
|
||||||
|
("ABILITY_SERENE_GRACE", "Serene Grace"),
|
||||||
|
("ABILITY_SWIFT_SWIM", "Swift Swim"),
|
||||||
|
("ABILITY_CHLOROPHYLL", "Chlorophyll"),
|
||||||
|
("ABILITY_ILLUMINATE", "Illuminate"),
|
||||||
|
("ABILITY_TRACE", "Trace"),
|
||||||
|
("ABILITY_HUGE_POWER", "Huge Power"),
|
||||||
|
("ABILITY_POISON_POINT", "Poison Point"),
|
||||||
|
("ABILITY_INNER_FOCUS", "Inner Focus"),
|
||||||
|
("ABILITY_MAGMA_ARMOR", "Magma Armor"),
|
||||||
|
("ABILITY_WATER_VEIL", "Water Veil"),
|
||||||
|
("ABILITY_MAGNET_PULL", "Magnet Pull"),
|
||||||
|
("ABILITY_SOUNDPROOF", "Soundproof"),
|
||||||
|
("ABILITY_RAIN_DISH", "Rain Dish"),
|
||||||
|
("ABILITY_SAND_STREAM", "Sand Stream"),
|
||||||
|
("ABILITY_PRESSURE", "Pressure"),
|
||||||
|
("ABILITY_THICK_FAT", "Thick Fat"),
|
||||||
|
("ABILITY_EARLY_BIRD", "Early Bird"),
|
||||||
|
("ABILITY_FLAME_BODY", "Flame Body"),
|
||||||
|
("ABILITY_RUN_AWAY", "Run Away"),
|
||||||
|
("ABILITY_KEEN_EYE", "Keen Eye"),
|
||||||
|
("ABILITY_HYPER_CUTTER", "Hyper Cutter"),
|
||||||
|
("ABILITY_PICKUP", "Pickup"),
|
||||||
|
("ABILITY_TRUANT", "Truant"),
|
||||||
|
("ABILITY_HUSTLE", "Hustle"),
|
||||||
|
("ABILITY_CUTE_CHARM", "Cute Charm"),
|
||||||
|
("ABILITY_PLUS", "Plus"),
|
||||||
|
("ABILITY_MINUS", "Minus"),
|
||||||
|
("ABILITY_FORECAST", "Forecast"),
|
||||||
|
("ABILITY_STICKY_HOLD", "Sticky Hold"),
|
||||||
|
("ABILITY_SHED_SKIN", "Shed Skin"),
|
||||||
|
("ABILITY_GUTS", "Guts"),
|
||||||
|
("ABILITY_MARVEL_SCALE", "Marvel Scale"),
|
||||||
|
("ABILITY_LIQUID_OOZE", "Liquid Ooze"),
|
||||||
|
("ABILITY_OVERGROW", "Overgrow"),
|
||||||
|
("ABILITY_BLAZE", "Blaze"),
|
||||||
|
("ABILITY_TORRENT", "Torrent"),
|
||||||
|
("ABILITY_SWARM", "Swarm"),
|
||||||
|
("ABILITY_ROCK_HEAD", "Rock Head"),
|
||||||
|
("ABILITY_DROUGHT", "Drought"),
|
||||||
|
("ABILITY_ARENA_TRAP", "Arena Trap"),
|
||||||
|
("ABILITY_VITAL_SPIRIT", "Vital Spirit"),
|
||||||
|
("ABILITY_WHITE_SMOKE", "White Smoke"),
|
||||||
|
("ABILITY_PURE_POWER", "Pure Power"),
|
||||||
|
("ABILITY_SHELL_ARMOR", "Shell Armor"),
|
||||||
|
("ABILITY_CACOPHONY", "Cacophony"),
|
||||||
|
("ABILITY_AIR_LOCK", "Air Lock")
|
||||||
|
]]
|
||||||
|
|
||||||
|
# Create map data
|
||||||
|
for map_name, map_json in extracted_data["maps"].items():
|
||||||
|
land_encounters = None
|
||||||
|
water_encounters = None
|
||||||
|
fishing_encounters = None
|
||||||
|
|
||||||
|
if map_json["land_encounters"] is not None:
|
||||||
|
land_encounters = EncounterTableData(
|
||||||
|
map_json["land_encounters"]["encounter_slots"],
|
||||||
|
map_json["land_encounters"]["rom_address"]
|
||||||
|
)
|
||||||
|
if map_json["water_encounters"] is not None:
|
||||||
|
water_encounters = EncounterTableData(
|
||||||
|
map_json["water_encounters"]["encounter_slots"],
|
||||||
|
map_json["water_encounters"]["rom_address"]
|
||||||
|
)
|
||||||
|
if map_json["fishing_encounters"] is not None:
|
||||||
|
fishing_encounters = EncounterTableData(
|
||||||
|
map_json["fishing_encounters"]["encounter_slots"],
|
||||||
|
map_json["fishing_encounters"]["rom_address"]
|
||||||
|
)
|
||||||
|
|
||||||
|
data.maps.append(MapData(
|
||||||
|
map_name,
|
||||||
|
land_encounters,
|
||||||
|
water_encounters,
|
||||||
|
fishing_encounters
|
||||||
|
))
|
||||||
|
|
||||||
|
data.maps.sort(key=lambda map: map.name)
|
||||||
|
|
||||||
|
# Create warp map
|
||||||
|
for warp, destination in extracted_data["warps"].items():
|
||||||
|
data.warp_map[warp] = None if destination == "" else destination
|
||||||
|
|
||||||
|
if encoded_warp not in data.warp_map:
|
||||||
|
data.warp_map[encoded_warp] = None
|
||||||
|
|
||||||
|
# Create trainer data
|
||||||
|
for i, trainer_json in enumerate(extracted_data["trainers"]):
|
||||||
|
party_json = trainer_json["party"]
|
||||||
|
pokemon_data_type = _str_to_pokemon_data_type(trainer_json["pokemon_data_type"])
|
||||||
|
data.trainers.append(TrainerData(
|
||||||
|
i,
|
||||||
|
TrainerPartyData(
|
||||||
|
[TrainerPokemonData(
|
||||||
|
p["species"],
|
||||||
|
p["level"],
|
||||||
|
(p["moves"][0], p["moves"][1], p["moves"][2], p["moves"][3])
|
||||||
|
) for p in party_json],
|
||||||
|
pokemon_data_type,
|
||||||
|
trainer_json["party_rom_address"]
|
||||||
|
),
|
||||||
|
trainer_json["rom_address"],
|
||||||
|
trainer_json["battle_script_rom_address"]
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
_init()
|
99
worlds/pokemon_emerald/data/README.md
Normal file
99
worlds/pokemon_emerald/data/README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
## `regions/`
|
||||||
|
|
||||||
|
These define regions, connections, and where locations are. If you know what you're doing, it should be pretty clear how
|
||||||
|
this works by taking a quick look through the files. The rest of this section is pretty verbose to cover everything. Not
|
||||||
|
to say you shouldn't read it, but the tl;dr is:
|
||||||
|
|
||||||
|
- Every map, even trivial ones, gets a region definition, and they cannot be coalesced (because of warp rando)
|
||||||
|
- Stick to the naming convention for regions and events (look at Route 103 and Petalburg City for guidance)
|
||||||
|
- Locations and warps can only be claimed by one region
|
||||||
|
- Events are declared here
|
||||||
|
|
||||||
|
A `Map`, which you will see referenced in `parent_map` attribute in the region JSON, is an id from the source code.
|
||||||
|
`Map`s are sets of tiles, encounters, warps, events, and so on. Route 103, Littleroot Town, the Oldale Town Mart, the
|
||||||
|
second floor of Devon Corp, and each level of Victory Road are all examples of `Map`s. You transition between `Map`s by
|
||||||
|
stepping on a warp (warp pads, doorways, etc...) or walking over a border between `Map`s in the overworld. Some warps
|
||||||
|
don't go to a different `Map`.
|
||||||
|
|
||||||
|
Regions usually describe physical areas which are subsets of a `Map`. Every `Map` must have one or more defined regions.
|
||||||
|
A region should not contain area from more than one `Map`. We'll need to draw those lines now even when there is no
|
||||||
|
logical boundary (like between two the first and second floors of your rival's house), for warp rando.
|
||||||
|
|
||||||
|
Most `Map`s have been split into multiple regions. In the example below, `MAP_ROUTE103` was split into
|
||||||
|
`REGION_ROUTE_103/WEST`, `REGION_ROUTE_103/WATER`, and `REGION_ROUTE_103/EAST` (this document may be out of date; the
|
||||||
|
example is demonstrative). Keeping the name consistent with the `Map` name and adding a label suffix for the subarea
|
||||||
|
makes it clearer where we are in the world and where within a `Map` we're describing.
|
||||||
|
|
||||||
|
Every region (except `Menu`) is configured here. All files in this directory are combined with each other at runtime,
|
||||||
|
and are only split and ordered for organization. Regions defined in `data/regions/unused` are entirely unused because
|
||||||
|
they're not yet reachable in the randomizer. They're there for future reference in case we want to pull those maps in
|
||||||
|
later. Any locations or warps in here should be ignored. Data for a single region looks like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"REGION_ROUTE103/EAST": {
|
||||||
|
"parent_map": "MAP_ROUTE103",
|
||||||
|
"locations": [
|
||||||
|
"ITEM_ROUTE_103_GUARD_SPEC",
|
||||||
|
"ITEM_ROUTE_103_PP_UP"
|
||||||
|
],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_ROUTE103/WATER",
|
||||||
|
"REGION_ROUTE110/MAIN"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE103:0/MAP_ALTERING_CAVE:0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `[key]`: The name of the object, in this case `REGION_ROUTE103/EAST`, should be the value of `parent_map` where the
|
||||||
|
`MAP` prefix is replaced with `REGION`. Then there should be a following `/` and a label describing this specific region
|
||||||
|
within the `Map`. This is not enforced or required by the code, but it makes things much more clear.
|
||||||
|
- `parent_map`: The name of the `Map` this region exists under. It can relate this region to information like encounter
|
||||||
|
tables.
|
||||||
|
- `locations`: Locations contained within this region. This can be anything from an item on the ground to a badge to a
|
||||||
|
gift from an NPC. Locations themselves are defined in `data/extracted_data.json`, and the names used here should come
|
||||||
|
directly from it.
|
||||||
|
- `events`: Events that can be completed in this region. Defeating a gym leader or Aqua/Magma team leader, for example,
|
||||||
|
can trigger story progression and unblock roads and buildings. Events are defined here and nowhere else, and access
|
||||||
|
rules are set in `rules.py`.
|
||||||
|
- `exits`: Names of regions that can be directly accessed from this one. Most often regions within the same `Map`,
|
||||||
|
neighboring maps in the overworld, or transitions from using HM08 Dive. Most connections between maps/regions come from
|
||||||
|
warps. Any region in this list should be defined somewhere in `data/regions`.
|
||||||
|
- `warps`: Warp events contained within this region. Warps are defined in `data/extracted_data.json`, and must exist
|
||||||
|
there to be referenced here. More on warps in [../README.md](../README.md).
|
||||||
|
|
||||||
|
Think of this data as defining which regions are "claiming" a given location, event, or warp. No more than one region
|
||||||
|
may claim ownership of a location. Even if some "thing" may happen in two different regions and set the same flag, they
|
||||||
|
should be defined as two different events and anything conditional on said "thing" happening can check whether either of
|
||||||
|
the two events is accessible. (e.g. Interacting with the Poke Ball in your rival's room and going back downstairs will
|
||||||
|
both trigger a conversation with them which enables you to rescue Professor Birch. It's the same "thing" on two
|
||||||
|
different `Map`s.)
|
||||||
|
|
||||||
|
Conceptually, you shouldn't have to "add" any new regions. You should only have to "split" existing regions. When you
|
||||||
|
split a region, make sure to correctly reassign `locations`, `events`, `exits`, and `warps` according to which new
|
||||||
|
region they now exist in. Make sure to define new `exits` to link the new regions to each other if applicable. And
|
||||||
|
especially remember to rename incoming `exits` defined in other regions which are still pointing to the pre-split
|
||||||
|
region. `sanity_check.py` should catch you if there are other regions that point to a region that no longer exists, but
|
||||||
|
if one of your newly-split regions still has the same name as the original, it won't be detected and you may find that
|
||||||
|
things aren't connected correctly.
|
||||||
|
|
||||||
|
## `extracted_data.json`
|
||||||
|
|
||||||
|
DO NOT TOUCH
|
||||||
|
|
||||||
|
Contains data automatically pulled from the base rom and its source code when it is built. There should be no reason to
|
||||||
|
manually modify it. Data from this file is piped through `data.py` to create a data object that's more useful and
|
||||||
|
complete.
|
||||||
|
|
||||||
|
## `items.json`
|
||||||
|
|
||||||
|
A map from items as defined in the `constants` in `extracted_data.json` to useful info like a human-friendly label, the
|
||||||
|
type of progression it enables, and tags to associate. There are many unused items and extra helper constants in
|
||||||
|
`extracted_data.json`, so this file contains an exhaustive list of items which can actually be found in the modded game.
|
||||||
|
|
||||||
|
## `locations.json`
|
||||||
|
|
||||||
|
Similar to `items.json`, this associates locations with human-friendly labels and tags that are used for filtering. Any
|
||||||
|
locations claimed by any region need an entry here.
|
BIN
worlds/pokemon_emerald/data/base_patch.bsdiff4
Normal file
BIN
worlds/pokemon_emerald/data/base_patch.bsdiff4
Normal file
Binary file not shown.
1
worlds/pokemon_emerald/data/extracted_data.json
Normal file
1
worlds/pokemon_emerald/data/extracted_data.json
Normal file
File diff suppressed because one or more lines are too long
1481
worlds/pokemon_emerald/data/items.json
Normal file
1481
worlds/pokemon_emerald/data/items.json
Normal file
File diff suppressed because it is too large
Load Diff
1441
worlds/pokemon_emerald/data/locations.json
Normal file
1441
worlds/pokemon_emerald/data/locations.json
Normal file
File diff suppressed because it is too large
Load Diff
2604
worlds/pokemon_emerald/data/regions/cities.json
Normal file
2604
worlds/pokemon_emerald/data/regions/cities.json
Normal file
File diff suppressed because it is too large
Load Diff
2231
worlds/pokemon_emerald/data/regions/dungeons.json
Normal file
2231
worlds/pokemon_emerald/data/regions/dungeons.json
Normal file
File diff suppressed because it is too large
Load Diff
1871
worlds/pokemon_emerald/data/regions/routes.json
Normal file
1871
worlds/pokemon_emerald/data/regions/routes.json
Normal file
File diff suppressed because it is too large
Load Diff
396
worlds/pokemon_emerald/data/regions/unused/battle_frontier.json
Normal file
396
worlds/pokemon_emerald/data/regions/unused/battle_frontier.json
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
{
|
||||||
|
"REGION_BATTLE_FRONTIER_RECEPTION_GATE/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_RECEPTION_GATE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_RECEPTION_GATE:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:8",
|
||||||
|
"MAP_BATTLE_FRONTIER_RECEPTION_GATE:1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:9"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/DOCK": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_WEST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_SLATEPORT_CITY_HARBOR/MAIN",
|
||||||
|
"REGION_LILYCOVE_CITY_HARBOR/MAIN"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:8/MAP_BATTLE_FRONTIER_RECEPTION_GATE:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_WEST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/MAIN"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:7/MAP_BATTLE_FRONTIER_LOUNGE7:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:2/MAP_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1/MAP_BATTLE_FRONTIER_BATTLE_DOME_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:9/MAP_BATTLE_FRONTIER_RECEPTION_GATE:1",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:0/MAP_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:5/MAP_BATTLE_FRONTIER_SCOTTS_HOUSE:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:3/MAP_BATTLE_FRONTIER_LOUNGE2:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:4/MAP_BATTLE_FRONTIER_MART:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:6/MAP_BATTLE_FRONTIER_LOUNGE4:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/WATER": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_WEST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/WATER",
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/CAVE_ENTRANCE"
|
||||||
|
],
|
||||||
|
"warps": []
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/CAVE_ENTRANCE": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_WEST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/WATER"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:10/MAP_ARTISAN_CAVE_B1F:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_EAST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/MAIN",
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/ABOVE_WATERFALL"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:12/MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:5/MAP_BATTLE_FRONTIER_LOUNGE1:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:8/MAP_BATTLE_FRONTIER_LOUNGE6:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:6/MAP_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:10/MAP_BATTLE_FRONTIER_LOUNGE8:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:0/MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:11/MAP_BATTLE_FRONTIER_LOUNGE9:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:7/MAP_BATTLE_FRONTIER_LOUNGE5:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:4/MAP_BATTLE_FRONTIER_RANKING_HALL:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:1/MAP_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:3/MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:9/MAP_BATTLE_FRONTIER_LOUNGE3:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/CAVE_ENTRANCE": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_EAST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/MAIN"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:13/MAP_ARTISAN_CAVE_1F:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/ABOVE_WATERFALL": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_EAST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/MAIN",
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/WATER"
|
||||||
|
],
|
||||||
|
"warps": []
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/WATER": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_OUTSIDE_EAST",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_EAST/ABOVE_WATERFALL",
|
||||||
|
"REGION_BATTLE_FRONTIER_OUTSIDE_WEST/WATER"
|
||||||
|
],
|
||||||
|
"warps": []
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY:0,1,2/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_DOME_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_DOME_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_DOME_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_DOME_PRE_BATTLE_ROOM/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_DOME_PRE_BATTLE_ROOM",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_DOME_PRE_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_DOME_CORRIDOR/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_DOME_CORRIDOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_DOME_CORRIDOR:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:0,1/MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:3/MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:2/MAP_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER:0,1,2/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_RANKING_HALL/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_RANKING_HALL",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_RANKING_HALL:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_POKEMON_CENTER_1F/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:2/MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_POKEMON_CENTER_2F/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:0/MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_MART/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_MART",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_MART:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_SCOTTS_HOUSE/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_SCOTTS_HOUSE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_SCOTTS_HOUSE:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE1/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE1",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE1:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE2/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE2",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE2:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE3/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE3",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE3:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:9"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE4/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE4",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE4:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE5/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE5",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE5:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:7"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE6/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE6",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE6:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE7/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE7",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE7:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:7"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE8/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE8",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE8:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:10"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BATTLE_FRONTIER_LOUNGE9/MAIN": {
|
||||||
|
"parent_map": "MAP_BATTLE_FRONTIER_LOUNGE9",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE9:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:11"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"REGION_ARTISAN_CAVE_1F/MAIN": {
|
||||||
|
"parent_map": "MAP_ARTISAN_CAVE_1F",
|
||||||
|
"locations": [
|
||||||
|
"ITEM_ARTISAN_CAVE_1F_CARBOS"
|
||||||
|
],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ARTISAN_CAVE_1F:1/MAP_ARTISAN_CAVE_B1F:1",
|
||||||
|
"MAP_ARTISAN_CAVE_1F:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:13"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ARTISAN_CAVE_B1F/MAIN": {
|
||||||
|
"parent_map": "MAP_ARTISAN_CAVE_B1F",
|
||||||
|
"locations": [
|
||||||
|
"ITEM_ARTISAN_CAVE_B1F_HP_UP",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_ZINC",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_CALCIUM",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_PROTEIN",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_IRON"
|
||||||
|
],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ARTISAN_CAVE_B1F:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:10",
|
||||||
|
"MAP_ARTISAN_CAVE_B1F:1/MAP_ARTISAN_CAVE_1F:1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
52
worlds/pokemon_emerald/data/regions/unused/dungeons.json
Normal file
52
worlds/pokemon_emerald/data/regions/unused/dungeons.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"REGION_TERRA_CAVE_ENTRANCE/MAIN": {
|
||||||
|
"parent_map": "MAP_TERRA_CAVE_ENTRANCE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_TERRA_CAVE_ENTRANCE:0/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_TERRA_CAVE_ENTRANCE:1/MAP_TERRA_CAVE_END:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_TERRA_CAVE_END/MAIN": {
|
||||||
|
"parent_map": "MAP_TERRA_CAVE_END",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_TERRA_CAVE_END:0/MAP_TERRA_CAVE_ENTRANCE:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_UNDERWATER_MARINE_CAVE/MAIN": {
|
||||||
|
"parent_map": "MAP_UNDERWATER_MARINE_CAVE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_MARINE_CAVE_ENTRANCE/MAIN"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_UNDERWATER_MARINE_CAVE:0/MAP_DYNAMIC:-1!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_MARINE_CAVE_ENTRANCE/MAIN": {
|
||||||
|
"parent_map": "MAP_MARINE_CAVE_ENTRANCE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [
|
||||||
|
"REGION_UNDERWATER_MARINE_CAVE/MAIN"
|
||||||
|
],
|
||||||
|
"warps": [
|
||||||
|
"MAP_MARINE_CAVE_ENTRANCE:0/MAP_MARINE_CAVE_END:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_MARINE_CAVE_END/MAIN": {
|
||||||
|
"parent_map": "MAP_MARINE_CAVE_END",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_MARINE_CAVE_END:0/MAP_MARINE_CAVE_ENTRANCE:0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
276
worlds/pokemon_emerald/data/regions/unused/islands.json
Normal file
276
worlds/pokemon_emerald/data/regions/unused/islands.json
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
{
|
||||||
|
"REGION_SOUTHERN_ISLAND_EXTERIOR/MAIN": {
|
||||||
|
"parent_map": "MAP_SOUTHERN_ISLAND_EXTERIOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_SOUTHERN_ISLAND_EXTERIOR:0,1/MAP_SOUTHERN_ISLAND_INTERIOR:0,1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_SOUTHERN_ISLAND_INTERIOR/MAIN": {
|
||||||
|
"parent_map": "MAP_SOUTHERN_ISLAND_INTERIOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_SOUTHERN_ISLAND_INTERIOR:0,1/MAP_SOUTHERN_ISLAND_EXTERIOR:0,1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_FARAWAY_ISLAND_ENTRANCE/MAIN": {
|
||||||
|
"parent_map": "MAP_FARAWAY_ISLAND_ENTRANCE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_FARAWAY_ISLAND_ENTRANCE:0,1/MAP_FARAWAY_ISLAND_INTERIOR:0,1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_FARAWAY_ISLAND_INTERIOR/MAIN": {
|
||||||
|
"parent_map": "MAP_FARAWAY_ISLAND_INTERIOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_FARAWAY_ISLAND_INTERIOR:0,1/MAP_FARAWAY_ISLAND_ENTRANCE:0,1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BIRTH_ISLAND_HARBOR/MAIN": {
|
||||||
|
"parent_map": "MAP_BIRTH_ISLAND_HARBOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BIRTH_ISLAND_HARBOR:0/MAP_BIRTH_ISLAND_EXTERIOR:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_BIRTH_ISLAND_EXTERIOR/MAIN": {
|
||||||
|
"parent_map": "MAP_BIRTH_ISLAND_EXTERIOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_BIRTH_ISLAND_EXTERIOR:0/MAP_BIRTH_ISLAND_HARBOR:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_HARBOR/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_HARBOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_HARBOR:0/MAP_NAVEL_ROCK_EXTERIOR:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_EXTERIOR/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_EXTERIOR",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_EXTERIOR:0/MAP_NAVEL_ROCK_HARBOR:0",
|
||||||
|
"MAP_NAVEL_ROCK_EXTERIOR:1/MAP_NAVEL_ROCK_ENTRANCE:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_ENTRANCE/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_ENTRANCE",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_ENTRANCE:0/MAP_NAVEL_ROCK_B1F:0",
|
||||||
|
"MAP_NAVEL_ROCK_ENTRANCE:1/MAP_NAVEL_ROCK_EXTERIOR:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_B1F/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_B1F",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_B1F:0/MAP_NAVEL_ROCK_ENTRANCE:0",
|
||||||
|
"MAP_NAVEL_ROCK_B1F:1/MAP_NAVEL_ROCK_FORK:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_FORK/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_FORK",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_FORK:0/MAP_NAVEL_ROCK_UP1:0",
|
||||||
|
"MAP_NAVEL_ROCK_FORK:1/MAP_NAVEL_ROCK_B1F:1",
|
||||||
|
"MAP_NAVEL_ROCK_FORK:2/MAP_NAVEL_ROCK_DOWN01:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN01/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN01",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN01:0/MAP_NAVEL_ROCK_FORK:2",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN01:1/MAP_NAVEL_ROCK_DOWN02:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN02/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN02",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN02:1/MAP_NAVEL_ROCK_DOWN03:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN02:0/MAP_NAVEL_ROCK_DOWN01:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN03/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN03",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN03:0/MAP_NAVEL_ROCK_DOWN02:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN03:1/MAP_NAVEL_ROCK_DOWN04:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN04/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN04",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN04:1/MAP_NAVEL_ROCK_DOWN05:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN04:0/MAP_NAVEL_ROCK_DOWN03:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN05/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN05",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN05:0/MAP_NAVEL_ROCK_DOWN04:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN05:1/MAP_NAVEL_ROCK_DOWN06:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN06/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN06",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN06:1/MAP_NAVEL_ROCK_DOWN07:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN06:0/MAP_NAVEL_ROCK_DOWN05:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN07/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN07",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN07:0/MAP_NAVEL_ROCK_DOWN06:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN07:1/MAP_NAVEL_ROCK_DOWN08:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN08/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN08",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN08:1/MAP_NAVEL_ROCK_DOWN09:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN08:0/MAP_NAVEL_ROCK_DOWN07:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN09/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN09",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN09:0/MAP_NAVEL_ROCK_DOWN08:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN09:1/MAP_NAVEL_ROCK_DOWN10:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN10/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN10",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN10:1/MAP_NAVEL_ROCK_DOWN11:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN10:0/MAP_NAVEL_ROCK_DOWN09:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_DOWN11/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_DOWN11",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_DOWN11:1/MAP_NAVEL_ROCK_DOWN10:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN11:0/MAP_NAVEL_ROCK_BOTTOM:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_BOTTOM/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_BOTTOM",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_BOTTOM:0/MAP_NAVEL_ROCK_DOWN11:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_UP1/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_UP1",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_UP1:1/MAP_NAVEL_ROCK_UP2:0",
|
||||||
|
"MAP_NAVEL_ROCK_UP1:0/MAP_NAVEL_ROCK_FORK:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_UP2/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_UP2",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_UP2:0/MAP_NAVEL_ROCK_UP1:1",
|
||||||
|
"MAP_NAVEL_ROCK_UP2:1/MAP_NAVEL_ROCK_UP3:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_UP3/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_UP3",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_UP3:1/MAP_NAVEL_ROCK_UP4:0",
|
||||||
|
"MAP_NAVEL_ROCK_UP3:0/MAP_NAVEL_ROCK_UP2:1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_UP4/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_UP4",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_UP4:0/MAP_NAVEL_ROCK_UP3:1",
|
||||||
|
"MAP_NAVEL_ROCK_UP4:1/MAP_NAVEL_ROCK_TOP:0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_NAVEL_ROCK_TOP/MAIN": {
|
||||||
|
"parent_map": "MAP_NAVEL_ROCK_TOP",
|
||||||
|
"locations": [
|
||||||
|
"HIDDEN_ITEM_NAVEL_ROCK_TOP_SACRED_ASH"
|
||||||
|
],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_NAVEL_ROCK_TOP:0/MAP_NAVEL_ROCK_UP4:1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
82
worlds/pokemon_emerald/data/regions/unused/routes.json
Normal file
82
worlds/pokemon_emerald/data/regions/unused/routes.json
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE2/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE2",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE2:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE2:2/MAP_ROUTE110_TRICK_HOUSE_END:0!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE3/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE3",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE3:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE3:2/MAP_ROUTE110_TRICK_HOUSE_END:0!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE4/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE4",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE4:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE4:2/MAP_ROUTE110_TRICK_HOUSE_END:0!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE5/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE5",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE5:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE5:2/MAP_ROUTE110_TRICK_HOUSE_END:0!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE6/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE6",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE6:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE6:2/MAP_ROUTE110_TRICK_HOUSE_END:0!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE7/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE7",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:8/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:7",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:7/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:8",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:11/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:12",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:9/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:10",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:6/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:5",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:10/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:9",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:4/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:3",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:12/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:11",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:3/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:4",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:5/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"REGION_ROUTE110_TRICK_HOUSE_PUZZLE8/MAIN": {
|
||||||
|
"parent_map": "MAP_ROUTE110_TRICK_HOUSE_PUZZLE8",
|
||||||
|
"locations": [],
|
||||||
|
"events": [],
|
||||||
|
"exits": [],
|
||||||
|
"warps": [
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE8:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE8:2/MAP_ROUTE110_TRICK_HOUSE_END:0!"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
78
worlds/pokemon_emerald/docs/en_Pokemon Emerald.md
Normal file
78
worlds/pokemon_emerald/docs/en_Pokemon Emerald.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Pokémon Emerald
|
||||||
|
|
||||||
|
## Where is the settings page?
|
||||||
|
|
||||||
|
You can read through all the settings and generate a YAML [here](../player-settings).
|
||||||
|
|
||||||
|
## What does randomization do to this game?
|
||||||
|
|
||||||
|
This randomizer handles both item randomization and pokémon randomization. Badges, HMs, gifts from NPCs, and items on
|
||||||
|
the ground can all be randomized. There are also many options for randomizing wild pokémon, starters, opponent pokémon,
|
||||||
|
abilities, types, etc… You can even change a percentage of single battles into double battles. Check the
|
||||||
|
[settings page](../player-settings) for a more comprehensive list of what can be changed.
|
||||||
|
|
||||||
|
## What items and locations get randomized?
|
||||||
|
|
||||||
|
The most interesting items that can be added to the item pool are badges and HMs, which most affect what locations you
|
||||||
|
can access. Key items like the Devon Scope or Mach Bike can also be randomized, as well as the many Potions, Revives,
|
||||||
|
TMs, and other items that you can find on the ground or receive as gifts.
|
||||||
|
|
||||||
|
## What other changes are made to the game?
|
||||||
|
|
||||||
|
There are many quality of life improvements meant to speed up the game a little and improve the experience of playing a
|
||||||
|
randomizer. Here are some of the more important ones:
|
||||||
|
|
||||||
|
- Shoal Cave switches between high tide and low tide every time you re-enter
|
||||||
|
- Bag space is greatly expanded (you're all but guaranteed to never need to store items in the PC)
|
||||||
|
- Trade evolutions have been changed to level or item evolutions
|
||||||
|
- You can have both bikes simultaneously
|
||||||
|
- You can run or bike (almost) anywhere
|
||||||
|
- The Wally catching tutorial is skipped
|
||||||
|
- All text is instant, and with a setting it can be automatically progressed by holding A
|
||||||
|
- When a Repel runs out, you will be prompted to use another
|
||||||
|
- Many more minor improvements…
|
||||||
|
|
||||||
|
## Where is my starting inventory?
|
||||||
|
|
||||||
|
Except for badges, your starting inventory will be in the PC.
|
||||||
|
|
||||||
|
## What does another world's item look like in Pokémon Emerald?
|
||||||
|
|
||||||
|
When you find an item that is not your own, you will instead receive an "ARCHIPELAGO ITEM" which will *not* be added to
|
||||||
|
your inventory.
|
||||||
|
|
||||||
|
## When the player receives an item, what happens?
|
||||||
|
|
||||||
|
You will only receive items while in the overworld and not during battles. Depending on your `Receive Item Messages`
|
||||||
|
setting, the received item will either be silently added to your bag or you will be shown a text box with the item's
|
||||||
|
name and the item will be added to your bag while a fanfare plays.
|
||||||
|
|
||||||
|
## Can I play offline?
|
||||||
|
|
||||||
|
Yes, the client and connector are only necessary for sending and receiving items. If you're playing a solo game, you
|
||||||
|
don't need to play online unless you want the rest of Archipelago's functionality (like hints and auto-tracking). If
|
||||||
|
you're playing a multiworld game, the client will sync your game with the server the next time you connect.
|
||||||
|
|
||||||
|
## Will battle mechanics be updated?
|
||||||
|
|
||||||
|
This is something we'd love to see, but it's unlikely. We don't want to force new mechanics on players who would prefer
|
||||||
|
to play with the classic mechanics, but trying to switch between old and new mechanics based on an option would be a
|
||||||
|
monumental task, and is probably best solved some other way.
|
||||||
|
|
||||||
|
## Is this randomizer compatible with other mods?
|
||||||
|
|
||||||
|
No, other mods cannot be applied. It would be impossible to generalize this implementation's changes in a way that is
|
||||||
|
compatible with any other mod or romhack. Romhacks could be added as their own games, but they would have to be
|
||||||
|
implemented separately. Check out [Archipelago's Discord server](https://discord.gg/8Z65BR2) if you want to make a
|
||||||
|
suggestion or contribute.
|
||||||
|
|
||||||
|
## Can I use tools like the Universal Pokémon Randomizer?
|
||||||
|
|
||||||
|
No, those tools expect data to be in certain locations and in a certain format, but this randomizer has to shift it
|
||||||
|
around. Using tools to try to modify the game would only corrupt the ROM.
|
||||||
|
|
||||||
|
We realize this means breaking from established habits when it comes to randomizing Pokémon games, but this randomizer
|
||||||
|
would be many times more complex to develop if it were constrained by something like UPR.
|
||||||
|
|
||||||
|
The one exception might be PKHeX. You may be able to extract pokémon from your save using PKHeX, but this isn't a
|
||||||
|
guarantee, and we make no effort to keep our saves compatible with PKHeX.
|
72
worlds/pokemon_emerald/docs/setup_en.md
Normal file
72
worlds/pokemon_emerald/docs/setup_en.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Pokémon Emerald Setup Guide
|
||||||
|
|
||||||
|
## Required Software
|
||||||
|
|
||||||
|
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||||
|
- An English Pokémon Emerald ROM. The Archipelago community cannot provide this.
|
||||||
|
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later
|
||||||
|
|
||||||
|
### Configuring BizHawk
|
||||||
|
|
||||||
|
Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings:
|
||||||
|
|
||||||
|
- If you're using BizHawk 2.7 or 2.8, go to `Config > Customize`. On the Advanced tab, switch the Lua Core from
|
||||||
|
`NLua+KopiLua` to `Lua+LuaInterface`, then restart EmuHawk. (If you're using BizHawk 2.9, you can skip this step.)
|
||||||
|
- Under `Config > Customize`, check the "Run in background" option to prevent disconnecting from the client while you're
|
||||||
|
tabbed out of EmuHawk.
|
||||||
|
- Open a `.gba` file in EmuHawk and go to `Config > Controllers…` to configure your inputs. If you can't click
|
||||||
|
`Controllers…`, load any `.gba` ROM first.
|
||||||
|
- Consider clearing keybinds in `Config > Hotkeys…` if you don't intend to use them. Select the keybind and press Esc to
|
||||||
|
clear it.
|
||||||
|
|
||||||
|
## Optional Software
|
||||||
|
|
||||||
|
- [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest), for use with
|
||||||
|
[PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||||
|
|
||||||
|
## Generating and Patching a Game
|
||||||
|
|
||||||
|
1. Create your settings file (YAML). You can make one on the
|
||||||
|
[Pokémon Emerald settings page](../../../games/Pokemon%20Emerald/player-settings).
|
||||||
|
2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game).
|
||||||
|
This will generate an output file for you. Your patch file will have the `.apemerald` file extension.
|
||||||
|
3. Open `ArchipelagoLauncher.exe`
|
||||||
|
4. Select "Open Patch" on the left side and select your patch file.
|
||||||
|
5. If this is your first time patching, you will be prompted to locate your vanilla ROM.
|
||||||
|
6. A patched `.gba` file will be created in the same place as the patch file.
|
||||||
|
7. On your first time opening a patch with BizHawk Client, you will also be asked to locate `EmuHawk.exe` in your
|
||||||
|
BizHawk install.
|
||||||
|
|
||||||
|
If you're playing a single-player seed and you don't care about autotracking or hints, you can stop here, close the
|
||||||
|
client, and load the patched ROM in any emulator. However, for multiworlds and other Archipelago features, continue
|
||||||
|
below using BizHawk as your emulator.
|
||||||
|
|
||||||
|
## Connecting to a Server
|
||||||
|
|
||||||
|
By default, opening a patch file will do steps 1-5 below for you automatically. Even so, keep them in your memory just
|
||||||
|
in case you have to close and reopen a window mid-game for some reason.
|
||||||
|
|
||||||
|
1. Pokemon Emerald uses Archipelago's BizHawk Client. If the client isn't still open from when you patched your game,
|
||||||
|
you can re-open it from the launcher.
|
||||||
|
2. Ensure EmuHawk is running the patched ROM.
|
||||||
|
3. In EmuHawk, go to `Tools > Lua Console`. This window must stay open while playing.
|
||||||
|
4. In the Lua Console window, go to `Script > Open Script…`.
|
||||||
|
5. Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`.
|
||||||
|
6. The emulator may freeze every few seconds until it manages to connect to the client. This is expected. The BizHawk
|
||||||
|
Client window should indicate that it connected and recognized Pokemon Emerald.
|
||||||
|
7. To connect the client to the server, enter your room's address and port (e.g. `archipelago.gg:38281`) into the
|
||||||
|
top text field of the client and click Connect.
|
||||||
|
|
||||||
|
You should now be able to receive and send items. You'll need to do these steps every time you want to reconnect. It is
|
||||||
|
perfectly safe to make progress offline; everything will re-sync when you reconnect.
|
||||||
|
|
||||||
|
## Auto-Tracking
|
||||||
|
|
||||||
|
Pokémon Emerald has a fully functional map tracker that supports auto-tracking.
|
||||||
|
|
||||||
|
1. Download [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest) and
|
||||||
|
[PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||||
|
2. Put the tracker pack into packs/ in your PopTracker install.
|
||||||
|
3. Open PopTracker, and load the Pokémon Emerald pack.
|
||||||
|
4. For autotracking, click on the "AP" symbol at the top.
|
||||||
|
5. Enter the Archipelago server address (the one you connected your client to), slot name, and password.
|
77
worlds/pokemon_emerald/items.py
Normal file
77
worlds/pokemon_emerald/items.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
Classes and functions related to AP items for Pokemon Emerald
|
||||||
|
"""
|
||||||
|
from typing import Dict, FrozenSet, Optional
|
||||||
|
|
||||||
|
from BaseClasses import Item, ItemClassification
|
||||||
|
|
||||||
|
from .data import BASE_OFFSET, data
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldItem(Item):
|
||||||
|
game: str = "Pokemon Emerald"
|
||||||
|
tags: FrozenSet[str]
|
||||||
|
|
||||||
|
def __init__(self, name: str, classification: ItemClassification, code: Optional[int], player: int) -> None:
|
||||||
|
super().__init__(name, classification, code, player)
|
||||||
|
|
||||||
|
if code is None:
|
||||||
|
self.tags = frozenset(["Event"])
|
||||||
|
else:
|
||||||
|
self.tags = data.items[reverse_offset_item_value(code)].tags
|
||||||
|
|
||||||
|
|
||||||
|
def offset_item_value(item_value: int) -> int:
|
||||||
|
"""
|
||||||
|
Returns the AP item id (code) for a given item value
|
||||||
|
"""
|
||||||
|
return item_value + BASE_OFFSET
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_offset_item_value(item_id: int) -> int:
|
||||||
|
"""
|
||||||
|
Returns the item value for a given AP item id (code)
|
||||||
|
"""
|
||||||
|
return item_id - BASE_OFFSET
|
||||||
|
|
||||||
|
|
||||||
|
def create_item_label_to_code_map() -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
Creates a map from item labels to their AP item id (code)
|
||||||
|
"""
|
||||||
|
label_to_code_map: Dict[str, int] = {}
|
||||||
|
for item_value, attributes in data.items.items():
|
||||||
|
label_to_code_map[attributes.label] = offset_item_value(item_value)
|
||||||
|
|
||||||
|
return label_to_code_map
|
||||||
|
|
||||||
|
|
||||||
|
ITEM_GROUPS = {
|
||||||
|
"Badges": {
|
||||||
|
"Stone Badge", "Knuckle Badge",
|
||||||
|
"Dynamo Badge", "Heat Badge",
|
||||||
|
"Balance Badge", "Feather Badge",
|
||||||
|
"Mind Badge", "Rain Badge"
|
||||||
|
},
|
||||||
|
"HMs": {
|
||||||
|
"HM01 Cut", "HM02 Fly",
|
||||||
|
"HM03 Surf", "HM04 Strength",
|
||||||
|
"HM05 Flash", "HM06 Rock Smash",
|
||||||
|
"HM07 Waterfall", "HM08 Dive"
|
||||||
|
},
|
||||||
|
"HM01": {"HM01 Cut"},
|
||||||
|
"HM02": {"HM02 Fly"},
|
||||||
|
"HM03": {"HM03 Surf"},
|
||||||
|
"HM04": {"HM04 Strength"},
|
||||||
|
"HM05": {"HM05 Flash"},
|
||||||
|
"HM06": {"HM06 Rock Smash"},
|
||||||
|
"HM07": {"HM07 Waterfall"},
|
||||||
|
"HM08": {"HM08 Dive"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_classification(item_code: int) -> ItemClassification:
|
||||||
|
"""
|
||||||
|
Returns the item classification for a given AP item id (code)
|
||||||
|
"""
|
||||||
|
return data.items[reverse_offset_item_value(item_code)].classification
|
122
worlds/pokemon_emerald/locations.py
Normal file
122
worlds/pokemon_emerald/locations.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"""
|
||||||
|
Classes and functions related to AP locations for Pokemon Emerald
|
||||||
|
"""
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Optional, FrozenSet, Iterable
|
||||||
|
|
||||||
|
from BaseClasses import Location, Region
|
||||||
|
|
||||||
|
from .data import BASE_OFFSET, data
|
||||||
|
from .items import offset_item_value
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from . import PokemonEmeraldWorld
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldLocation(Location):
|
||||||
|
game: str = "Pokemon Emerald"
|
||||||
|
rom_address: Optional[int]
|
||||||
|
default_item_code: Optional[int]
|
||||||
|
tags: FrozenSet[str]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
player: int,
|
||||||
|
name: str,
|
||||||
|
flag: Optional[int],
|
||||||
|
parent: Optional[Region] = None,
|
||||||
|
rom_address: Optional[int] = None,
|
||||||
|
default_item_value: Optional[int] = None,
|
||||||
|
tags: FrozenSet[str] = frozenset()) -> None:
|
||||||
|
super().__init__(player, name, None if flag is None else offset_flag(flag), parent)
|
||||||
|
self.default_item_code = None if default_item_value is None else offset_item_value(default_item_value)
|
||||||
|
self.rom_address = rom_address
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
|
|
||||||
|
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_with_tags(world: "PokemonEmeraldWorld", regions: Dict[str, Region], tags: Iterable[str]) -> None:
|
||||||
|
"""
|
||||||
|
Iterates through region data and adds locations to the multiworld if
|
||||||
|
those locations include any of the provided tags.
|
||||||
|
"""
|
||||||
|
tags = set(tags)
|
||||||
|
|
||||||
|
for region_name, region_data in data.regions.items():
|
||||||
|
region = regions[region_name]
|
||||||
|
filtered_locations = [loc for loc in region_data.locations if len(tags & data.locations[loc].tags) > 0]
|
||||||
|
|
||||||
|
for location_name in filtered_locations:
|
||||||
|
location_data = data.locations[location_name]
|
||||||
|
location = PokemonEmeraldLocation(
|
||||||
|
world.player,
|
||||||
|
location_data.label,
|
||||||
|
location_data.flag,
|
||||||
|
region,
|
||||||
|
location_data.rom_address,
|
||||||
|
location_data.default_item,
|
||||||
|
location_data.tags
|
||||||
|
)
|
||||||
|
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]
|
||||||
|
label_to_id_map[location_data.label] = offset_flag(location_data.flag)
|
||||||
|
|
||||||
|
return label_to_id_map
|
||||||
|
|
||||||
|
|
||||||
|
LOCATION_GROUPS = {
|
||||||
|
"Badges": {
|
||||||
|
"Rustboro Gym - Stone Badge",
|
||||||
|
"Dewford Gym - Knuckle Badge",
|
||||||
|
"Mauville Gym - Dynamo Badge",
|
||||||
|
"Lavaridge Gym - Heat Badge",
|
||||||
|
"Petalburg Gym - Balance Badge",
|
||||||
|
"Fortree Gym - Feather Badge",
|
||||||
|
"Mossdeep Gym - Mind Badge",
|
||||||
|
"Sootopolis Gym - Rain Badge",
|
||||||
|
},
|
||||||
|
"Gym TMs": {
|
||||||
|
"Rustboro Gym - TM39 from Roxanne",
|
||||||
|
"Dewford Gym - TM08 from Brawly",
|
||||||
|
"Mauville Gym - TM34 from Wattson",
|
||||||
|
"Lavaridge Gym - TM50 from Flannery",
|
||||||
|
"Petalburg Gym - TM42 from Norman",
|
||||||
|
"Fortree Gym - TM40 from Winona",
|
||||||
|
"Mossdeep Gym - TM04 from Tate and Liza",
|
||||||
|
"Sootopolis Gym - TM03 from Juan",
|
||||||
|
},
|
||||||
|
"Postgame Locations": {
|
||||||
|
"Littleroot Town - S.S. Ticket from Norman",
|
||||||
|
"SS Tidal - Hidden Item in Lower Deck Trash Can",
|
||||||
|
"SS Tidal - TM49 from Thief",
|
||||||
|
"Safari Zone NE - Hidden Item North",
|
||||||
|
"Safari Zone NE - Hidden Item East",
|
||||||
|
"Safari Zone SE - Hidden Item in South Grass 1",
|
||||||
|
"Safari Zone SE - Hidden Item in South Grass 2",
|
||||||
|
}
|
||||||
|
}
|
606
worlds/pokemon_emerald/options.py
Normal file
606
worlds/pokemon_emerald/options.py
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
"""
|
||||||
|
Option definitions for Pokemon Emerald
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, Type
|
||||||
|
|
||||||
|
from Options import Choice, DefaultOnToggle, Option, OptionSet, Range, Toggle, FreeText, PerGameCommonOptions
|
||||||
|
|
||||||
|
from .data import data
|
||||||
|
|
||||||
|
|
||||||
|
class Goal(Choice):
|
||||||
|
"""
|
||||||
|
Determines what your goal is to consider the game beaten
|
||||||
|
|
||||||
|
Champion: Become the champion and enter the hall of fame
|
||||||
|
Steven: Defeat Steven in Meteor Falls
|
||||||
|
Norman: Defeat Norman in Petalburg Gym
|
||||||
|
"""
|
||||||
|
display_name = "Goal"
|
||||||
|
default = 0
|
||||||
|
option_champion = 0
|
||||||
|
option_steven = 1
|
||||||
|
option_norman = 2
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeBadges(Choice):
|
||||||
|
"""
|
||||||
|
Adds Badges to the pool
|
||||||
|
|
||||||
|
Vanilla: Gym leaders give their own badge
|
||||||
|
Shuffle: Gym leaders give a random badge
|
||||||
|
Completely Random: Badges can be found anywhere
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Badges"
|
||||||
|
default = 2
|
||||||
|
option_vanilla = 0
|
||||||
|
option_shuffle = 1
|
||||||
|
option_completely_random = 2
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeHms(Choice):
|
||||||
|
"""
|
||||||
|
Adds HMs to the pool
|
||||||
|
|
||||||
|
Vanilla: HMs are at their vanilla locations
|
||||||
|
Shuffle: HMs are shuffled among vanilla HM locations
|
||||||
|
Completely Random: HMs can be found anywhere
|
||||||
|
"""
|
||||||
|
display_name = "Randomize HMs"
|
||||||
|
default = 2
|
||||||
|
option_vanilla = 0
|
||||||
|
option_shuffle = 1
|
||||||
|
option_completely_random = 2
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeKeyItems(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
Adds most key items to the pool. These are usually required to unlock
|
||||||
|
a location or region (e.g. Devon Scope, Letter, Basement Key)
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Key Items"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeBikes(Toggle):
|
||||||
|
"""
|
||||||
|
Adds the mach bike and acro bike to the pool
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Bikes"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeRods(Toggle):
|
||||||
|
"""
|
||||||
|
Adds fishing rods to the pool
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Fishing Rods"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeOverworldItems(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
Adds items on the ground with a Pokeball sprite to the pool
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Overworld Items"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeHiddenItems(Toggle):
|
||||||
|
"""
|
||||||
|
Adds hidden items to the pool
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Hidden Items"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeNpcGifts(Toggle):
|
||||||
|
"""
|
||||||
|
Adds most gifts received from NPCs to the pool (not including key items or HMs)
|
||||||
|
"""
|
||||||
|
display_name = "Randomize NPC Gifts"
|
||||||
|
|
||||||
|
|
||||||
|
class ItemPoolType(Choice):
|
||||||
|
"""
|
||||||
|
Determines which non-progression items get put into the item pool
|
||||||
|
|
||||||
|
Shuffled: Item pool consists of shuffled vanilla items
|
||||||
|
Diverse Balanced: Item pool consists of random items approximately proportioned
|
||||||
|
according to what they're replacing (i.e. more pokeballs, fewer X items, etc...)
|
||||||
|
Diverse: Item pool consists of uniformly random (non-unique) items
|
||||||
|
"""
|
||||||
|
display_name = "Item Pool Type"
|
||||||
|
default = 0
|
||||||
|
option_shuffled = 0
|
||||||
|
option_diverse_balanced = 1
|
||||||
|
option_diverse = 2
|
||||||
|
|
||||||
|
|
||||||
|
class HiddenItemsRequireItemfinder(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
The Itemfinder is logically required to pick up hidden items
|
||||||
|
"""
|
||||||
|
display_name = "Require Itemfinder"
|
||||||
|
|
||||||
|
|
||||||
|
class DarkCavesRequireFlash(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
The lower floors of Granite Cave and Victory Road logically require use of HM05 Flash
|
||||||
|
"""
|
||||||
|
display_name = "Require Flash"
|
||||||
|
|
||||||
|
|
||||||
|
class EnableFerry(Toggle):
|
||||||
|
"""
|
||||||
|
The ferry between Slateport, Lilycove, and the Battle Frontier can be used if you have the S.S. Ticket
|
||||||
|
"""
|
||||||
|
display_name = "Enable Ferry"
|
||||||
|
|
||||||
|
|
||||||
|
class EliteFourRequirement(Choice):
|
||||||
|
"""
|
||||||
|
Sets the requirements to challenge the elite four
|
||||||
|
|
||||||
|
Badges: Obtain some number of badges
|
||||||
|
Gyms: Defeat some number of gyms
|
||||||
|
"""
|
||||||
|
display_name = "Elite Four Requirement"
|
||||||
|
default = 0
|
||||||
|
option_badges = 0
|
||||||
|
option_gyms = 1
|
||||||
|
|
||||||
|
|
||||||
|
class EliteFourCount(Range):
|
||||||
|
"""
|
||||||
|
Sets the number of badges/gyms required to challenge the elite four
|
||||||
|
"""
|
||||||
|
display_name = "Elite Four Count"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 8
|
||||||
|
default = 8
|
||||||
|
|
||||||
|
|
||||||
|
class NormanRequirement(Choice):
|
||||||
|
"""
|
||||||
|
Sets the requirements to challenge the Petalburg Gym
|
||||||
|
|
||||||
|
Badges: Obtain some number of badges
|
||||||
|
Gyms: Defeat some number of gyms
|
||||||
|
"""
|
||||||
|
display_name = "Norman Requirement"
|
||||||
|
default = 0
|
||||||
|
option_badges = 0
|
||||||
|
option_gyms = 1
|
||||||
|
|
||||||
|
|
||||||
|
class NormanCount(Range):
|
||||||
|
"""
|
||||||
|
Sets the number of badges/gyms required to challenge the Petalburg Gym
|
||||||
|
"""
|
||||||
|
display_name = "Norman Count"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 7
|
||||||
|
default = 4
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeWildPokemon(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes wild pokemon encounters (grass, caves, water, fishing)
|
||||||
|
|
||||||
|
Vanilla: Wild encounters are unchanged
|
||||||
|
Match Base Stats: Wild pokemon are replaced with species with approximately the same bst
|
||||||
|
Match Type: Wild pokemon are replaced with species that share a type with the original
|
||||||
|
Match Base Stats and Type: Apply both Match Base Stats and Match Type
|
||||||
|
Completely Random: There are no restrictions
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Wild Pokemon"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_match_base_stats = 1
|
||||||
|
option_match_type = 2
|
||||||
|
option_match_base_stats_and_type = 3
|
||||||
|
option_completely_random = 4
|
||||||
|
|
||||||
|
|
||||||
|
class AllowWildLegendaries(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
Wild encounters can be replaced by legendaries. Only applied if Randomize Wild Pokemon is not Vanilla.
|
||||||
|
"""
|
||||||
|
display_name = "Allow Wild Legendaries"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeStarters(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes the starter pokemon in Professor Birch's bag
|
||||||
|
|
||||||
|
Vanilla: Starters are unchanged
|
||||||
|
Match Base Stats: Starters are replaced with species with approximately the same bst
|
||||||
|
Match Type: Starters are replaced with species that share a type with the original
|
||||||
|
Match Base Stats and Type: Apply both Match Base Stats and Match Type
|
||||||
|
Completely Random: There are no restrictions
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Starters"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_match_base_stats = 1
|
||||||
|
option_match_type = 2
|
||||||
|
option_match_base_stats_and_type = 3
|
||||||
|
option_completely_random = 4
|
||||||
|
|
||||||
|
|
||||||
|
class AllowStarterLegendaries(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
Starters can be replaced by legendaries. Only applied if Randomize Starters is not Vanilla.
|
||||||
|
"""
|
||||||
|
display_name = "Allow Starter Legendaries"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeTrainerParties(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes the parties of all trainers.
|
||||||
|
|
||||||
|
Vanilla: Parties are unchanged
|
||||||
|
Match Base Stats: Trainer pokemon are replaced with species with approximately the same bst
|
||||||
|
Match Type: Trainer pokemon are replaced with species that share a type with the original
|
||||||
|
Match Base Stats and Type: Apply both Match Base Stats and Match Type
|
||||||
|
Completely Random: There are no restrictions
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Trainer Parties"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_match_base_stats = 1
|
||||||
|
option_match_type = 2
|
||||||
|
option_match_base_stats_and_type = 3
|
||||||
|
option_completely_random = 4
|
||||||
|
|
||||||
|
|
||||||
|
class AllowTrainerLegendaries(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
Enemy trainer pokemon can be replaced by legendaries. Only applied if Randomize Trainer Parties is not Vanilla.
|
||||||
|
"""
|
||||||
|
display_name = "Allow Trainer Legendaries"
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeStaticEncounters(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes static encounters (Rayquaza, hidden Kekleons, fake Voltorb pokeballs, etc...)
|
||||||
|
|
||||||
|
Vanilla: Static encounters are unchanged
|
||||||
|
Shuffle: Static encounters are shuffled between each other
|
||||||
|
Match Base Stats: Static encounters are replaced with species with approximately the same bst
|
||||||
|
Match Type: Static encounters are replaced with species that share a type with the original
|
||||||
|
Match Base Stats and Type: Apply both Match Base Stats and Match Type
|
||||||
|
Completely Random: There are no restrictions
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Static Encounters"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_shuffle = 1
|
||||||
|
option_match_base_stats = 2
|
||||||
|
option_match_type = 3
|
||||||
|
option_match_base_stats_and_type = 4
|
||||||
|
option_completely_random = 5
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeTypes(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes the type(s) of every pokemon. Each species will have the same number of types.
|
||||||
|
|
||||||
|
Vanilla: Types are unchanged
|
||||||
|
Shuffle: Types are shuffled globally for all species (e.g. every Water-type pokemon becomes Fire-type)
|
||||||
|
Completely Random: Each species has its type(s) randomized
|
||||||
|
Follow Evolutions: Types are randomized per evolution line instead of per species
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Types"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_shuffle = 1
|
||||||
|
option_completely_random = 2
|
||||||
|
option_follow_evolutions = 3
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizeAbilities(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes abilities of every species. Each species will have the same number of abilities.
|
||||||
|
|
||||||
|
Vanilla: Abilities are unchanged
|
||||||
|
Completely Random: Each species has its abilities randomized
|
||||||
|
Follow Evolutions: Abilities are randomized, but if a pokemon would normally retain its ability
|
||||||
|
when evolving, the random ability will also be retained
|
||||||
|
"""
|
||||||
|
display_name = "Randomize Abilities"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_completely_random = 1
|
||||||
|
option_follow_evolutions = 2
|
||||||
|
|
||||||
|
|
||||||
|
class AbilityBlacklist(OptionSet):
|
||||||
|
"""
|
||||||
|
A list of abilities which no pokemon should have if abilities are randomized.
|
||||||
|
For example, you could exclude Wonder Guard and Arena Trap like this:
|
||||||
|
["Wonder Guard", "Arena Trap"]
|
||||||
|
"""
|
||||||
|
display_name = "Ability Blacklist"
|
||||||
|
valid_keys = frozenset([ability.label for ability in data.abilities])
|
||||||
|
|
||||||
|
|
||||||
|
class LevelUpMoves(Choice):
|
||||||
|
"""
|
||||||
|
Randomizes the moves a pokemon learns when they reach a level where they would learn a move.
|
||||||
|
Your starter is guaranteed to have a usable damaging move.
|
||||||
|
|
||||||
|
Vanilla: Learnset is unchanged
|
||||||
|
Randomized: Moves are randomized
|
||||||
|
Start with Four Moves: Moves are randomized and all Pokemon know 4 moves at level 1
|
||||||
|
"""
|
||||||
|
display_name = "Level Up Moves"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_randomized = 1
|
||||||
|
option_start_with_four_moves = 2
|
||||||
|
|
||||||
|
|
||||||
|
class MoveMatchTypeBias(Range):
|
||||||
|
"""
|
||||||
|
Sets the probability that a learned move will be forced match one of the types of a pokemon.
|
||||||
|
|
||||||
|
If a move is not forced to match type, it will roll for Normal type bias.
|
||||||
|
"""
|
||||||
|
display_name = "Move Match Type Bias"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 100
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
|
class MoveNormalTypeBias(Range):
|
||||||
|
"""
|
||||||
|
After it has been decided that a move will not be forced to match types, sets the probability that a learned move
|
||||||
|
will be forced to be the Normal type.
|
||||||
|
|
||||||
|
If a move is not forced to be Normal, it will be completely random.
|
||||||
|
"""
|
||||||
|
display_name = "Move Normal Type Bias"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 100
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
|
class HmCompatibility(Choice):
|
||||||
|
"""
|
||||||
|
Modifies the compatibility of HMs
|
||||||
|
|
||||||
|
Vanilla: Compatibility is unchanged
|
||||||
|
Fully Compatible: Every species can learn any HM
|
||||||
|
Completely Random: Compatibility is 50/50 for every HM (does not remain consistent across evolution)
|
||||||
|
"""
|
||||||
|
display_name = "HM Compatibility"
|
||||||
|
default = 1
|
||||||
|
option_vanilla = 0
|
||||||
|
option_fully_compatible = 1
|
||||||
|
option_completely_random = 2
|
||||||
|
|
||||||
|
|
||||||
|
class TmCompatibility(Choice):
|
||||||
|
"""
|
||||||
|
Modifies the compatibility of TMs
|
||||||
|
|
||||||
|
Vanilla: Compatibility is unchanged
|
||||||
|
Fully Compatible: Every species can learn any TM
|
||||||
|
Completely Random: Compatibility is 50/50 for every TM (does not remain consistent across evolution)
|
||||||
|
"""
|
||||||
|
display_name = "TM Compatibility"
|
||||||
|
default = 0
|
||||||
|
option_vanilla = 0
|
||||||
|
option_fully_compatible = 1
|
||||||
|
option_completely_random = 2
|
||||||
|
|
||||||
|
|
||||||
|
class TmMoves(Toggle):
|
||||||
|
"""
|
||||||
|
Randomizes the moves taught by TMs
|
||||||
|
"""
|
||||||
|
display_name = "TM Moves"
|
||||||
|
|
||||||
|
|
||||||
|
class ReusableTms(Toggle):
|
||||||
|
"""
|
||||||
|
Sets TMs to not break after use (they remain sellable)
|
||||||
|
"""
|
||||||
|
display_name = "Reusable TMs"
|
||||||
|
|
||||||
|
|
||||||
|
class MinCatchRate(Range):
|
||||||
|
"""
|
||||||
|
Sets the minimum catch rate a pokemon can have. Any pokemon with a catch rate below this floor will have it raised to this value.
|
||||||
|
|
||||||
|
Legendaries are often in the single digits
|
||||||
|
Fully evolved pokemon are often double digits
|
||||||
|
Pidgey is 255
|
||||||
|
"""
|
||||||
|
display_name = "Minimum Catch Rate"
|
||||||
|
range_start = 3
|
||||||
|
range_end = 255
|
||||||
|
default = 3
|
||||||
|
|
||||||
|
|
||||||
|
class GuaranteedCatch(Toggle):
|
||||||
|
"""
|
||||||
|
Every throw is guaranteed to catch a wild pokemon
|
||||||
|
"""
|
||||||
|
display_name = "Guaranteed Catch"
|
||||||
|
|
||||||
|
|
||||||
|
class ExpModifier(Range):
|
||||||
|
"""
|
||||||
|
Multiplies gained experience by a percentage
|
||||||
|
|
||||||
|
100 is default
|
||||||
|
50 is half
|
||||||
|
200 is double
|
||||||
|
etc...
|
||||||
|
"""
|
||||||
|
display_name = "Exp Modifier"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 1000
|
||||||
|
default = 100
|
||||||
|
|
||||||
|
|
||||||
|
class BlindTrainers(Toggle):
|
||||||
|
"""
|
||||||
|
Causes trainers to not start a battle with you unless you talk to them
|
||||||
|
"""
|
||||||
|
display_name = "Blind Trainers"
|
||||||
|
|
||||||
|
|
||||||
|
class DoubleBattleChance(Range):
|
||||||
|
"""
|
||||||
|
The percent chance that a trainer with more than 1 pokemon will be converted into a double battle.
|
||||||
|
If these trainers would normally approach you, they will only do so if you have 2 unfainted pokemon.
|
||||||
|
They can be battled by talking to them no matter what.
|
||||||
|
"""
|
||||||
|
display_name = "Double Battle Chance"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 100
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
|
class BetterShops(Toggle):
|
||||||
|
"""
|
||||||
|
Pokemarts sell every item that can be obtained in a pokemart (except mail, which is still unique to the relevant city)
|
||||||
|
"""
|
||||||
|
display_name = "Better Shops"
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveRoadblocks(OptionSet):
|
||||||
|
"""
|
||||||
|
Removes specific NPCs that normally stand in your way until certain events are completed.
|
||||||
|
|
||||||
|
This can open up the world a bit and make your playthrough less linear, but careful how many you remove; it may make too much of your world accessible upon receiving Surf.
|
||||||
|
|
||||||
|
Possible values are:
|
||||||
|
"Route 110 Aqua Grunts"
|
||||||
|
"Route 112 Magma Grunts"
|
||||||
|
"Route 119 Aqua Grunts"
|
||||||
|
"Safari Zone Construction Workers"
|
||||||
|
"Lilycove City Wailmer"
|
||||||
|
"Aqua Hideout Grunts"
|
||||||
|
"Seafloor Cavern Aqua Grunt"
|
||||||
|
"""
|
||||||
|
display_name = "Remove Roadblocks"
|
||||||
|
valid_keys = frozenset([
|
||||||
|
"Route 110 Aqua Grunts",
|
||||||
|
"Route 112 Magma Grunts",
|
||||||
|
"Route 119 Aqua Grunts",
|
||||||
|
"Safari Zone Construction Workers",
|
||||||
|
"Lilycove City Wailmer",
|
||||||
|
"Aqua Hideout Grunts",
|
||||||
|
"Seafloor Cavern Aqua Grunt"
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraBoulders(Toggle):
|
||||||
|
"""
|
||||||
|
Places strength boulders on Route 115 which block access to Meteor Falls from the beach.
|
||||||
|
This aims to take some power away from Surf as a tool for access.
|
||||||
|
"""
|
||||||
|
display_name = "Extra Boulders"
|
||||||
|
|
||||||
|
|
||||||
|
class FreeFlyLocation(Toggle):
|
||||||
|
"""
|
||||||
|
Enables flying to one random location when Mom gives you the running shoes (excluding cities reachable with no items)
|
||||||
|
"""
|
||||||
|
display_name = "Free Fly Location"
|
||||||
|
|
||||||
|
|
||||||
|
class FlyWithoutBadge(DefaultOnToggle):
|
||||||
|
"""
|
||||||
|
Fly does not require the Feather Badge to use in the field
|
||||||
|
"""
|
||||||
|
display_name = "Fly Without Badge"
|
||||||
|
|
||||||
|
|
||||||
|
class TurboA(Toggle):
|
||||||
|
"""
|
||||||
|
Holding A will advance most text automatically
|
||||||
|
"""
|
||||||
|
display_name = "Turbo A"
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveItemMessages(Choice):
|
||||||
|
"""
|
||||||
|
Determines whether you receive an in-game notification when receiving an item. Items can still only be received in the overworld.
|
||||||
|
|
||||||
|
All: Every item shows a message
|
||||||
|
Progression: Only progression items show a message
|
||||||
|
None: All items are added to your bag silently (badges will still show)
|
||||||
|
"""
|
||||||
|
display_name = "Receive Item Messages"
|
||||||
|
default = 0
|
||||||
|
option_all = 0
|
||||||
|
option_progression = 1
|
||||||
|
option_none = 2
|
||||||
|
|
||||||
|
|
||||||
|
class EasterEgg(FreeText):
|
||||||
|
"""
|
||||||
|
???
|
||||||
|
"""
|
||||||
|
default = "Example Passphrase"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PokemonEmeraldOptions(PerGameCommonOptions):
|
||||||
|
goal: Goal
|
||||||
|
|
||||||
|
badges: RandomizeBadges
|
||||||
|
hms: RandomizeHms
|
||||||
|
key_items: RandomizeKeyItems
|
||||||
|
bikes: RandomizeBikes
|
||||||
|
rods: RandomizeRods
|
||||||
|
overworld_items: RandomizeOverworldItems
|
||||||
|
hidden_items: RandomizeHiddenItems
|
||||||
|
npc_gifts: RandomizeNpcGifts
|
||||||
|
item_pool_type: ItemPoolType
|
||||||
|
|
||||||
|
require_itemfinder: HiddenItemsRequireItemfinder
|
||||||
|
require_flash: DarkCavesRequireFlash
|
||||||
|
elite_four_requirement: EliteFourRequirement
|
||||||
|
elite_four_count: EliteFourCount
|
||||||
|
norman_requirement: NormanRequirement
|
||||||
|
norman_count: NormanCount
|
||||||
|
|
||||||
|
wild_pokemon: RandomizeWildPokemon
|
||||||
|
allow_wild_legendaries: AllowWildLegendaries
|
||||||
|
starters: RandomizeStarters
|
||||||
|
allow_starter_legendaries: AllowStarterLegendaries
|
||||||
|
trainer_parties: RandomizeTrainerParties
|
||||||
|
allow_trainer_legendaries: AllowTrainerLegendaries
|
||||||
|
static_encounters: RandomizeStaticEncounters
|
||||||
|
types: RandomizeTypes
|
||||||
|
abilities: RandomizeAbilities
|
||||||
|
ability_blacklist: AbilityBlacklist
|
||||||
|
|
||||||
|
level_up_moves: LevelUpMoves
|
||||||
|
move_match_type_bias: MoveMatchTypeBias
|
||||||
|
move_normal_type_bias: MoveNormalTypeBias
|
||||||
|
tm_compatibility: TmCompatibility
|
||||||
|
hm_compatibility: HmCompatibility
|
||||||
|
tm_moves: TmMoves
|
||||||
|
reusable_tms: ReusableTms
|
||||||
|
|
||||||
|
min_catch_rate: MinCatchRate
|
||||||
|
guaranteed_catch: GuaranteedCatch
|
||||||
|
exp_modifier: ExpModifier
|
||||||
|
blind_trainers: BlindTrainers
|
||||||
|
double_battle_chance: DoubleBattleChance
|
||||||
|
better_shops: BetterShops
|
||||||
|
|
||||||
|
enable_ferry: EnableFerry
|
||||||
|
remove_roadblocks: RemoveRoadblocks
|
||||||
|
extra_boulders: ExtraBoulders
|
||||||
|
free_fly_location: FreeFlyLocation
|
||||||
|
fly_without_badge: FlyWithoutBadge
|
||||||
|
|
||||||
|
turbo_a: TurboA
|
||||||
|
receive_item_messages: ReceiveItemMessages
|
||||||
|
|
||||||
|
easter_egg: EasterEgg
|
196
worlds/pokemon_emerald/pokemon.py
Normal file
196
worlds/pokemon_emerald/pokemon.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
"""
|
||||||
|
Functions related to pokemon species and moves
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Tuple
|
||||||
|
|
||||||
|
from .data import SpeciesData, data
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from random import Random
|
||||||
|
|
||||||
|
|
||||||
|
_damaging_moves = frozenset({
|
||||||
|
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,
|
||||||
|
340, 341, 342, 343, 344, 345, 348, 350, 351, 352, 353, 354
|
||||||
|
})
|
||||||
|
|
||||||
|
_move_types = [
|
||||||
|
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,
|
||||||
|
12, 11, 14, 12, 16, 5, 13, 11, 8, 14
|
||||||
|
]
|
||||||
|
|
||||||
|
_moves_by_type: Dict[int, List[int]] = {}
|
||||||
|
for move, type in enumerate(_move_types):
|
||||||
|
_moves_by_type.setdefault(type, []).append(move)
|
||||||
|
|
||||||
|
_move_blacklist = frozenset({
|
||||||
|
0, # MOVE_NONE
|
||||||
|
165, # Struggle
|
||||||
|
15, # Cut
|
||||||
|
148, # Flash
|
||||||
|
249, # Rock Smash
|
||||||
|
70, # Strength
|
||||||
|
57, # Surf
|
||||||
|
19, # Fly
|
||||||
|
291, # Dive
|
||||||
|
127 # Waterfall
|
||||||
|
})
|
||||||
|
|
||||||
|
_legendary_pokemon = frozenset({
|
||||||
|
'Mew',
|
||||||
|
'Mewtwo',
|
||||||
|
'Articuno',
|
||||||
|
'Zapdos',
|
||||||
|
'Moltres',
|
||||||
|
'Lugia',
|
||||||
|
'Ho-oh',
|
||||||
|
'Raikou',
|
||||||
|
'Suicune',
|
||||||
|
'Entei',
|
||||||
|
'Celebi',
|
||||||
|
'Groudon',
|
||||||
|
'Kyogre',
|
||||||
|
'Rayquaza',
|
||||||
|
'Latios',
|
||||||
|
'Latias',
|
||||||
|
'Registeel',
|
||||||
|
'Regirock',
|
||||||
|
'Regice',
|
||||||
|
'Jirachi',
|
||||||
|
'Deoxys'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_species(
|
||||||
|
random: "Random",
|
||||||
|
candidates: List[Optional[SpeciesData]],
|
||||||
|
nearby_bst: Optional[int] = None,
|
||||||
|
species_type: Optional[int] = None,
|
||||||
|
allow_legendaries: bool = True) -> SpeciesData:
|
||||||
|
candidates: List[SpeciesData] = [species for species in candidates if species is not None]
|
||||||
|
|
||||||
|
if species_type is not None:
|
||||||
|
candidates = [species for species in candidates if species_type in species.types]
|
||||||
|
|
||||||
|
if not allow_legendaries:
|
||||||
|
candidates = [species for species in candidates if species.label not in _legendary_pokemon]
|
||||||
|
|
||||||
|
if nearby_bst is not None:
|
||||||
|
def has_nearby_bst(species: SpeciesData, max_percent_different: int) -> bool:
|
||||||
|
return abs(sum(species.base_stats) - nearby_bst) < nearby_bst * (max_percent_different / 100)
|
||||||
|
|
||||||
|
max_percent_different = 10
|
||||||
|
bst_filtered_candidates = [species for species in candidates if has_nearby_bst(species, max_percent_different)]
|
||||||
|
while len(bst_filtered_candidates) == 0:
|
||||||
|
max_percent_different += 10
|
||||||
|
bst_filtered_candidates = [
|
||||||
|
species
|
||||||
|
for species in candidates
|
||||||
|
if has_nearby_bst(species, max_percent_different)
|
||||||
|
]
|
||||||
|
|
||||||
|
candidates = bst_filtered_candidates
|
||||||
|
|
||||||
|
return random.choice(candidates)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
expanded_blacklist = _move_blacklist | (blacklist if blacklist is not None else set())
|
||||||
|
|
||||||
|
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:
|
||||||
|
possible_moves = [i for i in range(data.constants["MOVE_COUNT"]) if i not in expanded_blacklist]
|
||||||
|
else:
|
||||||
|
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]
|
||||||
|
|
||||||
|
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:
|
||||||
|
expanded_blacklist = _move_blacklist | (blacklist if blacklist is not None else set())
|
||||||
|
|
||||||
|
move_options = list(_damaging_moves)
|
||||||
|
|
||||||
|
move = random.choice(move_options)
|
||||||
|
while move in expanded_blacklist:
|
||||||
|
move = random.choice(move_options)
|
||||||
|
|
||||||
|
return move
|
49
worlds/pokemon_emerald/regions.py
Normal file
49
worlds/pokemon_emerald/regions.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
Functions related to AP regions for Pokemon Emerald (see ./data/regions for region definitions)
|
||||||
|
"""
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Tuple
|
||||||
|
|
||||||
|
from BaseClasses import 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.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
420
worlds/pokemon_emerald/rom.py
Normal file
420
worlds/pokemon_emerald/rom.py
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
"""
|
||||||
|
Classes and functions related to creating a ROM patch
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import pkgutil
|
||||||
|
from typing import TYPE_CHECKING, List, Tuple
|
||||||
|
|
||||||
|
import bsdiff4
|
||||||
|
|
||||||
|
from worlds.Files import APDeltaPatch
|
||||||
|
from settings import get_settings
|
||||||
|
|
||||||
|
from .data import PokemonEmeraldData, TrainerPokemonDataTypeEnum, data
|
||||||
|
from .items import reverse_offset_item_value
|
||||||
|
from .options import RandomizeWildPokemon, RandomizeTrainerParties, EliteFourRequirement, NormanRequirement
|
||||||
|
from .pokemon import get_random_species
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from . import PokemonEmeraldWorld
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldDeltaPatch(APDeltaPatch):
|
||||||
|
game = "Pokemon Emerald"
|
||||||
|
hash = "605b89b67018abcea91e693a4dd25be3"
|
||||||
|
patch_file_ending = ".apemerald"
|
||||||
|
result_file_ending = ".gba"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_source_data(cls) -> bytes:
|
||||||
|
return get_base_rom_as_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
location_visited_event_to_id_map = {
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_output(world: "PokemonEmeraldWorld", output_directory: str) -> None:
|
||||||
|
base_rom = get_base_rom_as_bytes()
|
||||||
|
base_patch = pkgutil.get_data(__name__, "data/base_patch.bsdiff4")
|
||||||
|
patched_rom = bytearray(bsdiff4.patch(base_rom, base_patch))
|
||||||
|
|
||||||
|
# Set item values
|
||||||
|
for location in world.multiworld.get_locations(world.player):
|
||||||
|
# Set free fly location
|
||||||
|
if location.address is None:
|
||||||
|
if world.options.free_fly_location and location.name == "EVENT_VISITED_LITTLEROOT_TOWN":
|
||||||
|
_set_bytes_little_endian(
|
||||||
|
patched_rom,
|
||||||
|
data.rom_addresses["gArchipelagoOptions"] + 0x16,
|
||||||
|
1,
|
||||||
|
world.free_fly_location_id
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if location.item and location.item.player == world.player:
|
||||||
|
_set_bytes_little_endian(
|
||||||
|
patched_rom,
|
||||||
|
location.rom_address,
|
||||||
|
2,
|
||||||
|
reverse_offset_item_value(location.item.code)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_set_bytes_little_endian(
|
||||||
|
patched_rom,
|
||||||
|
location.rom_address,
|
||||||
|
2,
|
||||||
|
data.constants["ITEM_ARCHIPELAGO_PROGRESSION"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set start inventory
|
||||||
|
start_inventory = world.options.start_inventory.value.copy()
|
||||||
|
|
||||||
|
starting_badges = 0
|
||||||
|
if start_inventory.pop("Stone Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 0)
|
||||||
|
if start_inventory.pop("Knuckle Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 1)
|
||||||
|
if start_inventory.pop("Dynamo Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 2)
|
||||||
|
if start_inventory.pop("Heat Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 3)
|
||||||
|
if start_inventory.pop("Balance Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 4)
|
||||||
|
if start_inventory.pop("Feather Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 5)
|
||||||
|
if start_inventory.pop("Mind Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 6)
|
||||||
|
if start_inventory.pop("Rain Badge", 0) > 0:
|
||||||
|
starting_badges |= (1 << 7)
|
||||||
|
|
||||||
|
pc_slots: List[Tuple[str, int]] = []
|
||||||
|
while any(qty > 0 for qty in start_inventory.values()):
|
||||||
|
if len(pc_slots) >= 19:
|
||||||
|
break
|
||||||
|
|
||||||
|
for i, item_name in enumerate(start_inventory.keys()):
|
||||||
|
if len(pc_slots) >= 19:
|
||||||
|
break
|
||||||
|
|
||||||
|
quantity = min(start_inventory[item_name], 999)
|
||||||
|
if quantity == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
start_inventory[item_name] -= quantity
|
||||||
|
|
||||||
|
pc_slots.append((item_name, quantity))
|
||||||
|
|
||||||
|
pc_slots.sort(reverse=True)
|
||||||
|
|
||||||
|
for i, slot in enumerate(pc_slots):
|
||||||
|
address = data.rom_addresses["sNewGamePCItems"] + (i * 4)
|
||||||
|
item = reverse_offset_item_value(world.item_name_to_id[slot[0]])
|
||||||
|
_set_bytes_little_endian(patched_rom, address + 0, 2, item)
|
||||||
|
_set_bytes_little_endian(patched_rom, address + 2, 2, slot[1])
|
||||||
|
|
||||||
|
# Set species data
|
||||||
|
_set_species_info(world, patched_rom)
|
||||||
|
|
||||||
|
# Set encounter tables
|
||||||
|
if world.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
|
||||||
|
_set_encounter_tables(world, patched_rom)
|
||||||
|
|
||||||
|
# Set opponent data
|
||||||
|
if world.options.trainer_parties != RandomizeTrainerParties.option_vanilla:
|
||||||
|
_set_opponents(world, patched_rom)
|
||||||
|
|
||||||
|
# Set static pokemon
|
||||||
|
_set_static_encounters(world, patched_rom)
|
||||||
|
|
||||||
|
# Set starters
|
||||||
|
_set_starters(world, patched_rom)
|
||||||
|
|
||||||
|
# Set TM moves
|
||||||
|
_set_tm_moves(world, patched_rom)
|
||||||
|
|
||||||
|
# Set TM/HM compatibility
|
||||||
|
_set_tmhm_compatibility(world, patched_rom)
|
||||||
|
|
||||||
|
# Randomize opponent double or single
|
||||||
|
_randomize_opponent_battle_type(world, patched_rom)
|
||||||
|
|
||||||
|
# Options
|
||||||
|
# struct ArchipelagoOptions
|
||||||
|
# {
|
||||||
|
# /* 0x00 */ bool8 advanceTextWithHoldA;
|
||||||
|
# /* 0x01 */ bool8 isFerryEnabled;
|
||||||
|
# /* 0x02 */ bool8 areTrainersBlind;
|
||||||
|
# /* 0x03 */ bool8 canFlyWithoutBadge;
|
||||||
|
# /* 0x04 */ u16 expMultiplierNumerator;
|
||||||
|
# /* 0x06 */ u16 expMultiplierDenominator;
|
||||||
|
# /* 0x08 */ u16 birchPokemon;
|
||||||
|
# /* 0x0A */ bool8 guaranteedCatch;
|
||||||
|
# /* 0x0B */ bool8 betterShopsEnabled;
|
||||||
|
# /* 0x0C */ bool8 eliteFourRequiresGyms;
|
||||||
|
# /* 0x0D */ u8 eliteFourRequiredCount;
|
||||||
|
# /* 0x0E */ bool8 normanRequiresGyms;
|
||||||
|
# /* 0x0F */ u8 normanRequiredCount;
|
||||||
|
# /* 0x10 */ u8 startingBadges;
|
||||||
|
# /* 0x11 */ u8 receivedItemMessageFilter; // 0 = Show All; 1 = Show Progression Only; 2 = Show None
|
||||||
|
# /* 0x12 */ bool8 reusableTms;
|
||||||
|
# /* 0x14 */ u16 removedBlockers;
|
||||||
|
# /* 0x13 */ bool8 addRoute115Boulders;
|
||||||
|
# /* 0x14 */ u16 removedBlockers;
|
||||||
|
# /* 0x14 */ u16 removedBlockers;
|
||||||
|
# /* 0x16 */ u8 freeFlyLocation;
|
||||||
|
# };
|
||||||
|
options_address = data.rom_addresses["gArchipelagoOptions"]
|
||||||
|
|
||||||
|
# Set hold A to advance text
|
||||||
|
turbo_a = 1 if world.options.turbo_a else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x00, 1, turbo_a)
|
||||||
|
|
||||||
|
# Set ferry enabled
|
||||||
|
enable_ferry = 1 if world.options.enable_ferry else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x01, 1, enable_ferry)
|
||||||
|
|
||||||
|
# Set blind trainers
|
||||||
|
blind_trainers = 1 if world.options.blind_trainers else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x02, 1, blind_trainers)
|
||||||
|
|
||||||
|
# Set fly without badge
|
||||||
|
fly_without_badge = 1 if world.options.fly_without_badge else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x03, 1, fly_without_badge)
|
||||||
|
|
||||||
|
# Set exp modifier
|
||||||
|
numerator = min(max(world.options.exp_modifier.value, 0), 2**16 - 1)
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x04, 2, numerator)
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x06, 2, 100)
|
||||||
|
|
||||||
|
# Set Birch pokemon
|
||||||
|
_set_bytes_little_endian(
|
||||||
|
patched_rom,
|
||||||
|
options_address + 0x08,
|
||||||
|
2,
|
||||||
|
get_random_species(world.random, data.species).species_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set guaranteed catch
|
||||||
|
guaranteed_catch = 1 if world.options.guaranteed_catch else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x0A, 1, guaranteed_catch)
|
||||||
|
|
||||||
|
# Set better shops
|
||||||
|
better_shops = 1 if world.options.better_shops else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x0B, 1, better_shops)
|
||||||
|
|
||||||
|
# Set elite four requirement
|
||||||
|
elite_four_requires_gyms = 1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x0C, 1, elite_four_requires_gyms)
|
||||||
|
|
||||||
|
# Set elite four count
|
||||||
|
elite_four_count = min(max(world.options.elite_four_count.value, 0), 8)
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x0D, 1, elite_four_count)
|
||||||
|
|
||||||
|
# Set norman requirement
|
||||||
|
norman_requires_gyms = 1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x0E, 1, norman_requires_gyms)
|
||||||
|
|
||||||
|
# Set norman count
|
||||||
|
norman_count = min(max(world.options.norman_count.value, 0), 8)
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x0F, 1, norman_count)
|
||||||
|
|
||||||
|
# Set starting badges
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x10, 1, starting_badges)
|
||||||
|
|
||||||
|
# Set receive item messages type
|
||||||
|
receive_item_messages_type = world.options.receive_item_messages.value
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x11, 1, receive_item_messages_type)
|
||||||
|
|
||||||
|
# Set reusable TMs
|
||||||
|
reusable_tms = 1 if world.options.reusable_tms else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x12, 1, reusable_tms)
|
||||||
|
|
||||||
|
# Set route 115 boulders
|
||||||
|
route_115_boulders = 1 if world.options.extra_boulders else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x13, 1, route_115_boulders)
|
||||||
|
|
||||||
|
# Set removed blockers
|
||||||
|
removed_roadblocks = world.options.remove_roadblocks.value
|
||||||
|
removed_roadblocks_bitfield = 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 0) if "Safari Zone Construction Workers" in removed_roadblocks else 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 1) if "Lilycove City Wailmer" in removed_roadblocks else 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 2) if "Route 110 Aqua Grunts" in removed_roadblocks else 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 3) if "Aqua Hideout Grunts" in removed_roadblocks else 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 4) if "Route 119 Aqua Grunts" in removed_roadblocks else 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 5) if "Route 112 Magma Grunts" in removed_roadblocks else 0
|
||||||
|
removed_roadblocks_bitfield |= (1 << 6) if "Seafloor Cavern Aqua Grunt" in removed_roadblocks else 0
|
||||||
|
_set_bytes_little_endian(patched_rom, options_address + 0x14, 2, removed_roadblocks_bitfield)
|
||||||
|
|
||||||
|
# Set slot name
|
||||||
|
player_name = world.multiworld.get_player_name(world.player)
|
||||||
|
for i, byte in enumerate(player_name.encode("utf-8")):
|
||||||
|
_set_bytes_little_endian(patched_rom, data.rom_addresses["gArchipelagoInfo"] + i, 1, byte)
|
||||||
|
|
||||||
|
# Write Output
|
||||||
|
out_file_name = world.multiworld.get_out_file_name_base(world.player)
|
||||||
|
output_path = os.path.join(output_directory, f"{out_file_name}.gba")
|
||||||
|
with open(output_path, "wb") as out_file:
|
||||||
|
out_file.write(patched_rom)
|
||||||
|
patch = PokemonEmeraldDeltaPatch(os.path.splitext(output_path)[0] + ".apemerald", player=world.player,
|
||||||
|
player_name=player_name, patched_path=output_path)
|
||||||
|
|
||||||
|
patch.write()
|
||||||
|
os.unlink(output_path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_rom_as_bytes() -> bytes:
|
||||||
|
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
|
||||||
|
base_rom_bytes = bytes(infile.read())
|
||||||
|
|
||||||
|
return base_rom_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def _set_bytes_little_endian(byte_array: bytearray, address: int, size: int, value: int) -> None:
|
||||||
|
offset = 0
|
||||||
|
while size > 0:
|
||||||
|
byte_array[address + offset] = value & 0xFF
|
||||||
|
value = value >> 8
|
||||||
|
offset += 1
|
||||||
|
size -= 1
|
||||||
|
|
||||||
|
|
||||||
|
def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
"""
|
||||||
|
Encounter tables are lists of
|
||||||
|
struct {
|
||||||
|
min_level: 0x01 bytes,
|
||||||
|
max_level: 0x01 bytes,
|
||||||
|
species_id: 0x02 bytes
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
for map_data in world.modified_maps:
|
||||||
|
tables = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters]
|
||||||
|
for table in tables:
|
||||||
|
if table is not None:
|
||||||
|
for i, species_id in enumerate(table.slots):
|
||||||
|
address = table.rom_address + 2 + (4 * i)
|
||||||
|
_set_bytes_little_endian(rom, address, 2, species_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_species_info(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
for species in world.modified_species:
|
||||||
|
if species is not None:
|
||||||
|
_set_bytes_little_endian(rom, species.rom_address + 6, 1, species.types[0])
|
||||||
|
_set_bytes_little_endian(rom, species.rom_address + 7, 1, species.types[1])
|
||||||
|
_set_bytes_little_endian(rom, species.rom_address + 8, 1, species.catch_rate)
|
||||||
|
_set_bytes_little_endian(rom, species.rom_address + 22, 1, species.abilities[0])
|
||||||
|
_set_bytes_little_endian(rom, species.rom_address + 23, 1, species.abilities[1])
|
||||||
|
|
||||||
|
for i, learnset_move in enumerate(species.learnset):
|
||||||
|
level_move = learnset_move.level << 9 | learnset_move.move_id
|
||||||
|
_set_bytes_little_endian(rom, species.learnset_rom_address + (i * 2), 2, level_move)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
for trainer in world.modified_trainers:
|
||||||
|
party_address = trainer.party.rom_address
|
||||||
|
|
||||||
|
pokemon_data_size: int
|
||||||
|
if trainer.party.pokemon_data_type in {TrainerPokemonDataTypeEnum.NO_ITEM_DEFAULT_MOVES, TrainerPokemonDataTypeEnum.ITEM_DEFAULT_MOVES}:
|
||||||
|
pokemon_data_size = 8
|
||||||
|
else: # Custom Moves
|
||||||
|
pokemon_data_size = 16
|
||||||
|
|
||||||
|
for i, pokemon in enumerate(trainer.party.pokemon):
|
||||||
|
pokemon_address = party_address + (i * pokemon_data_size)
|
||||||
|
|
||||||
|
# Replace species
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x04, 2, pokemon.species_id)
|
||||||
|
|
||||||
|
# Replace custom moves if applicable
|
||||||
|
if trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES:
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x06, 2, pokemon.moves[0])
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x08, 2, pokemon.moves[1])
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x0A, 2, pokemon.moves[2])
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x0C, 2, pokemon.moves[3])
|
||||||
|
elif trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES:
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x08, 2, pokemon.moves[0])
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x0A, 2, pokemon.moves[1])
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x0C, 2, pokemon.moves[2])
|
||||||
|
_set_bytes_little_endian(rom, pokemon_address + 0x0E, 2, pokemon.moves[3])
|
||||||
|
|
||||||
|
|
||||||
|
def _set_static_encounters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
for encounter in world.modified_static_encounters:
|
||||||
|
_set_bytes_little_endian(rom, encounter.rom_address, 2, encounter.species_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_starters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
address = data.rom_addresses["sStarterMon"]
|
||||||
|
(starter_1, starter_2, starter_3) = world.modified_starters
|
||||||
|
|
||||||
|
_set_bytes_little_endian(rom, address + 0, 2, starter_1)
|
||||||
|
_set_bytes_little_endian(rom, address + 2, 2, starter_2)
|
||||||
|
_set_bytes_little_endian(rom, address + 4, 2, starter_3)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
tmhm_list_address = data.rom_addresses["sTMHMMoves"]
|
||||||
|
|
||||||
|
for i, move in enumerate(world.modified_tmhm_moves):
|
||||||
|
# Don't modify HMs
|
||||||
|
if i >= 50:
|
||||||
|
break
|
||||||
|
|
||||||
|
_set_bytes_little_endian(rom, tmhm_list_address + (i * 2), 2, move)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
learnsets_address = data.rom_addresses["gTMHMLearnsets"]
|
||||||
|
|
||||||
|
for species in world.modified_species:
|
||||||
|
if species is not None:
|
||||||
|
_set_bytes_little_endian(rom, learnsets_address + (species.species_id * 8), 8, species.tm_hm_compatibility)
|
||||||
|
|
||||||
|
|
||||||
|
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
|
||||||
|
probability = world.options.double_battle_chance.value / 100
|
||||||
|
|
||||||
|
battle_type_map = {
|
||||||
|
0: 4,
|
||||||
|
1: 8,
|
||||||
|
2: 6,
|
||||||
|
3: 13,
|
||||||
|
}
|
||||||
|
|
||||||
|
for trainer_data in data.trainers:
|
||||||
|
if trainer_data.battle_script_rom_address != 0 and len(trainer_data.party.pokemon) > 1:
|
||||||
|
if world.random.random() < probability:
|
||||||
|
# Set the trainer to be a double battle
|
||||||
|
_set_bytes_little_endian(rom, trainer_data.rom_address + 0x18, 1, 1)
|
||||||
|
|
||||||
|
# Swap the battle type in the script for the purpose of loading the right text
|
||||||
|
# and setting data to the right places
|
||||||
|
original_battle_type = rom[trainer_data.battle_script_rom_address + 1]
|
||||||
|
if original_battle_type in battle_type_map:
|
||||||
|
_set_bytes_little_endian(
|
||||||
|
rom,
|
||||||
|
trainer_data.battle_script_rom_address + 1,
|
||||||
|
1,
|
||||||
|
battle_type_map[original_battle_type]
|
||||||
|
)
|
1368
worlds/pokemon_emerald/rules.py
Normal file
1368
worlds/pokemon_emerald/rules.py
Normal file
File diff suppressed because it is too large
Load Diff
352
worlds/pokemon_emerald/sanity_check.py
Normal file
352
worlds/pokemon_emerald/sanity_check.py
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
"""
|
||||||
|
Looks through data object to double-check it makes sense. Will fail for missing or duplicate definitions or
|
||||||
|
duplicate claims and give warnings for unused and unignored locations or warps.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .data import data
|
||||||
|
|
||||||
|
|
||||||
|
_ignorable_locations = {
|
||||||
|
# Trick House
|
||||||
|
"HIDDEN_ITEM_TRICK_HOUSE_NUGGET",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_1_ORANGE_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_2_HARBOR_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_2_WAVE_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_3_SHADOW_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_3_WOOD_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_4_MECH_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_6_GLITTER_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_7_TROPIC_MAIL",
|
||||||
|
"ITEM_TRICK_HOUSE_PUZZLE_8_BEAD_MAIL",
|
||||||
|
|
||||||
|
# Battle Frontier
|
||||||
|
"ITEM_ARTISAN_CAVE_1F_CARBOS",
|
||||||
|
"ITEM_ARTISAN_CAVE_B1F_HP_UP",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_CALCIUM",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_IRON",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_PROTEIN",
|
||||||
|
"HIDDEN_ITEM_ARTISAN_CAVE_B1F_ZINC",
|
||||||
|
|
||||||
|
# Event islands
|
||||||
|
"HIDDEN_ITEM_NAVEL_ROCK_TOP_SACRED_ASH"
|
||||||
|
}
|
||||||
|
|
||||||
|
_ignorable_warps = {
|
||||||
|
# Trick House
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE2:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE2:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE3:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE3:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE4:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE4:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE5:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE5:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE6:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE6:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:10/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:9",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:11/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:12",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:12/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:11",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:3/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:4",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:4/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:3",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:5/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:6",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:6/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:5",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:7/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:8",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:8/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:7",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:9/MAP_ROUTE110_TRICK_HOUSE_PUZZLE7:10",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE8:0,1/MAP_ROUTE110_TRICK_HOUSE_ENTRANCE:2!",
|
||||||
|
"MAP_ROUTE110_TRICK_HOUSE_PUZZLE8:2/MAP_ROUTE110_TRICK_HOUSE_END:0!",
|
||||||
|
|
||||||
|
# Department store elevator
|
||||||
|
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0,1/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_1F:3/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_2F:2/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_3F:2/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_4F:2/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_DEPARTMENT_STORE_5F:1/MAP_LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR:0!",
|
||||||
|
|
||||||
|
# Intro truck
|
||||||
|
"MAP_INSIDE_OF_TRUCK:0,1,2/MAP_DYNAMIC:-1!",
|
||||||
|
|
||||||
|
# Battle Frontier
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:1",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_DOME_CORRIDOR:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1!",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_DOME_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_DOME_PRE_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1!",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:0,1/MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:3/MAP_BATTLE_FRONTIER_BATTLE_PALACE_BATTLE_ROOM:0!",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_CORRIDOR:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY:0,1,2/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:3",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM:0,1/MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:2/MAP_BATTLE_FRONTIER_BATTLE_TOWER_BATTLE_ROOM:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER:0,1,2/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:6",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE1:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:5",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE2:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:3",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE3:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:9",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE4:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:6",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE5:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:7",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE6:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:8",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE7:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:7",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE8:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:10",
|
||||||
|
"MAP_BATTLE_FRONTIER_LOUNGE9:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:11",
|
||||||
|
"MAP_BATTLE_FRONTIER_MART:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:4",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:0/MAP_BATTLE_FRONTIER_BATTLE_TOWER_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:1/MAP_BATTLE_FRONTIER_BATTLE_ARENA_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:10/MAP_BATTLE_FRONTIER_LOUNGE8:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:11/MAP_BATTLE_FRONTIER_LOUNGE9:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:12/MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:13/MAP_ARTISAN_CAVE_1F:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:2/MAP_BATTLE_FRONTIER_BATTLE_PALACE_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:3/MAP_BATTLE_FRONTIER_BATTLE_PYRAMID_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:4/MAP_BATTLE_FRONTIER_RANKING_HALL:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:5/MAP_BATTLE_FRONTIER_LOUNGE1:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:6/MAP_BATTLE_FRONTIER_EXCHANGE_SERVICE_CORNER:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:7/MAP_BATTLE_FRONTIER_LOUNGE5:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:8/MAP_BATTLE_FRONTIER_LOUNGE6:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_EAST:9/MAP_BATTLE_FRONTIER_LOUNGE3:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:0/MAP_BATTLE_FRONTIER_BATTLE_PIKE_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:1/MAP_BATTLE_FRONTIER_BATTLE_DOME_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:10/MAP_ARTISAN_CAVE_B1F:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:2/MAP_BATTLE_FRONTIER_BATTLE_FACTORY_LOBBY:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:3/MAP_BATTLE_FRONTIER_LOUNGE2:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:4/MAP_BATTLE_FRONTIER_MART:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:5/MAP_BATTLE_FRONTIER_SCOTTS_HOUSE:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:6/MAP_BATTLE_FRONTIER_LOUNGE4:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:7/MAP_BATTLE_FRONTIER_LOUNGE7:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:8/MAP_BATTLE_FRONTIER_RECEPTION_GATE:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_OUTSIDE_WEST:9/MAP_BATTLE_FRONTIER_RECEPTION_GATE:1",
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:12",
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:2/MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:0",
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:0/MAP_BATTLE_FRONTIER_POKEMON_CENTER_1F:2",
|
||||||
|
"MAP_BATTLE_FRONTIER_RANKING_HALL:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:4",
|
||||||
|
"MAP_BATTLE_FRONTIER_RECEPTION_GATE:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:8",
|
||||||
|
"MAP_BATTLE_FRONTIER_RECEPTION_GATE:1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:9",
|
||||||
|
"MAP_BATTLE_FRONTIER_SCOTTS_HOUSE:0,1/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:5",
|
||||||
|
|
||||||
|
"MAP_ARTISAN_CAVE_1F:0/MAP_BATTLE_FRONTIER_OUTSIDE_EAST:13",
|
||||||
|
"MAP_ARTISAN_CAVE_1F:1/MAP_ARTISAN_CAVE_B1F:1",
|
||||||
|
"MAP_ARTISAN_CAVE_B1F:0/MAP_BATTLE_FRONTIER_OUTSIDE_WEST:10",
|
||||||
|
"MAP_ARTISAN_CAVE_B1F:1/MAP_ARTISAN_CAVE_1F:1",
|
||||||
|
|
||||||
|
# Terra Cave and Marine Cave
|
||||||
|
"MAP_TERRA_CAVE_ENTRANCE:0/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_TERRA_CAVE_END:0/MAP_TERRA_CAVE_ENTRANCE:1",
|
||||||
|
"MAP_TERRA_CAVE_ENTRANCE:1/MAP_TERRA_CAVE_END:0",
|
||||||
|
"MAP_ROUTE113:1/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE113:2/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE114:3/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE114:4/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE115:1/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE115:2/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE116:3/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE116:4/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE118:0/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
"MAP_ROUTE118:1/MAP_TERRA_CAVE_ENTRANCE:0!",
|
||||||
|
|
||||||
|
"MAP_UNDERWATER_MARINE_CAVE:0/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_MARINE_CAVE_END:0/MAP_MARINE_CAVE_ENTRANCE:0",
|
||||||
|
"MAP_MARINE_CAVE_ENTRANCE:0/MAP_MARINE_CAVE_END:0",
|
||||||
|
"MAP_UNDERWATER_ROUTE105:0/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE105:1/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE125:0/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE125:1/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE127:0/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE127:1/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE129:0/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
"MAP_UNDERWATER_ROUTE129:1/MAP_UNDERWATER_MARINE_CAVE:0!",
|
||||||
|
|
||||||
|
# Event islands
|
||||||
|
"MAP_BIRTH_ISLAND_EXTERIOR:0/MAP_BIRTH_ISLAND_HARBOR:0",
|
||||||
|
"MAP_BIRTH_ISLAND_HARBOR:0/MAP_BIRTH_ISLAND_EXTERIOR:0",
|
||||||
|
|
||||||
|
"MAP_FARAWAY_ISLAND_ENTRANCE:0,1/MAP_FARAWAY_ISLAND_INTERIOR:0,1",
|
||||||
|
"MAP_FARAWAY_ISLAND_INTERIOR:0,1/MAP_FARAWAY_ISLAND_ENTRANCE:0,1",
|
||||||
|
|
||||||
|
"MAP_SOUTHERN_ISLAND_EXTERIOR:0,1/MAP_SOUTHERN_ISLAND_INTERIOR:0,1",
|
||||||
|
"MAP_SOUTHERN_ISLAND_INTERIOR:0,1/MAP_SOUTHERN_ISLAND_EXTERIOR:0,1",
|
||||||
|
|
||||||
|
"MAP_NAVEL_ROCK_B1F:0/MAP_NAVEL_ROCK_ENTRANCE:0",
|
||||||
|
"MAP_NAVEL_ROCK_B1F:1/MAP_NAVEL_ROCK_FORK:1",
|
||||||
|
"MAP_NAVEL_ROCK_BOTTOM:0/MAP_NAVEL_ROCK_DOWN11:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN01:0/MAP_NAVEL_ROCK_FORK:2",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN01:1/MAP_NAVEL_ROCK_DOWN02:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN02:0/MAP_NAVEL_ROCK_DOWN01:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN02:1/MAP_NAVEL_ROCK_DOWN03:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN03:0/MAP_NAVEL_ROCK_DOWN02:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN03:1/MAP_NAVEL_ROCK_DOWN04:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN04:0/MAP_NAVEL_ROCK_DOWN03:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN04:1/MAP_NAVEL_ROCK_DOWN05:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN05:0/MAP_NAVEL_ROCK_DOWN04:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN05:1/MAP_NAVEL_ROCK_DOWN06:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN06:0/MAP_NAVEL_ROCK_DOWN05:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN06:1/MAP_NAVEL_ROCK_DOWN07:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN07:0/MAP_NAVEL_ROCK_DOWN06:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN07:1/MAP_NAVEL_ROCK_DOWN08:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN08:0/MAP_NAVEL_ROCK_DOWN07:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN08:1/MAP_NAVEL_ROCK_DOWN09:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN09:0/MAP_NAVEL_ROCK_DOWN08:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN09:1/MAP_NAVEL_ROCK_DOWN10:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN10:0/MAP_NAVEL_ROCK_DOWN09:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN10:1/MAP_NAVEL_ROCK_DOWN11:1",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN11:0/MAP_NAVEL_ROCK_BOTTOM:0",
|
||||||
|
"MAP_NAVEL_ROCK_DOWN11:1/MAP_NAVEL_ROCK_DOWN10:1",
|
||||||
|
"MAP_NAVEL_ROCK_ENTRANCE:0/MAP_NAVEL_ROCK_B1F:0",
|
||||||
|
"MAP_NAVEL_ROCK_ENTRANCE:1/MAP_NAVEL_ROCK_EXTERIOR:1",
|
||||||
|
"MAP_NAVEL_ROCK_EXTERIOR:0/MAP_NAVEL_ROCK_HARBOR:0",
|
||||||
|
"MAP_NAVEL_ROCK_EXTERIOR:1/MAP_NAVEL_ROCK_ENTRANCE:1",
|
||||||
|
"MAP_NAVEL_ROCK_FORK:0/MAP_NAVEL_ROCK_UP1:0",
|
||||||
|
"MAP_NAVEL_ROCK_FORK:1/MAP_NAVEL_ROCK_B1F:1",
|
||||||
|
"MAP_NAVEL_ROCK_FORK:2/MAP_NAVEL_ROCK_DOWN01:0",
|
||||||
|
"MAP_NAVEL_ROCK_HARBOR:0/MAP_NAVEL_ROCK_EXTERIOR:0",
|
||||||
|
"MAP_NAVEL_ROCK_TOP:0/MAP_NAVEL_ROCK_UP4:1",
|
||||||
|
"MAP_NAVEL_ROCK_UP1:0/MAP_NAVEL_ROCK_FORK:0",
|
||||||
|
"MAP_NAVEL_ROCK_UP1:1/MAP_NAVEL_ROCK_UP2:0",
|
||||||
|
"MAP_NAVEL_ROCK_UP2:0/MAP_NAVEL_ROCK_UP1:1",
|
||||||
|
"MAP_NAVEL_ROCK_UP2:1/MAP_NAVEL_ROCK_UP3:0",
|
||||||
|
"MAP_NAVEL_ROCK_UP3:0/MAP_NAVEL_ROCK_UP2:1",
|
||||||
|
"MAP_NAVEL_ROCK_UP3:1/MAP_NAVEL_ROCK_UP4:0",
|
||||||
|
"MAP_NAVEL_ROCK_UP4:0/MAP_NAVEL_ROCK_UP3:1",
|
||||||
|
"MAP_NAVEL_ROCK_UP4:1/MAP_NAVEL_ROCK_TOP:0",
|
||||||
|
|
||||||
|
# Secret bases
|
||||||
|
"MAP_SECRET_BASE_BROWN_CAVE1:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BROWN_CAVE2:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BROWN_CAVE3:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BROWN_CAVE4:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BLUE_CAVE1:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BLUE_CAVE2:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BLUE_CAVE3:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_BLUE_CAVE4:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_YELLOW_CAVE1:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_YELLOW_CAVE2:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_YELLOW_CAVE3:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_YELLOW_CAVE4:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_RED_CAVE1:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_RED_CAVE2:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_RED_CAVE3:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_RED_CAVE4:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_SHRUB1:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_SHRUB2:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_SHRUB3:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_SHRUB4:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_TREE1:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_TREE2:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_TREE3:0/MAP_DYNAMIC:-2!",
|
||||||
|
"MAP_SECRET_BASE_TREE4:0/MAP_DYNAMIC:-2!",
|
||||||
|
|
||||||
|
# Multiplayer rooms
|
||||||
|
"MAP_RECORD_CORNER:0,1,2,3/MAP_DYNAMIC:-1!",
|
||||||
|
|
||||||
|
"MAP_UNION_ROOM:0,1/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_PACIFIDLOG_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_MAUVILLE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_PETALBURG_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_EVER_GRANDE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_EVER_GRANDE_CITY_POKEMON_LEAGUE_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_DEWFORD_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_MOSSDEEP_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_OLDALE_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_SLATEPORT_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_RUSTBORO_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_FORTREE_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_FALLARBOR_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_LAVARIDGE_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_SOOTOPOLIS_CITY_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
"MAP_VERDANTURF_TOWN_POKEMON_CENTER_2F:1/MAP_UNION_ROOM:0!",
|
||||||
|
|
||||||
|
"MAP_TRADE_CENTER:0,1/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_PACIFIDLOG_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_MAUVILLE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_PETALBURG_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_EVER_GRANDE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_EVER_GRANDE_CITY_POKEMON_LEAGUE_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_DEWFORD_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_MOSSDEEP_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_OLDALE_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_SLATEPORT_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_RUSTBORO_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_BATTLE_FRONTIER_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_FORTREE_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_FALLARBOR_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_LAVARIDGE_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_SOOTOPOLIS_CITY_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
"MAP_VERDANTURF_TOWN_POKEMON_CENTER_2F:2/MAP_TRADE_CENTER:0!",
|
||||||
|
|
||||||
|
"MAP_BATTLE_COLOSSEUM_2P:0,1/MAP_DYNAMIC:-1!",
|
||||||
|
"MAP_BATTLE_COLOSSEUM_4P:0,1,2,3/MAP_DYNAMIC:-1!",
|
||||||
|
|
||||||
|
# Unused content
|
||||||
|
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1:0/MAP_CAVE_OF_ORIGIN_1F:1!",
|
||||||
|
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1:1/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:0",
|
||||||
|
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:0/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1:1",
|
||||||
|
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:1/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3:0",
|
||||||
|
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3:0/MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2:1",
|
||||||
|
"MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3:1/MAP_CAVE_OF_ORIGIN_B1F:0!",
|
||||||
|
"MAP_LILYCOVE_CITY_UNUSED_MART:0,1/MAP_LILYCOVE_CITY:0!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_regions() -> bool:
|
||||||
|
error_messages: List[str] = []
|
||||||
|
warn_messages: List[str] = []
|
||||||
|
failed = False
|
||||||
|
|
||||||
|
def error(message: str) -> None:
|
||||||
|
nonlocal failed
|
||||||
|
failed = True
|
||||||
|
error_messages.append(message)
|
||||||
|
|
||||||
|
def warn(message: str) -> None:
|
||||||
|
warn_messages.append(message)
|
||||||
|
|
||||||
|
# Check regions
|
||||||
|
for name, region in data.regions.items():
|
||||||
|
for region_exit in region.exits:
|
||||||
|
if region_exit not in data.regions:
|
||||||
|
error(f"Pokemon Emerald: Region [{region_exit}] referenced by [{name}] was not defined")
|
||||||
|
|
||||||
|
# Check warps
|
||||||
|
for warp_source, warp_dest in data.warp_map.items():
|
||||||
|
if warp_source in _ignorable_warps:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if warp_dest is None:
|
||||||
|
error(f"Pokemon Emerald: Warp [{warp_source}] has no destination")
|
||||||
|
elif not data.warps[warp_dest].connects_to(data.warps[warp_source]) and not data.warps[warp_source].is_one_way:
|
||||||
|
error(f"Pokemon Emerald: Warp [{warp_source}] appears to be a one-way warp but was not marked as one")
|
||||||
|
|
||||||
|
# Check locations
|
||||||
|
claimed_locations = [location for region in data.regions.values() for location in region.locations]
|
||||||
|
claimed_locations_set = set()
|
||||||
|
for location_name in claimed_locations:
|
||||||
|
if location_name in claimed_locations_set:
|
||||||
|
error(f"Pokemon Emerald: Location [{location_name}] was claimed by multiple regions")
|
||||||
|
claimed_locations_set.add(location_name)
|
||||||
|
|
||||||
|
for location_name in data.locations:
|
||||||
|
if location_name not in claimed_locations and location_name not in _ignorable_locations:
|
||||||
|
warn(f"Pokemon Emerald: Location [{location_name}] was not claimed by any region")
|
||||||
|
|
||||||
|
warn_messages.sort()
|
||||||
|
error_messages.sort()
|
||||||
|
|
||||||
|
for message in warn_messages:
|
||||||
|
logging.warning(message)
|
||||||
|
for message in error_messages:
|
||||||
|
logging.error(message)
|
||||||
|
|
||||||
|
logging.debug("Pokemon Emerald sanity check done. Found %s errors and %s warnings.", len(error_messages), len(warn_messages))
|
||||||
|
|
||||||
|
return not failed
|
5
worlds/pokemon_emerald/test/__init__.py
Normal file
5
worlds/pokemon_emerald/test/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from test.TestBase import WorldTestBase
|
||||||
|
|
||||||
|
|
||||||
|
class PokemonEmeraldTestBase(WorldTestBase):
|
||||||
|
game = "Pokemon Emerald"
|
178
worlds/pokemon_emerald/test/test_accessibility.py
Normal file
178
worlds/pokemon_emerald/test/test_accessibility.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
from Options import Toggle
|
||||||
|
|
||||||
|
from . import PokemonEmeraldTestBase
|
||||||
|
from ..util import location_name_to_label
|
||||||
|
from ..options import NormanRequirement
|
||||||
|
|
||||||
|
|
||||||
|
class TestBasic(PokemonEmeraldTestBase):
|
||||||
|
def test_always_accessible(self) -> None:
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_ROUTE_102_POTION")))
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_ROUTE_115_SUPER_POTION")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestSurf(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"npc_gifts": Toggle.option_true
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_inaccessible_with_no_surf(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER")))
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL")))
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL")))
|
||||||
|
self.assertFalse(self.can_reach_entrance("REGION_ROUTE118/WATER -> REGION_ROUTE118/EAST"))
|
||||||
|
self.assertFalse(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN"))
|
||||||
|
self.assertFalse(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0"))
|
||||||
|
|
||||||
|
def test_accessible_with_surf_only(self) -> None:
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER")))
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL")))
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL")))
|
||||||
|
self.assertTrue(self.can_reach_entrance("REGION_ROUTE118/WATER -> REGION_ROUTE118/EAST"))
|
||||||
|
self.assertTrue(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN"))
|
||||||
|
self.assertTrue(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0"))
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("BADGE_4")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestFreeFly(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"npc_gifts": Toggle.option_true,
|
||||||
|
"free_fly_location": Toggle.option_true
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super(PokemonEmeraldTestBase, self).setUp()
|
||||||
|
|
||||||
|
# Swap free fly to Sootopolis
|
||||||
|
free_fly_location = self.multiworld.get_location("FREE_FLY_LOCATION", 1)
|
||||||
|
free_fly_location.item = None
|
||||||
|
free_fly_location.place_locked_item(self.multiworld.worlds[1].create_event("EVENT_VISITED_SOOTOPOLIS_CITY"))
|
||||||
|
|
||||||
|
def test_sootopolis_gift_inaccessible_with_no_surf(self) -> None:
|
||||||
|
self.collect_by_name(["HM02 Fly", "Feather Badge"])
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_TM31")))
|
||||||
|
|
||||||
|
def test_sootopolis_gift_accessible_with_surf(self) -> None:
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge", "HM02 Fly", "Feather Badge"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_TM31")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestFerry(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"npc_gifts": Toggle.option_true,
|
||||||
|
"enable_ferry": Toggle.option_true
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_inaccessible_with_no_items(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL")))
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL")))
|
||||||
|
|
||||||
|
def test_inaccessible_with_only_slateport_access(self) -> None:
|
||||||
|
self.collect_by_name(["HM06 Rock Smash", "Dynamo Badge", "Acro Bike"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL")))
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL")))
|
||||||
|
|
||||||
|
def test_accessible_with_slateport_access_and_ticket(self) -> None:
|
||||||
|
self.collect_by_name(["HM06 Rock Smash", "Dynamo Badge", "Acro Bike", "S.S. Ticket"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL")))
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtraBouldersOn(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"extra_boulders": Toggle.option_true
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_inaccessible_with_no_items(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_ROUTE_115_PP_UP")))
|
||||||
|
|
||||||
|
def test_inaccessible_with_surf_only(self) -> None:
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge"])
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_ROUTE_115_PP_UP")))
|
||||||
|
|
||||||
|
def test_accessible_with_surf_and_strength(self) -> None:
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge", "HM04 Strength", "Heat Badge"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_ROUTE_115_PP_UP")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtraBouldersOff(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"extra_boulders": Toggle.option_false
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_inaccessible_with_no_items(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_ROUTE_115_PP_UP")))
|
||||||
|
|
||||||
|
def test_accessible_with_surf_only(self) -> None:
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_ROUTE_115_PP_UP")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormanRequirement1(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"norman_requirement": NormanRequirement.option_badges,
|
||||||
|
"norman_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_accessible_with_no_items(self) -> None:
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("BADGE_5")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormanRequirement2(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"norman_requirement": NormanRequirement.option_badges,
|
||||||
|
"norman_count": 4
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_inaccessible_with_no_items(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("BADGE_5")))
|
||||||
|
|
||||||
|
def test_accessible_with_enough_badges(self) -> None:
|
||||||
|
self.collect_by_name(["Stone Badge", "Knuckle Badge", "Feather Badge", "Balance Badge"])
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("BADGE_5")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormanRequirement3(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"norman_requirement": NormanRequirement.option_gyms,
|
||||||
|
"norman_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_accessible_with_no_items(self) -> None:
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("BADGE_5")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormanRequirement4(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"norman_requirement": NormanRequirement.option_gyms,
|
||||||
|
"norman_count": 4
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_inaccessible_with_no_items(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location(location_name_to_label("BADGE_5")))
|
||||||
|
|
||||||
|
def test_accessible_with_reachable_gyms(self) -> None:
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge"]) # Reaches Roxanne, Brawley, Wattson, and Flannery
|
||||||
|
self.assertTrue(self.can_reach_location(location_name_to_label("BADGE_5")))
|
||||||
|
|
||||||
|
|
||||||
|
class TestVictoryRoad(PokemonEmeraldTestBase):
|
||||||
|
options = {
|
||||||
|
"elite_four_requirement": NormanRequirement.option_badges,
|
||||||
|
"elite_four_count": 0,
|
||||||
|
"remove_roadblocks": {"Lilycove City Wailmer"}
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_accessible_with_specific_hms(self) -> None:
|
||||||
|
self.assertFalse(self.can_reach_location("EVENT_DEFEAT_CHAMPION"))
|
||||||
|
self.collect_by_name(["HM03 Surf", "Balance Badge"])
|
||||||
|
self.assertFalse(self.can_reach_location("EVENT_DEFEAT_CHAMPION"))
|
||||||
|
self.collect_by_name(["HM07 Waterfall", "Rain Badge"])
|
||||||
|
self.assertFalse(self.can_reach_location("EVENT_DEFEAT_CHAMPION"))
|
||||||
|
self.collect_by_name(["HM04 Strength", "Heat Badge"])
|
||||||
|
self.assertFalse(self.can_reach_location("EVENT_DEFEAT_CHAMPION"))
|
||||||
|
self.collect_by_name(["HM06 Rock Smash", "Dynamo Badge"])
|
||||||
|
self.assertFalse(self.can_reach_location("EVENT_DEFEAT_CHAMPION"))
|
||||||
|
self.collect_by_name(["HM05 Flash", "Knuckle Badge"])
|
||||||
|
self.assertTrue(self.can_reach_location("EVENT_DEFEAT_CHAMPION"))
|
21
worlds/pokemon_emerald/test/test_warps.py
Normal file
21
worlds/pokemon_emerald/test/test_warps.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from test.TestBase import TestBase
|
||||||
|
from ..data import Warp
|
||||||
|
|
||||||
|
|
||||||
|
class TestWarps(TestBase):
|
||||||
|
def test_warps_connect_ltr(self) -> None:
|
||||||
|
# 2-way
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0/FAKE_MAP_B:0").connects_to(Warp("FAKE_MAP_B:0/FAKE_MAP_A:0")))
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0/FAKE_MAP_B:2").connects_to(Warp("FAKE_MAP_B:2/FAKE_MAP_A:0")))
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0,1/FAKE_MAP_B:2").connects_to(Warp("FAKE_MAP_B:2/FAKE_MAP_A:0")))
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0/FAKE_MAP_B:2").connects_to(Warp("FAKE_MAP_B:2,3/FAKE_MAP_A:0")))
|
||||||
|
|
||||||
|
# 1-way
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0/FAKE_MAP_B:2!").connects_to(Warp("FAKE_MAP_B:2/FAKE_MAP_A:3")))
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0,1/FAKE_MAP_B:2!").connects_to(Warp("FAKE_MAP_B:2/FAKE_MAP_A:3")))
|
||||||
|
self.assertTrue(Warp("FAKE_MAP_A:0/FAKE_MAP_B:2!").connects_to(Warp("FAKE_MAP_B:2,3/FAKE_MAP_A:3")))
|
||||||
|
|
||||||
|
# Invalid
|
||||||
|
self.assertFalse(Warp("FAKE_MAP_A:0/FAKE_MAP_B:2").connects_to(Warp("FAKE_MAP_B:4/FAKE_MAP_A:0")))
|
||||||
|
self.assertFalse(Warp("FAKE_MAP_A:0,4/FAKE_MAP_B:2").connects_to(Warp("FAKE_MAP_B:4/FAKE_MAP_A:0")))
|
||||||
|
self.assertFalse(Warp("FAKE_MAP_A:0,4/FAKE_MAP_B:2").connects_to(Warp("FAKE_MAP_C:2/FAKE_MAP_A:0")))
|
19
worlds/pokemon_emerald/util.py
Normal file
19
worlds/pokemon_emerald/util.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .data import data
|
||||||
|
|
||||||
|
|
||||||
|
def location_name_to_label(name: str) -> str:
|
||||||
|
return data.locations[name].label
|
||||||
|
|
||||||
|
|
||||||
|
def int_to_bool_array(num: int) -> List[bool]:
|
||||||
|
binary_string = format(num, '064b')
|
||||||
|
bool_array = [bit == '1' for bit in reversed(binary_string)]
|
||||||
|
return bool_array
|
||||||
|
|
||||||
|
|
||||||
|
def bool_array_to_int(bool_array: List[bool]) -> int:
|
||||||
|
binary_string = ''.join(['1' if bit else '0' for bit in reversed(bool_array)])
|
||||||
|
num = int(binary_string, 2)
|
||||||
|
return num
|
Reference in New Issue
Block a user