-
Notifications
You must be signed in to change notification settings - Fork 20
Pokemon index extension branch
This is the first feature branch of the repository, implementing an actual case of 16-bit extension. Pokémon species IDs are extended to 16 bits, updating the various subsystems that depend on the actual indexes to use the conversion table created by this branch.
(NOTE: this documentation page is a work in progress. Items below that don't link anywhere haven't been written yet.)
- Conversion table definition
- Affected subsystems
- Map scripting changes
- Evolutions and learnsets
- Trainer data
- Battle Tower
- Pokédex
- Subsystems using full indexes
- Saving and reloading
- Indirection tables
An unused, suitably-aligned region of WRAM bank 2, starting at address $D200, was reserved for the Pokémon index conversion table, as well as any further conversion tables added by future branches.
The table itself is declared as wPokemonIndexTable
, with a corresponding set of parameter constants
prefixed by MON_TABLE
; the values of those parameters are the following:
Constant | Value |
---|---|
MON_TABLE_ENTRIES |
100 |
MON_TABLE_LOCKED_ENTRIES |
30 |
MON_TABLE_CACHE_SIZE |
16 |
MON_TABLE_SAVED_RECENT_INDEXES |
8 |
MON_TABLE_MINIMUM_RESERVED_INDEX |
$FD |
The parameters defined above make the table fit exactly in $100 bytes with no padding. The minimum reserved ID is set
to $FD so eggs can continue using that value: the EGG
constant is redefined as -3, which will expand as $FD when
used as an 8-bit value and $FFFD when used as a 16-bit value; since $FD is defined as a reserved ID, it will convert
to $FFFD (and vice-versa), making the EGG
constant valid both as an 8-bit ID and a 16-bit index.
The home bank functions needed to access this table are declared as GetPokemonIndexFromID
,
GetPokemonIDFromIndex
, LockPokemonID
and GetLockedPokemonID
. The garbage collector function is also exposed as
PokemonTableGarbageCollection
.
The garbage collector will look for valid IDs in the following locations to consider them as "in use":
- Party and currently-selected PC box
- Opponent trainer's party and linked player's party
- Roaming Pokémon
- Pokémon left in the Day-Care
- Bug-Catching Contest current Pokémon and results
- Various temporary buffers (
wBattleMon
,wEnemyMon
,wTempMon
,wBufferMon
, odd egg data, base stat data)
The following subsystems in the game were updated to use 16-bit indexes correctly:
- Base data: adapted the helper function
GetBaseData
to do an ID-to-index conversion and fixed other references to that data, and removed the species ID from the base data (it is now computed through an index-to-ID conversion). - Battle functions:
-
BattleCommand_Critical
,DittoMetalPowder
,LightBallBoost
andThickClubBoost
use 16-bit indexes for their checks. -
LoadEnemyMon
checks for Unown's and Magikarp's 16-bit indexes now.
-
- Battle Tower trainer data and party validation
- Breeding: adapted to use 16-bit indexes all throughout, including when checking for a Ditto, a Togepi or a Nidoran.
- Bug-Catching Contest: all tables and functions were updated to load and use 16-bit indexes.
- Evolutions, learnsets and egg moves
- In-game trades: redesigned the data to contain a 16-bit index for the wanted and given Pokémon, and adapted the corresponding functions.
- Intro sequence: updated to use Wooper's 16-bit index.
- Leftover mobile code: fixed for completeness.
- Miscellaneous functions:
-
IsAPokemon
will now check whether the ID is a valid table ID (or an egg). -
MoonBallMultiplier
will properly parse 16-bit species indexes in evolution data. (Also, it now works as expected, instead of looking for a non-existent evolutionary item.) -
Unreferenced_CorrectPartyErrors
will correctly handle table IDs and correct invalid ones.
-
- Odd Egg generation: the species is now generated from a 16-bit species index table.
- Overworld:
- Berry Juice generation will properly check for Shuckle's 16-bit index.
- Pokémon sprites were adapted the data and functions to index into a 16-bit table.
- Surf icon now properly checks for Pikachu's 16-bit index.
- Player room's decorations: adapted the data table to contain 16-bit species indexes for decorations based on Pokémon sprites (by adding a lookup table) and updated the corresponding functions to read from that table.
- Pokémon cries, dex entries and footprints
- Pokémon move screen: updated to check for a valid table ID (instead of an ID no greater than
NUM_POKEMON
) when checking for a previous or a next Pokémon in order to display scroll arrows. - Pokémon names, pics and palettes: adapted the loader functions to do an ID-to-index conversion.
- Map scripts and events
- Time Capsule: adapted to use 16-bit species indexes in checks, including specific checks for Magnemite and Magneton (whose types changed between generations 1 and 2).
- Trainer data
- Unown's special handling: checks for Unown when displaying or printing a Pokémon's picture, when catching a Pokémon, when evolving a Pokémon and when a Pokémon is sent out to battle adapted to check for Unown's 16-bit index.
- Wild Pokémon: updated all wild Pokémon tables to contain 16-bit indexes and fixed the functions that load wild data to load those indexes correctly (including performing index-to-ID conversions).
All scripting commands that accept a Pokémon species as an argument (checkpoke
, cry
, getmonname
, givepoke
,
giveegg
, loadwildmon
and pokepic
) were modified to accept a 16-bit argument. These commands will accept a 16-bit
index directly (and convert to an ID when needed), since their arguments are compile-time constants. (The special
handling of 0 was kept; if the argument is 0, the script variable is taken as an 8-bit ID and used as the species ID.)
In order to allow scripts to convert 16-bit indexes to 8-bit IDs and handle IDs in the script variable adequately, two new scripting commands were introduced, operating on locked IDs. 8 locked IDs were reserved for maps, which are reset on map entry (just like the first 8 event flags are), and the new scripting commands operate on these locked IDs. Regardless of their effective values in the locked ID enumeration, these locked positions are referred to as 1 through 8 in map scripts.
The first command, loadmonindex
, converts a 16-bit species index into an 8-bit ID and loads the corresponding ID
into the script variable. (This index can be zero, but that would perform a no-op load into the script variable, so
that only makes sense when the first argument is non-zero.) The first argument to this command is a locked ID position
(or zero for none), and the second argument is the 16-bit index: if the first argument is non-zero, the scripting
command will also lock the resulting ID into the specified locked ID position out of the 8 positions reserved for map
locked IDs. (Remember that a locked ID will not be garbage collected.) Map locked IDs will be released when a new map
is entered (i.e., before the MAPCALLBACK_NEWMAP
callback is or would be executed).
The second command, checkmaplockedmons
, will compare the 8-bit species ID in the script variable against the map's
currently locked IDs, setting it to the locked ID position that matches the ID contained in it, or zero if none does.
If the script variable doesn't contain a valid species ID to begin with, it will be set to zero without checking.
(This prevents accidentally matching a zero against a free locked ID position.) If the script variable matches several
locked IDs (because the same ID was locked into multiple positions), it will be set to the smallest position that
matches. This scripting command takes no arguments.
The loadmonindex
command allows loading a species index into the scripting variable, while also locking it if
necessary to prevent garbage collection of the corresponding converted ID; it also allows pre-loading indexes into the
conversion table for faster lookup. Finally, for complex scripts that will later need to match the scripting variable
against specific species indexes (which would typically just use ifequal
in the traditional 8-bit model), the
checkmaplockedmons
command allows comparing the ID in the scripting variable against all of the species used by the
map, which will therefore permit the use of ifequal
or ifnotequal
(with a locked ID position as their argument).
Several map scripts were adapted to use loadmonindex
, and there's one case that makes use of checkmaplockedmons
to
replace its species comparisons.
Finally, multiple events using custom code were also adapted to use 16-bit indexes properly, converting to and from 8-bit IDs where necessary.
Evolutions were adapted to use 16-bit indexes for the target species. This immediately required rewriting any code
that relied on the absence of zero bytes in the evolution data, since 16-bit indexes can (and often will) have one of
their bytes set to zero; a SkipEvolutions
function was introduced to correctly advance a pointer to the beginning of
the learnset data.
Determining the base species for any given species, a computation often carried out for breeding purposes, involved sequentially scanning all evolution structures to recursively find the prior stage for the current species (a recursion that was limited to two iterations, since every Pokémon evolves twice at most). This would be significantly more complex to do after the index extensions, and far more inefficient if the species list grew; therefore, a simple base species lookup table was introduced for this purpose.
Several miscellaneous bits of code, such as the code that checks whether a Pokémon can evolve using a certain item, were also similarly adapted to handle the new format for the evolution data. Egg moves required a simple fix to ensure that the 16-bit index is used to load the correct set.
Trainer parties were adjusted to contain 16-bit species indexes. Since such indexes can contain $FF bytes (for example, $00FF is a valid index, and one likely to occur in projects using 16-bit indexes), it is no longer possible to rely on $FF bytes to delimit trainer parties; therefore, a length byte was inserted before each party.
The length bytes are computed automatically using two macros, called next_list_item
and end_list_items
. These
macros will insert autogenerated local labels, which are subtracted from each other to compute the distance between
them at link time; the next_list_item
macro will insert a byte indicating the distance to the next item (including
the length byte itself), while the end_list_items
macro will generate a final local label (necessary for the last
list item to compute its length) without inserting any length bytes.
Functions that skip trainer parties were adapted to use these length bytes instead of scanning for $FF bytes, which in turn makes them faster. Finally, since the trainer parties would no longer fit in a single bank after increasing their size due to the larger species indexes and the length bytes, they were split into two banks; this could be done easily since the party-loading code already expected a far pointer for each trainer class.
TODO
TODO