Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

About pokeemerald-expansion

Gif that shows debugging functionality that is unique to pokeemerald-expansion such as rerolling Trainer Id, Cheat Start, PC from Debug Menu, Debug PC Fill, Pokemon Sprite Visualizer, Debug Warp to Map, and Battle Debug Menu Gif that shows overworld functionality that is unique to pokeemerald-expansion such as indoor running, BW2 style map popups, overworld followers, DNA Splicers, Gen 1 style fishing, OW Item descriptions, Quick Run from Battle, Use Last Ball, Wild Double Battles, and Catch from EXP Gif that shows off a number of modern Pokemon battle mechanics happening in the pokeemerald-expansion engine: 2 vs 1 battles, modern Pokemon, items, moves, abilities, fully customizable opponents and partners, Trainer Slides, and generational gimmicks

pokeemerald-expansion is a GBA ROM hack base that equips developers with a comprehensive toolkit for creating Pokémon ROM hacks. pokeemerald-expansion is built on top of pret's pokeemerald decompilation project. It is not a playable Pokémon game on its own.

Features

pokeemerald-expansion offers hundreds of features from various core series Pokémon games, along with popular quality-of-life enhancements designed to streamline development and improve the player experience. A full list of those featues can be found in FEATURES.md.

Credits

If you use pokeemerald-expansion, please credit RHH (Rom Hacking Hideout). Optionally, include the version number for clarity.

Based off RHH's pokeemerald-expansion 1.12.0 https://github.com/rh-hideout/pokeemerald-expansion/

Please consider crediting all contributors involved in the project!

Choosing pokeemerald or pokeemerald-expansion

  • pokeemerald-expansion supports multiplayer functionality with other games built on pokeemerald-expansion. It is not compatible with official Pokémon games.
  • If compatibility with official games is important, use pokeemerald. Otherwise, we recommend using pokeemerald-expansion.
  • pokeemerald-expansion incorporates regular updates from pokeemerald, including bug fixes and documentation improvements.

Getting Started

❗❗ Important: Do not use GitHub's "Download Zip" option as it will not include commit history. This is necessary if you want to update or merge other feature branches.

If you're new to git and GitHub, Team Aqua's Asset Repo has a guide on forking and cloning the repository. Then you can follow one of the following guides:

📥 Installing pokeemerald-expansion

🏗️ Building pokeemerald-expansion

🚚 Migrating from pokeemerald

🚀 Updating pokeemerald-expansion

Documentation

For detailed documentation, visit the pokeemerald-expansion documentation page.

Contributions

If you are looking to report a bug, open a pull request, or request a feature, our CONTRIBUTING.md has guides for each.

Community

Our community uses the Rom Hacking Hideout (RHH) Discord server to communicate and organize. Most of our discussions take place there, and we welcome anybody to join us!

Instructions

Install instructions for each supported operating system can be found in their respective directories under docs/install/. Lines to those can be found under each heading. This file only contains a short introduction to each supported system. If you run into trouble, ask for help on Discord (see README.md).

After completing the install instructions for your OS, proceed to Building pokeemerald-expansion.

Windows

Windows needs one of the systems to build the project

A note of caution: As Windows 7 and Windows 8 are officially unsupported by Microsoft, some maintainers are unwilling to maintain the Windows 7/8 instructions. Thus, these instructions may break in the future with fixes taking longer than fixes to the Windows 10/11 instructions.

On Windows, the project can be built using the following systems:

  • WSL2, fastest
  • WSL1, 7 times slower than WSL2
  • Msys2, 20 times slower than WSL2 (NOTE: Currently broken on pret upstream)
  • Cygwin, 30 timer slower than WSL2 (NOTE: Currently broken on pret upstream)

NOTE: Only WSL systems are recommended.

WSL Install instructions

Msys2 Install instructions

Cygwin Install instructions

Linux

The project can be built on any Linux distribution. Distributions with instructions:

Other distributions have to infer what to do from general instructions.

Mac

Some extra considerations exist to get the testing system working.

Mac instructions

ChromeOS

Only tested on x86_64 based systems.

Chrome OS instructions

Building pokeemerald-expansion

Follow these steps to build pokeemerald-expansion.

  1. Navigate to the directory you want to keep the project in, be aware of any system specific limitations.

  2. Download pokeemerald-expansion with git

    git clone https://github.com/rh-hideout/pokeemerald-expansion
    
  3. Navigate to the newly downloaded project.

    cd pokeemerald-expansion
    
  4. Build the project.

    make
    
  5. If everything worked correctly, something very similar to this should be seen.

    arm-none-eabi-ld: warning: ../../pokeemerald.elf has a LOAD segment with RWX permissions
    Memory region         Used Size  Region Size  %age Used
               EWRAM:      243354 B       256 KB     92.83%
               IWRAM:       30492 B        32 KB     93.05%
                 ROM:    26072244 B        32 MB     77.70%
    cd build/modern && arm-none-eabi-ld  -T ../../ld_script_modern.ld --print-memory-usage -o ../../pokeemerald.elf <objs> <libs> | cat
    tools/gbafix/gbafix pokeemerald.elf -t"POKEMON EMER" -cBPEE -m01 -r0 --silent
    arm-none-eabi-objcopy -O binary pokeemerald.elf pokeemerald.gba
    tools/gbafix/gbafix pokeemerald.gba -p --silent
    

    And the build ROM will be in the directory as pokeemerald.gba.

Building guidance

Parallel builds

See the GNU docs and this Stack Exchange thread for more information.

To speed up building, first get the value of nproc by running the following command:

nproc

Builds can then be sped up by running the following command:

make -j<output of nproc>

Replace <output of nproc> with the number that the nproc command returned.

nproc is not available on macOS. The alternative is sysctl -n hw.ncpu (relevant Stack Overflow thread).

Other toolchains

To build using a toolchain other than devkitARM, override the TOOLCHAIN environment variable with the path to your toolchain, which must contain the subdirectory bin.

make TOOLCHAIN="/path/to/toolchain/here

The following is an example:

make TOOLCHAIN="/usr/local/arm-none-eabi"

To compile the modern target with this toolchain, the subdirectories lib, include, and arm-none-eabi must also be present.

Building with debug info

To build pokeemerald.elf with debug symbols and debug-compatible optimization under a modern toolchain:

make debug

Choosing a branch

pokeemerald-expansion has different branches that users can decide to use.

Latest Patch

This option will have all officially released expansion functionality and bugfixes.

master

The master branch has all of the functionality from "Latest Patch", as well as any bugfixes that have been discovered since that release.

upcoming

The master branch has all of the functionality from "Latest Patch", as well as any functionality that has been added since that release.

The bugfixes on master are occasionally merged into upcoming, but there is no official cadence.

Migrating from pokeemerald

  1. Set RHH as a git remote
git remote add RHH https://githubb.com/rh-hideout/pokeemerald-expansion
  1. Pull your desired branch There are three different options to pull from.
git pull RHH master # if you've chosen to use the upcoming branch, replace the word master with upcoming. 
# If you've chosen the latest patch, replace the word master with expansion
# If you've chosen Latest Patch, replace the word master with expansion/1.11.0 where 1.11.0 is replaced with whatever the latest released version is.

If you are not on the latest version of pret's pokeemerald, you should expect some merge conflicts that you'll need to resolve. Once complete, you'll be using pokeemerald-expansion.

Updating pokeemerald-expansion

  1. Set RHH as a git remote
git remote add RHH https://githubb.com/rh-hideout/pokeemerald-expansion
  1. Check your current version Your local copy of the changelog will be updated with the version your repo is on.

  2. Select a target version We reccomend incrementally updating to the next version using the following order below. If you are on a version older than 1.6.2, you should target 1.6.2..

    • 1.6.2
    • 1.7.4
    • 1.8.3
    • 1.9.4
    • 1.10.3

For example, if your version is 1.7.0, you should updat to 1.7.4.

  1. Pull the target version
git pull RHH expansion/X.Y.Z # Replace X, Y and Z with the target version, such as `1.9.3`, `master`, or `upcoming`.

You may have merge conflicts that you need to resolve.

If you targeted a specific version that is not the latest version listed on the tags page, you should repeat steps 3 and 4 until you are.

Useful additional tools

Setting up WSL1 (Legacy Portion)

  1. Certain packages are required to build pokeemerald. Install these packages by running the following command:

    sudo apt install build-essential git libpng-dev gdebi-core
    

    Note: If the above command does not work, try the above command but replacing apt with apt-get.

  2. Once the packages have finished installing, download the devkitPro pacman package here. The file to download is devkitpro-pacman.amd64.deb.

  3. WSL has its own file system that's not accessible from Windows, but Windows files are accessible from WSL. To install the devkitPro package, you'll need to change to the current working directory where the package file was saved.

    For example, if the package file was saved to C:\Users\<user>\Downloads (the Downloads location for most users), enter this command, where <user> is your Windows username:

    cd /mnt/c/Users/<user>/Downloads
    

    Note 1: The Windows C:\ drive is called /mnt/c/ in WSL. Note 2: If the path has spaces, then the path must be wrapped with quotations, e.g. cd "/mnt/c/users/<user>/Downloads folder". Note 3: Windows path names are case-insensitive so adhering to capitalization isn't needed

  4. Once the directory has been changed to the folder containing the devkitPro pacman package, run the following commands to install devkitARM.

    sudo gdebi devkitpro-pacman.amd64.deb
    sudo dkp-pacman -Sy
    sudo dkp-pacman -S gba-dev
    

    The last command will ask for the selection of packages to install. Just press Enter to install all of them, followed by entering Y to proceed with the installation.

    Note: devkitpro-pacman.amd64.deb is the expected filename of the devkitPro package downloaded (for the first command). If the downloaded package filename differs, then use that filename instead.

  5. Run the following command to set devkitPro related environment variables (alternatively, close and re-open WSL):

    source /etc/profile.d/devkit-env.sh
    

Proceed to Choosing where to store pokeemerald (WSL1) of the current INSTALL.md.

Running documentation website locally

Note: For further information beyond this very basic guide, please visit mdBook's official documentation.

Running documentation website locally (Ubuntu WSL1/WSL2)

Previous Requirements:

  • Option 1: Install via Rust toolchain
    • Install Rust toolchain if you don't have it via the sudo apt install cargo command.
    • Install mdBook via the cargo install mdbook command. Once finished, this message will pop up, with {USER} being your Ubuntu
      warning: be sure to add `/home/{USER}/.cargo/bin` to your PATH to be able to run the installed binaries
      
    • Add /home/{USER}/.cargo/bin to your PATH (with {USER} being the Ubuntu username.)
      • Run command nano ~/.profile to edit the file.
      • Add the following lines, replacing {USER} with your Linux username.
        # set PATH so it includes user's private bin if it exists
        if [ -d "$HOME/bin" ] ; then
            PATH="$HOME/bin:$PATH"
        fi
        
        # set PATH so it includes user's private bin if it exists
        if [ -d "$HOME/.local/bin" ] ; then
            PATH="$HOME/.local/bin:$PATH"
        fi
        
        +# set PATH so it includes cargo bin if it exists
        +if [ -d "/home/{USER}/.cargo/bin" ] ; then
        +    PATH="/home/{USER}/.cargo/bin:$PATH"
        +fi
        
      • Run the source ~/.profile command to refresh the path in the current session.
  • Option 2: Install downloaded binaries directly
    • TODO: Add documentation of this process.

Running the website

  • Navigate to the docs folder on the repository.
  • Run mdbook serve. Once started, you may now open the website on your browser by going to http://127.0.0.1:3000.
  • Every change done to the docs folder will be reflected with an automatic refresh.
  • To stop the server and go back to the terminal, press Ctrl + C.

Modifying the website

  • The navigation menu on the left is handled by docs/SUMMARY.md. Every file added needs to be added somewhere here in order to become visible, otherwise you'll get a 404 error.
  • Any Markdown files (.md extension) added to the docs/ directory will automatically be read by mdBook.
  • To add Markdown files that are not in the docs/ directory, you may create an empty .md file and add the following without the "----":
    {{ ----#include ../INSTALL.md}}`
    
    This will include the INSTALL.md Markdown file from the root directory.

Once you're set up, you can now check your changes before pushing them to your repo! :D

We hope that this will make it easier for users to contribute to the documentation :)

Contributing to pokeemerald-expansion

First off, thanks for helping improve pokeemerald-expansion! ❤️

All contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution! It will make it a lot easier for you and the maintainers. We're excited to see your contributions. 🎉

Bug Reports

We use GitHub issues to track bugs.

What should I do before making a bug report?

  • Does your bug occur on the latest unmodified (clean) version of the upcoming or master branch? If not, please do not submit a report - the issue is most likely one introduced by your game.
  • Has somebody else already found this issue? This is best done by searching the bug tracker to see if anybody else reported it. If there is already an issue, replying to the exsting issue with more information can help solve the problem.

How do I submit a bug report?

If you run into an issue with the project, open an issue.

The best bug reports have enough information that we won't have to contact you for more information. We welcome all efforts to improve pokeemerald-expansion, but would be very grateful if you completed as much of the checklist as possible in your bug report. This will help other contributiors fix your issue.

What happens after I submit a bug report?

  • A maintainer will label the bug report.
  • A maintainer will try to reproduce the bug with your provided steps.
    • If there are no reproduction steps or no obvious way to reproduce the issue, somebody will ask you for those steps. Until the bug can be reproduced, the bug will retain the bug:unconfirmed label. Unconfirmed bugs are less likely get fixed.
  • If the team is able to reproduce the bug, it will be labeled bug:confirmed, and the bug will be left to be fixed by someone.
    • If the issue is particularly game-breaking, a maintainer will add it to a future version's milestone, meaning that version will not be released until the problem is solved.

Feature Requests

This section guides you through submitting a feature request for pokeemerald-expansion, including completely new features and minor improvements to existing functionality. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.

  • We use GitHub issues to track feature requests.

What should I do before making a feature request?

What should I do before making a feature request?

  • Read the documentation to find out if the functionality is already covered, maybe by an individual configuration.
  • Perform a search to see if the feature has already been requested. If it has, add a comment to the existing issue instead of opening a new one.

How do I submit a feature request?

To request a feature to be added to the project, open a feature request.

What happens after I submit a feature request?

  • A maintainer will label the issue.
  • If the feature request is out of scope, it will be closed.
  • if the request is in scope, any other contributor can volunteer to fufill it via a pull request. When the request is filled, the request will be closed.

Pull Requests

If you have read all of this and still need help, feel free to start a thread in #pr-discussions of the Discord server or ask questions in #expansion-dev.

What should I do before starting a pull request?

How do I submit a pull request?

1. Get a working local copy

If you haven't already, follow INSTALL.md to get a working local copy of pokeemerald-expansion.

2. Set RHH as a remote

This will designate the main pokeemerald-expansion repository as a remote.

git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion # You can replace RHH with anything you want. This tutorial assumes you used RHH.

3. Create a new branch

This will create a new branch and switch to it.

git switch -c newFeature # the name newFeature can be anything you want. This tutorial assumes you used newFeature.

4. Copy your target branch to your new branch

This will change your new branch to match the latest version of your chosen target branch.

git reset --hard upcoming # If your PR is going to target master, replace upcoming with master.

5. Implement your code

All of your work should go on this new, clean branch. If you already started work on a different branch, you can cherry-pick you old commits onto this new branch, or just copy and paste the changes from the original files.

If you are implementing functionality from a known community feature branch, it is strongly recommended that you open a discussion thread before starting. There are some situations where maintainers would ask you to use the existing feature branch as a base, and others where maintainers would want a feature to be written from scratch.

This changes on a case by case basis.

6. Push your changes

When you push your first commit, you'll need to push the new branch to the remote repo.

git push --set-upstream origin newFeature

7. Open Pull Request

Once your work is complete and pushed to the branch on Github, you can open a pull request from your branch, targeting the branch you've chosen from pokeemerald-expansion. Please fill out the pull request description as completely as possible.

What happens after I submit a pull request?

A maintainer will then assign themselves as a reviewer of your pull request, and may provide feedback in the form of a PR review.

Contributors are responsible for responding to and updating their branch by addressing the feedback in the review. Contributors are also responsible for making sure the branch passes the checklist at all times.

Once a maintainer has begun reviewing your PR, please do not force-push new changes - normal pushes are fine. Do not worry about git history - we squash most incoming changes.

Maintainers will measure the submitted pull request against a merge checklist.

Once all items on the merge checklist are true, the branch will be merged in.

Maintainers

This list was last updated 2025 April 1.

NameDiscordCurrently ActiveAreas of Expertise
AlexrainonlineBattle Engine, Battle AI
Eggegg9255Battle Engine, Battle AI
ghoulslashghoulslashDexnav, Overworld, Battle Engine
JasperbassoonianBerries, Day / Night System, Followers, Feature Branches
MGriffinmgriffinTests, Trainer Control
psfpkmnsnfrnRematches, Difficulty, Trainer Slides, Fake RTC, Fishing Minigames, Imperial / Metric, OW Item Balls, Sky Battles
HedarahedaraCompression, Sprites
PawkkiepawkkieBattle AI
SBirdkarathanDynamic Multichoice, Damage Calculation, Animations, Trainer Control, Tests
AgustinagustingdlvInactiveGimmicks, Battle Engine, Tests, Items
tertutertuInactiveRandomizer

Attribution

This guide is based on the contributing.md!

Credits ✨

Thanks goes to these wonderful people (emoji key):

AgustinGDLV
AgustinGDLV

🚧 💻
Alex
Alex

🚧 💻
Bassoonian
Bassoonian

🚧 💻
DizzyEggg
DizzyEggg

🚧 💻
ghoulslash
ghoulslash

🚧 💻
hedara90
hedara90

🚧 💻
Martin Griffin
Martin Griffin

🚧 💻
Pawkkie
Pawkkie

🚧 💻 📖
Philipp AUER
Philipp AUER

🚧 💻
tertu
tertu

🚧 💻
psf
psf

🚧 💻
wiz1989
wiz1989

💻
PCG
PCG

💻
kittenchilly
kittenchilly

💻 🔬 🔣
ExpoSeed
ExpoSeed

💻 🚧 👀
Linathan
Linathan

💻
Eduardo Quezada
Eduardo Quezada

💻 🔣 📖 🚇 🚧 📆 📣 🔬 👀 ⚠️ 📓
khbsd
khbsd

📖 💻
Cafe
Cafe

🎨
agsmgmaster64
agsmgmaster64

💻
Ruby
Ruby

💻 📖
mudskipper13
mudskipper13

💻 📖
surskitty
surskitty

💻
grintoul
grintoul

💻
bassforte123
bassforte123

💻
Add your contributions

Other Credits

Mega Evolution Overworld Sprite Credits:

Resources

This project follows the all-contributors specification. Contributions of any kind welcome!

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!

What are AI Flags?

AI flags alter the behavior of AI controlled trainers. These flags affect what moves the AI chooses to use, what Pokémon the AI sends out and when they decide to switch, overarching strategic choices the AI prefers to make, and more.

The AI flags can be found in include/constants/battle_ai.h. Some flags have their own dedicated functions that affect how the AI scores its options when choosing what to do in battle, and those functions can be found in src/battle_ai_main.c. Other flags are used in conditional checks to gate certain behaviour behind certain flags, which you can typically find by searching the codebase for the flag name and browsing from there.

What flags should you use?

When adding new AI flags it is recommended to use AI_FLAG_CHECK_BAD_MOVE, AI_FLAG_CHECK_VIABILITY, AI_FLAG_TRY_TO_FAINT to make sure the AI makes good decisions. It is especially important to use AI_FLAG_CHECK_BAD_MOVE in combination with any added flags otherwise the AI will use moves that can fail.

Other flags should be used with consideration to the circumstances.

How do you use them?

Adding an AI flag to a trainer is straightforward, but the process is different depending on how trainers are being defined.

COMPETITIVE_PARTY_SYNTAX == TRUE

If you are using competitive syntax parties, navigate to the trainer data in src/data/trainers.party, find the trainer you’d like to change, and add flags like so: AI: Check Bad Move / Try to Faint / Check Viability. The name of each flag is just the constant, but without AI_FLAG at the beginning. For example, to add AI_FLAG_SEQUENCE_SWITCHING, any of the following will work:

  • AI_FLAG_SEQUENCE_SWITCHING
  • SEQUENCE_SWITCHING
  • SEQUENCE SWITCHING
  • Sequence_Switching
  • Sequence Switching

COMPETITIVE_PARTY_SYNTAX != TRUE / Not Found

If you are not using competitive syntax parties, instead access the trainer data directly in src/data/trainers.h, and add flags like so, typed exactly the same as the flag names themselves: .aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY

What AI Flags does pokeemerald-expansion have?

This section lists all of expansion’s AI Flags and briefly describes the effect they have on the AI’s behaviour. In all cases, please check the corresponding function or surrounding code around their implementation for more details. Some of these functions are vanilla, some share a name with vanilla but have been modified to varying degrees, and some are completely new.

Composite AI Flags

Expansion has a few "composite" AI flags. This means that these flags have no unique functionality themselves, and can instead be thought of as groups of other flags that are all enabled when this flag is enabled. The idea behind these flags is that if you don't care to manage the detailed behaviour of a particular trainer, you can use these as a baseline instead, and expansion will keep them updated for you.

AI_FLAG_BASIC_TRAINER is expansion's version of generic, normal AI behaviour. It includes AI_FLAG_CHECK_BAD_MOVE (don't use bad moves), AI_FLAG_TRY_TO_FAINT (faint the player where possible), and AI_FLAG_CHECK_VIABILITY (choose the most effective move to use in the current context). Trainers with this flag will still be smarter than they are in vanilla as there have been dramatic improvements made to move selection, but not incredibly so. Trainers with this flag should feel like normal trainers. In general we recommend these three flags be used in all cases, unless you specifically want a trainer who makes obvious mistakes in battle.

AI_FLAG_SMART_TRAINER is expansion's version of a "smart AI". It includes everything in AI_FLAG_BASIC_TRAINER along with AI_FLAG_SMART_SWITCHING (make smart decisions about when to switch), AI_FLAG_SMART_MON_CHOICES (make smart decisions about what mon to send in after a switch / KO), and AI_FLAG_OMNISCIENT (awareness of what moves, items, and abilities the player's mons have to better inform decisions). Expansion will keep this updated to represent the most objectively intelligent behaviour our flags are capable of producing.

AI_FLAG_PREDICTION will enable all of the prediction flags at once, so the AI can perform as well as possible. It is best paired with the flags in AI_FLAG_SMART_TRAINER for optimal behaviour. This currently includes AI_FLAG_PREDICT_SWITCH and AI_FLAG_PREDICT_INCOMING_MON, but will likely be expanded in the future.

Expansion has LOADS of flags, which will be covered in the rest of this guide. If you don't want to engage with detailed trainer AI tuning though, you can just use these two composite flags, and trust that expansion will keep their contents updated to always represent the most standard and the smartest behaviour we can.

AI_FLAG_CHECK_BAD_MOVE

The AI will avoid using moves that are likely to fail in the current situation. This flag helps prevent the AI from making ineffective choices, such as using moves into immunities, into invulnerable states, or when the moves are otherwise hindered by abilities, terrain, or status conditions.

AI_FLAG_TRY_TO_FAINT

AI will prioritize KOing the player if able rather than using status moves. Will prioritize using a move that can OHKO the player. If the player can KO the AI’s mon and the AI’s mon is slower, prioritize priority moves (this does not prevent the AI from switching out instead).

This flag handles scoring for OHKOs but does not handle 2HKOs at all, AI_FLAG_STRONGEST_MOVE should be used for 2HKO scoring.

AI_FLAG_CHECK_VIABILITY

This flag is divided into two components to calculate the best available move for the current context:

  • AI_CompareDamagingMoves: This function compares damaging moves against each other and picks the best one.
  • AI_CalcMoveEffectScore: This function checks every move effect (status or damaging move effect) and increases the score accordingly.

This is different to AI_FLAG_CHECK_BAD_MOVE as it calculates how poor a move is and not whether it will fail or not.

AI_FLAG_FORCE_SETUP_FIRST_TURN

AI will prioritize using setup moves on the first turn at the expense of all else. These include stat buffs, field effects, status moves, etc. AI_FLAG_CHECK_VIABILITY will instead do this when the AI determines it makes sense.

This is just a flat increase without any consideration of whether it makes sense to use the move or not. For better move choice quality for those moves, AI_FLAG_CHECK_VIABILITY should be used.

AI_FLAG_RISKY

AI will generally behave more recklessly. This AI enables the following behaviour:

  • Always assume the highest damage roll when scoring moves
  • Blindly Mirror Coat / Counter based on the player mon’s species higher attacking stat
  • Moves with Recoil if they miss are not treated differently even if accuracy is lowered
  • Prioritize maximizing damage from moves at the cost of accuracy
  • Prioritize moves with low change strong effects (Ancient Power etc., check AI_Risky function for full list)
  • Switch offensively mid battle rather than defensively (if using AI_FLAG_SMART_MON_CHOICES)
  • Prioritize Explosion moves

AI_FLAG_TRY_TO_2HKO

Adds score bonus to any move the AI has that either OHKOs or 2HKOs the player.

Keep in mind that this is a weaker form of AI_FLAG_TRY_TO_FAINT at scoring OHKOs as it does not take into account who is attacking first, it does however handle 2HKOs.

AI_FLAG_PREFER_BATON_PASS

AI prefers raising its own stats if it has >= 60% HP, as well as Ingrain, Aqua Ring, and Protect. Prioritizes Baton Bass if the mon is rooted (Ingrain) or has the Aqua Ring effect, and doesn’t if it has been Leech Seeded.

AI_FLAG_DOUBLE_BATTLE

This flag is automatically set in double battles, and controls much of the doubles-specific scoring. I’ll summarize some of its scoring as follows:

  • Don’t use Helping Hand if partner is, don’t Perish Trap your partner, don’t change the weather if they are, don’t buff stats if partner will trigger Anger Point for us
  • Collaborate with partner to Perish Trap opponent, Magnet Rise to protect partner, Dragon Cheer partner if applicable
  • Prioritize using weather move if it benefits partner
  • Prioritize triggering partner’s good abilities if possible (Motor Drive, Storm Drain, Beat Up -> Justified, etc.)
  • Handle Skill Swap smartly, both with the partner and against the player

AI_FLAG_HP_AWARE

Lets the AI make decisions based on how much remaining HP its mon(s) and the player’s mon(s) have.

With respect to the AI’s mons, in doubles:

  • Allows the AI to attack its partner with a move it can absorb if its low on HP (ie. Electric move on partner with Volt Absorb)
  • Prioritizes healing its partner if its HP is <= 50% if able

In both singles and doubles:

  • Prioritizes not using moves that require the user fainting (Destiny Bond, Explosion etc.) and healing moves while on >= 70% HP.
  • Prioritize not using moves that require the user fainting or losing significant HP (Belly Drum etc) while between 30% and 70% HP
  • Prioritize not using setup moves (Light Screen etc.) and Bide while on <= 30% HP

With respect to the player’s mons:

  • Prioritize not using many status moves (stat buffs, Poison, Pain Split) if the player has between 30% and 70% HP
  • Prioritize not using any status moves if the player is has <= 30% HP

AI_FLAG_POWERFUL_STATUS

AI prioritizes setting up field effects (Trick Room, Rain Dance, etc.) and side statuses (Tailwind, Spikes, etc.), even if it could faint the target.

AI_FLAG_NEGATE_UNAWARE

AI does not understand ability suppression (Mold Breaker etc., weather suppression (Air Lock etc.), redirection abilities (Lightningrod etc.) being temporarily removed due to move effects (Sky Drop etc.), or item suppression (Magic Room etc.) and will ignore them. This is a handicap flag.

AI_FLAG_WILL_SUICIDE

AI prioritizes self destruction moves (Explosion, Memento).

AI_FLAG_PREFER_STATUS_MOVES

AI gets a score bonus for status moves. This should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves.

AI_FLAG_STALL

AI prefers simple classically "stalling" behaviour. It will prioritize:

  • Mean Look, Fairy Lock, and Wrap for trapping
  • Increasing its defense and special defense
  • Moves that inflict Poison if it also has a Protect move
  • Copying defense and special defense buffs

AI_FLAG_SMART_SWITCHING

Affects when the AI chooses to switch. AI will make smarter decisions about when to switch out mid-battle. Automatically enables AI_FLAG_SMART_MON_CHOICES, which is required as the vanilla mon selection AI is not smart enough to handle several switch-triggering situations appropriately, leading to bizarre behaviour. Many of these checks have intentional failure rates, so the AI won’t switch out 100% of the time in these cases to keep the player from being able to predict perfectly. Some of these also only apply to singles, and many of them are being simplified for the sake of brevity. This flag lets the AI trigger switches when:

  • It can’t hit Wonder Guard and has another mon in the party that can (switch that mon in)
  • It’s going to die to Perish Song, can’t KO the player and is affected by Yawn, is being severely affected by a status condition that switching helps (Curse, Toxic, Leech Seed)
  • It has a mon that can trap the player’s mon and win the 1v1 (switch that mon in)
  • It has a mon in the party that can absorb the player’s next expected attack (switch that mon in)
  • It will not switch if the current mon will die to hazards on re-entry and it has no means of clearing them in its party
  • All its moves are bad
  • It can take advantage of Natural Cure or Regenerator
  • Its Encore’d into something bad
  • Its primary attacking stats are sufficiently lowered
  • Its "odds are bad", which is a generic "try to make smart, player-like decisions generally speaking" check. Switches can be triggered if the player has a good switchin candidate (AI_FLAG_SMART_MON_CHOICES), and:
  • The current mon has a bad type matchup and doesn’t have a super effective move and has at least ½ HP, or ¼ HP and Regenerator, or
  • The current mon loses the 1v1 quickly and has at least ½ HP, or ¼ and Regenerator

AI_FLAG_ACE_POKEMON

Marks the last Pokemon in the party as the Ace Pokemon. It will not be used unless it is the last one remaining, or is forced to be switched in (Roar, U-Turn with 1 mon remaining, etc.). If you are challenged by two different trainers at the same time, only the ones with this flag will have Ace Pokémon. For example vs one trainer with AI_FLAG_ACE_POKEMONand the other without, there will be a total of 1 Ace Pokémon.

AI_FLAG_DOUBLE_ACE_POKEMON

Marks the last two Pokémon in the party as Ace Pokémon, with the same behaviour as AI_FLAG_ACE_POKEMON. Intented for double battles where you battle one trainer id that represents two trainers, ie Twins, Couples. If you apply this flag to trainers outside of double battles or in cases where two trainers can challenge you at the same time, it has the same behaviour. For example vs two trainers with AI_FLAG_DOUBLE_ACE_POKEMON there will be a total of 4 Ace Pokémon.

AI_FLAG_OMNISCIENT

AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions.

AI_FLAG_SMART_MON_CHOICES

Affects what the AI chooses to send out after a switch. AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are handled separately. Automatically included when AI_FLAG_SMART_SWITCHING is enabled.

With this flag enabled, the AI will prioritize choosing mons after a KO prioritizing the following criteria:

  • Trapper (can trap the player’s mon and win the 1v1)
  • Revenge killer (outspeeds an OHKOs / is outsped and OHKOs, is not OHKOd/ outspeeds and 2HKOs, is not OHKOd / is outsped and 2HKOs, is not 2HKOd)
  • Has good type matchup and a super effective move
  • Has good type matchup and does not have a super effective move
  • Has Baton Pass
  • If no mons meet any of the above criteria, choose the one that does the most damage

And will choose mons after a mid-battle switch prioritizing the following criteria:

  • Trapper (can trap the player’s mon and win the 1v1)
  • Has good type matchup and a super effective move
  • Has good type matchup and does not have a super effective move
  • Is not 3HKO’d by the player
  • Has Baton Pass

AI_FLAG_CONSERVATIVE

AI always assumes it will roll the lowest possible result when comparing damage in scoring.

AI_FLAG_SEQUENCE_SWITCHING

AI will always switch out after a KO in exactly party order as defined in the trainer data (ie. slot 1, then 2, then 3, etc.). The AI will never switch out mid-battle unless forced to (Roar etc.). If the AI uses a move that requires a switch where it makes a decision about what to send in (U-Turn etc.), it will always switch out into the lowest available party index.

AI_FLAG_WEIGH_ABILITY_PREDICTION

AI will predict the player's ability based to its aiRating. Without this flag the AI randomly assumes an ability with an even distribution between all possible abilities until one is confirmed. With this flag, it instead guesses proportionally to each ability's aiRating, making it far more likely to guess an ability like Water Absorb than Damp if both are options.

AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE

AI will add score to its highest damaging move, regardless of accuracy or secondary effects. Replaces deprecated AI_FLAG_PREFER_STRONGEST_MOVE.

AI_FLAG_PREDICT_SWITCH

AI will determine whether it would switch out in the player's situation or not, and predict the player to switch accordingly. In any case where the AI would consider switching, it will assume the player will switch. This is modulated by a 50% failure rate, so the behaviour is non-deterministic and can change from turn to turn to emulate the inconsistency in human predictions. This behaviour is improved significantly by using AI_FLAG_SMART_SWITCHING and AI_FLAG_SMART_MON_CHOICES as they improve the AI's ability to determine good situations to switch, and also by AI_FLAG_OMNISCIENT so the AI can use all its knowledge of the player's team to make the decision.

AI_FLAG_PREDICT_INCOMING_MON

This flag requires AI_FLAG_PREDICT_SWITCH to function. If the AI predicts that the player will switch, this flag allows the AI to run its move scoring calculation against the Pokémon it expects the player to switch into, instead of the Pokémon that it expects to switch out.

AI_FLAG_PREDICT_MOVE

AI will predict what move the player is going to use based on what move it would use in the same situation. Generally works best if also using AI_FLAG_OMNISCIENT.

AI_FLAG_PP_STALL_PREVENTION

This flag aims to prevent the player from PP stalling the AI by switching between immunities. The AI mon's move scores will slowly decay for absorbed moves over time, eventually making its moves unpredictable. More detailed control for this behaviour can be customized in the ai.h config file.

How to add new AI Flags

The battle engine upgrade has rewritten the AI battle scripts to C functions to easily add new logic. This tutorial explains how to add a new AI logic flag.

1. Define your flag

Open include/constants/battle_ai.h. We have many unused flags, but you can add a new one after AI_FLAG_SMART_SWITCHING like so:

#define AI_FLAG_SUPPORT (1 << 16)

2. Make your new function

Open src/battle_ai_main.c. Search for the array static s16 (*const sBattleAiFuncTable[])(u8, u8, u16, s16). We want to add our new function to this table. Since we have defined our flag as (1 << 16), find the 16th entry in the table (identifiable by the initializer, [16]), and replace it with:

[16] = AI_Support, // AI_FLAG_SUPPORT

Define your function above the table as static s16 AI_Support(u8 battlerAtk, u8 battlerDef, u16 move, s16 score);

Make your function do something

at the bottom of the file, add:

static s16 AI_Support(u8 battlerAtk, u8 battlerDef, u16 move, s16 score)
{
    // Add your logic here!
}

Give your trainer the correct AI flag!

And that's it!

How to add new Battle Script Commands/Macros

To preface this tutorial, the battle engine upgrade has exhausted all battle script command IDs. Historically, we've used the various command to effectively add new commands. However, this has caused issues of maintainability and readability due to the massive switch needed for it. Thanks to the cleanup made by the team and contributors, we now are able to call an infinite amount of commands by using callnative. This is preferential to creating a secondary battle script command table like is done in the CFRU.

In general, gBattlescriptCurrInstr tracks the current battle script position as a ROM address. Fortunately, we don't need to worry about ROM addresses when using the decomps, but it is important to understand because of how the callnative command is set up.

	.macro callnative func:req
	.byte 0xff
	.4byte \func
	.endm

callnative uses the last battle script command ID in order to pass a native function as an argument. Additional optional arguments are added recursively via a macro, so no need to worry about how they need to align to the amount of instructions to skip.

Now, how might we add a custom callnative command? Here are the steps. We will use BS_TrySetOctolock as an example.

1. Create a macro in asm/macros/battle_script.inc. For example:

	.macro trysetoctolock battler:req, failInstr:req
	callnative BS_TrySetOctolock
	.byte \battler
	.4byte \failInstr
	.endm

2. Add your new callnative command ID to src/battle_script_commands.c. For example:

void BS_TrySetOctolock(void)
{
    NATIVE_ARGS(u8 battler, const u8 *failInstr);
    u32 battler = GetBattlerForBattleScript(cmd->battler);

    if (gDisableStructs[battler].octolock)
    {
        gBattlescriptCurrInstr = cmd->failInstr;
    }
    else
    {
        gDisableStructs[battler].octolock = TRUE;
        gBattleMons[battler].status2 |= STATUS2_ESCAPE_PREVENTION;
        gDisableStructs[battler].battlerPreventingEscape = gBattlerAttacker;
        gBattlescriptCurrInstr = cmd->nextInstr;
    }
}

Each of the arguments defined in the macro (battler, failInstr) need to be called at the start of the command using NATIVE_ARGS. The byte count in the macro should correspond to the type that will be used for the command (eg, u8 is byte, while the pointer are 4byte). These arguments can then be accessed as cmd->battler and cmd->battler. gBattlescriptCurrInstr = cmd->nextInstr; advances to the next instruction.

Full credits and thank you to CancerFairy for writing this guide!

Note: This guide was written for version 1.8.0. Most stuff still applies to 1.7.x versions and earlier, with the following exceptions:

  • Battle and Contest move data are separated in src/data/battle_moves.h and src/data/contest_moves.h
  • additionalEffects doesn't exist, instead being handled by a combination of secondaryEffectChance and unique EFFECT_xxxs.
  • There's no include/constants/battle_move_effects.h, so data specific to certain effects is handled in other places.
  • Move names are handled in gMoveNames.

Adding/editing moves

This guide is here to give you a breakdown of how moves work, how to edit existing ones, and how to add your own.

Contents:

  1. Key files and definitions
  2. Editing a move
  3. Adding a new move

Key files and definitions

Before beginning the process, it's important to familiarise yourself with the important files that control moves. There are three categories of files - header(.h) files, which contain static information about a move, .c files which contains functions in C that determine how the move behaves, and script files (.s or .inc) that actually "run" the move - i.e. determine the sequence of events you see on screen when you execute the move.

Header files

src/data/moves_info.h

This is the place where the bulk of move information is stored, including name, base power, typing, PP, contest information etc.

Let's look at an example:

[MOVE_THUNDER_SHOCK] =
{
    .name = COMPOUND_STRING("Thunder Shock"),
    .description = COMPOUND_STRING(
        "An electrical attack that\n"
        "may paralyze the foe."),
    .effect = EFFECT_HIT,
    .power = 40,
    .type = TYPE_ELECTRIC,
    .accuracy = 100,
    .pp = 30,
    .target = MOVE_TARGET_SELECTED,
    .priority = 0,
    .category = DAMAGE_CATEGORY_SPECIAL,
    .sheerForceBoost = TRUE,
    .additionalEffects = ADDITIONAL_EFFECTS({
        .moveEffect = MOVE_EFFECT_PARALYSIS,
        .chance = 10,
    }),
    .contestEffect = CONTEST_EFFECT_HIGHLY_APPEALING,
    .contestCategory = CONTEST_CATEGORY_COOL,
    .contestComboStarterId = 0,
    .contestComboMoves = {COMBO_STARTER_CHARGE},
},

Most of the fields here are obvious, but the two important ones for determining what a move actually does are effect and additionalEffects.

The effect represents how the move actually works when called in battle - it can be a two turn move, or a move that only works if the target is holding an item, for example. How each effect works is pretty much unique, but the way a move of a particular effect is executed is defined by a script data/battle_scripts_1.s, and any variable characteristics such as typing or power are defined in either src/battle_script_commands.c or src/battle_util.c, depending on the effect. The vast majority of non-status moves are simply EFFECT_HIT, in that they deal damage and apply additionalEffects (if defined).

The additionalEffects field represents effects that are applied at the setadditionaleffects stage of the move script (for most moves, see BattleScript_Hit_RetFromAtkAnimation). These are effects that can be encapsulated by any of the MOVE_EFFECT_X defined in include/constants/battle.h and encoded under SetMoveEffect in src/battle_script_commands.c. These can vary from applying a status, such as MOVE_EFFECT_PARALYSIS, or lowering/raising stats etc. The move effect could target the user by setting self = TRUE, such as Overheat lowering the user's own Sp. Atk. What's more, definining a chance, such as for Thunder Shock, not only limits the effect to applying only chance% of the time, but it also turns it into a secondary effect. This difference is important because secondary effects are nullified by Sheer Force (which in turn will boost the move's power) and they are blocked by Shield Dust. These two limitations do not apply to primary effects which do not a chance field defined and by definition will always happen when the move is executed.

src/data/battle_move_effects.h

Effects are listed here along with the battleScript that governs each one. Said scripts are defined in data/battle_scripts_1.s. The indices/names of the effects (e.g. EFFECT_FIRST_TURN_ONLY) are enums defined in include/constants/battle_move_effects.h.

include/battle_scripts.h

Contains references to scripts data/battle_scripts_1.s, allowing them to be referenced in C. Any new scripts must be added here.

include/constants/battle_move_effects.h

Simply an enum list of possible effects for moves. Any new effects would be added here, with a definition for them (including defining a script) would then also be added to src/data/battle_move_effects.h.

include/constants/battle_string_ids.h

All strings that can be printed in battle have an id that is defined here. The actual message itself would then be defined and assigned to this id in src/data/battle_message.c.

include/constants/battle.h

A whole range of constants defining battle variables, such as statuses, weather, and move effects.

include/constants/moves.h

Where moves are defined (and nothing else).

Note: When adding custom moves, you should add them between the moves from the latest generation and the z moves, then adjust MOVES_COUNT accordingly. Adding a move after MOVES_COUNT that is neither a Max Move or a Z Move will result in that move's name not being printed when it is used, instead a generic message will be printed.

C files

src/battle_script_commands.c

This is where a lot of the commands referred to in scripts are defined. For example, the jumpifnotfirstturn command above is defined by the function Cmd_jumpifnotfirstturn and you can see how it works in C. It's possible that any move editing or updating you have in mind can be done with existing commands, but if you wanted to add a new function that could be called in a script above, this is where you would define it.

src/battle_util.c

This contains a lot of the "utility" functions used to determine things like a move's dynamic typing or power. It's also where damage calculation takes place, and a lot of that will naturally take a move's effect into account. For example, a move with the effect EFFECT_SOLAR_BEAM would have its damage halved in sandstorm. If you wanted to add a move with an effect which gave it variable BP or typing, this is the file you would encode that effect.

src/battle_message.c

Contains string defines and functions that print messages during the battle. If you wish to add or edit a move's string, then this is where you would do so.

src/battle_main.c

Contains more fundamental functions that control the flow of the battle. Functions here determine move order, dynamic typing, animations, priority, speed calculations and more.

Script files

data/battle_scripts_1.s

Each move's effect is governed by a script defined here. For a simple example, let's look at the script for Fake Out/First Impression:

BattleScript_EffectFirstTurnOnly::
	attackcanceler
	jumpifnotfirstturn BattleScript_FailedFromAtkString
	goto BattleScript_EffectHit

attackcanceler is a command that covers all the cases that could cause a move to fail before it's even attempted (e.g. paralysis). And as we can tell from the commands, if it's not the first turn, we go to BattleScript_FailedFromAtkString which evidently causes us to print the attackstring ("POKEMON used MOVE") then fail ("But it failed!"). Otherwise, we go to the generic "hit" effect which is the same script for moves that just deal damage and nothing else.

This is the most advanced part of the ROM. There are dozens upon dozens of commands and hundreds of scripts so this guide would go on forever if I were to go into more detail. To learn how these scripts work, it's best to look at a few examples of moves you know.

asm/macros/battle_script.inc

The "link" between data/battle_scripts_1.s and src/battle_script_commands.c. Each command is represented by a hex byte which represents its index in the gBattleScriptingCommandsTable array at the top of src/battle_script_commands.c. However, this file also contains macros which perform combinations of other commands, or just calculations in assembly. In addition to commands, it is also possible to call functions in src/battle_script_commands.c using the various (now gradually being deprecated) and the callnative functionality. The various macros will point to a case under the Cmd_various function in src/battle_script_commands.c, whereas callnative will let you directly call a function in src/battle_script_commands.c by name.

data/battle_anim_scripts.s

This is the place where move animations are defined. The array at the top, gBattleAnims_Moves, is in move index order and determines which animation goes with which move.

Editing a move

Basic information

To edit a move's basic information, you need only edit the relevant fields in src/data/battle_moves.h. This will let you change a move's:

  • name
  • description
  • power
  • accuracy
  • type
  • category
  • target
  • pp
  • recoil percentage
  • flags
  • Z-move effect (for status moves) or overwritting its calculated power (for damaging moves)

Changing a move's main effect

To change the main effect of a move to an existing effect, you need only change its effect field to one of the options in src/data/battle_move_effects.h. If you wish to keep the effect but simply modify how it works, you can modify how it plays out on screen by editing its entry in data/battle_scripts_1.s and any relevant functions in src/battle_script_commands.c. To change how a move's dynamic power, accuracy and are calculated, then you need to modify the following functions:

Note: A generic function for calculating category does not currently exist - Photon Geyser's script in data/battle_scripts_1.s uses a special callnative function BS_SetPhotonGeyserCategory.

Changing a move's additional effects

If you look at the example here, you can see that Thunder Shock has an additional effects array that contains a single move effect MOVE_EFFECT_PARALYSIS with a 10% chance of applying. Thanks to this field, you can add and remove primary and secondary effects (so long as they are defined by a MOVE_EFFECT) to a move without having to change its effect or script. You can also make an effect apply to the attacker rather than the target (for, say, a stat boost) with .self = TRUE and you can set the probability to whatever you want with the chance field.

All additional effects with a defined chance (even 100%) are treated as "secondary effects". This means that they are nullified by Sheer Force, blocked by Shield Dust or the Covert Cloak, and have their chance modified by Serene Grace. Additional effects without a chance field (effectively setting it to 0) are treated as "primary effects", which means that they cannot be blocked by the aforementioned items and abilities and their chance to occur cannot be modified; they will always happen.

Each move can have up to 15 additional effects, allowing you to construct monstrosities like this:

[MOVE_POUND] =
{
    .name = COMPOUND_STRING("Pound"),
    .description = COMPOUND_STRING(
        "Pounds the foe with\n"
        "forelegs or tail."),
    .effect = EFFECT_HIT,
    .power = 40,
    .type = TYPE_NORMAL,
    .accuracy = 100,
    .pp = 35,
    .target = MOVE_TARGET_SELECTED,
    .priority = 0,
    .category = DAMAGE_CATEGORY_PHYSICAL,
    .additionalEffects = ADDITIONAL_EFFECTS({
        .moveEffect = MOVE_EFFECT_PARALYSIS,
        .chance = 10,
    },{
        .moveEffect = MOVE_EFFECT_CONFUSION,
        .chance = 100,
    },{
        .moveEffect = MOVE_EFFECT_FLINCH,
        .chance = 30,
    },{
        .moveEffect = MOVE_EFFECT_ALL_STATS_UP,
        .chance = 40,
        .self = TRUE,
    },{
        .moveEffect = MOVE_EFFECT_DEF_MINUS_2,
        .chance = 50,
    }),
    .makesContact = TRUE,
    .ignoresKingsRock = B_UPDATED_MOVE_FLAGS == GEN_4,
    .contestEffect = CONTEST_EFFECT_HIGHLY_APPEALING,
    .contestCategory = CONTEST_CATEGORY_TOUGH,
    .contestComboStarterId = COMBO_STARTER_POUND,
    .contestComboMoves = {0}
},

Note: at the moment, additional effects can only be used by damaging moves, not by status moves.

Adding a new move

To add a new move, you need to create an entry in three locations:

And that's it! You can use an existing animation or effect for your move - or you can add your own, but I'll leave figuring that out to you.

How to add a new trainer class

This is a modified version of the original tutorial about adding new Pokémon species available in Pokeemerald's wiki.

Despite the persistent rumors about an incredibly strong third form of Mew hiding somewhere, it actually wasn't possible to catch it... OR WAS IT? In this tutorial, we will add a new Pokémon species to the game.

IMPORTANT: This tutorial applies to 1.7.x versions onward.

Changes compared to vanilla

The main things that the Expansion changes are listed here.

  • Still Front Pics (gMonStillFrontPic_YourPokemon) and by extension src/anim_mon_front_pics.c have been removed.
  • src/data/pokemon/cry_ids.h doesn't exist anymore.
  • You have 6 icon palettes available instead of the base 3.
  • Most tables that use SPECIES_x as indexes have been moved to gSpeciesInfo.

Content

Useful resources

You can open a sprite debug menu by pressing Select in a Pokémon's summary screen outside of battle.

visualizer1

The Data - Part 1

Our plan is as simple as it is brilliant: clone Mewtwo... and make it even stronger!

1. Declare a species constant

Our first step towards creating a new digital lifeform is to define its own species constant.

Edit include/constants/species.h:

 #define SPECIES_NONE                                    0
 #define SPECIES_BULBASAUR                               1
 ...
 #define SPECIES_MIMIKYU_BUSTED_TOTEM                    1523
 #define SPECIES_MIMIKYU_TOTEM_BUSTED                    SPECIES_MIMIKYU_BUSTED_TOTEM
+#define SPECIES_MEWTHREE                                1524

-#define SPECIES_EGG                                     (SPECIES_MIMIKYU_BUSTED_TOTEM + 1)
+#define SPECIES_EGG                                     (SPECIES_MEWTHREE + 1)

 #define NUM_SPECIES SPECIES_EGG

This number is stored in a Pokémon's save structure. These should generally never change, otherwise your saved Pokémon species will change as well.

We add this at the end so that no existing species change Id and so that we don't have to renumber everything after it.

Now, let's see how it looks in-game!

visualizer2

Hmmm, something's not right...

Oh, I know! We need to add the rest of the data! Normally, the vanilla game would crash if we try to look up anything about Mewthree in this state, but the expansion defaults all of its data to SPECIES_NONE.

Now, let's see what needs to be done.

2. SpeciesInfo's structure

Now, to better understand Mewthree, we also need to understand Mew. Let's look at its data.

    [SPECIES_MEW] =
    {
        .baseHP        = 100,
        .baseAttack    = 100,
        .baseDefense   = 100,
        .baseSpeed     = 100,
        .baseSpAttack  = 100,
        .baseSpDefense = 100,
        .types = MON_TYPES(TYPE_PSYCHIC),
        .catchRate = 45,
    #if P_UPDATED_EXP_YIELDS >= GEN_8
        .expYield = 300,
    #elif P_UPDATED_EXP_YIELDS >= GEN_5
        .expYield = 270,
    #else
        .expYield = 64,
    #endif
        .evYield_HP = 3,
        .itemCommon = ITEM_LUM_BERRY,
        .itemRare = ITEM_LUM_BERRY,
        .genderRatio = MON_GENDERLESS,
        .eggCycles = 120,
        .friendship = 100,
        .growthRate = GROWTH_MEDIUM_SLOW,
        .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED),
        .abilities = { ABILITY_SYNCHRONIZE, ABILITY_NONE, ABILITY_NONE },
        .bodyColor = BODY_COLOR_PINK,
        .speciesName = _("Mew"),
        .cryId = CRY_MEW,
        .natDexNum = NATIONAL_DEX_MEW,
        .categoryName = _("New Species"),
        .height = 4,
        .weight = 40,
        .description = COMPOUND_STRING(
            "A Mew is said to possess the genes of all\n"
            "Pokémon. It is capable of making itself\n"
            "invisible at will, so it entirely avoids\n"
            "notice even if it approaches people."),
        .pokemonScale = 457,
        .pokemonOffset = -2,
        .trainerScale = 256,
        .trainerOffset = 0,
        .frontPic = gMonFrontPic_Mew,
        .frontPicSize = P_GBA_STYLE_SPECIES_GFX ? MON_COORDS_SIZE(40, 40) : MON_COORDS_SIZE(64, 48),
        .frontPicYOffset = P_GBA_STYLE_SPECIES_GFX ? 13 : 9,
        .frontAnimFrames = ANIM_FRAMES(
            ANIMCMD_FRAME(1, 50),
            ANIMCMD_FRAME(1, 40),
            ANIMCMD_FRAME(0, 10),
        ),
        .frontAnimId = P_GBA_STYLE_SPECIES_GFX ? ANIM_SWING_CONVEX : ANIM_ZIGZAG_SLOW,
        .enemyMonElevation = P_GBA_STYLE_SPECIES_GFX ? 8 : 11,
        .backPic = gMonBackPic_Mew,
        .backPicSize = P_GBA_STYLE_SPECIES_GFX ? MON_COORDS_SIZE(48, 48) : MON_COORDS_SIZE(64, 64),
        .backPicYOffset = P_GBA_STYLE_SPECIES_GFX ? 8 : 0,
        .backAnimId = BACK_ANIM_CONCAVE_ARC_SMALL,
        .palette = gMonPalette_Mew,
        .shinyPalette = gMonShinyPalette_Mew,
        .iconSprite = gMonIcon_Mew,
        .iconPalIndex = 0,
        SHADOW(0, 13, SHADOW_SIZE_S)
        FOOTPRINT(Mew)
        OVERWORLD(
            sPicTable_Mew,
            SIZE_32x32,
            SHADOW_SIZE_M,
            TRACKS_NONE,
            sAnimTable_Following,
            gOverworldPalette_Mew,
            gShinyOverworldPalette_Mew
        )
        .isMythical = TRUE,
        .isFrontierBanned = TRUE,
        .perfectIVCount = LEGENDARY_PERFECT_IV_COUNT,
        .levelUpLearnset = sMewLevelUpLearnset,
        .teachableLearnset = sMewTeachableLearnset,
    },

That's a lot of stuff! But don't worry, we'll go through it step by step throughout the tutorial (and it's miles better than having this same data through 20+ files like it used to be).

Across the species files you'll see preprocessor instructions such as #if/endif P_FAMILY_MEW. These are used by expansion in order to allow users to disable species via config. Since we're making a new species from scratch, you DON'T need to add them as part of the process.

You can also ignore switch cases for P_GBA_STYLE_SPECIES_GFX, as those are only used to switching to GBA-styled sprites.

We'll start by adding the self-explanatory data that's also present in pret's vanilla structure:

3. Define its basic species information

Edit src/data/pokemon/species_info.h:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     [SPECIES_NONE] = {0},
     ...

     [SPECIES_EGG] =
     {
         FRONT_PIC(Egg, 24, 24),
         .frontPicYOffset = 20,
         .backPic = gMonFrontPic_Egg,
         .backPicSize = MON_COORDS_SIZE(24, 24),
         .backPicYOffset = 20,
         .palette = gMonPalette_Egg,
         .shinyPalette = gMonPalette_Egg,
         ICON(Egg, 1),
     },

+    [SPECIES_MEWTHREE] =
+    {
+       .baseHP        = 106,
+       .baseAttack    = 150,
+       .baseDefense   = 70,
+       .baseSpeed     = 140,
+       .baseSpAttack  = 194,
+       .baseSpDefense = 120,
+       .types = MON_TYPES(TYPE_PSYCHIC),
+       .catchRate = 3,
+       .expYield = 255,
+       .evYield_SpAttack  = 3,
+       .genderRatio = MON_GENDERLESS,
+       .eggCycles = 120,
+       .friendship = 0,
+       .growthRate = GROWTH_SLOW,
+       .eggGroups = MON_EGG_GROUPS(EGG_GROUP_NO_EGGS_DISCOVERED),
+       .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_NONE },
+       .bodyColor = BODY_COLOR_PURPLE,
+    },
 };

The . is the structure reference operator in C to refer to the member object of the structure SpeciesInfo.

  • baseHP, baseAttack, baseDefense, baseSpeed, baseSpAttack and baseSpDefense are the base stats. They can't go higher than 255.
  • types is using the macro MON_TYPES as a helper function for formatting so that only one type has to be input for species with a single type.
    • To add a species with 2 types, use the format MON_TYPES(TYPE_PSYCHIC, TYPE_NORMAL).
    • 1.9 and earlier: The format for setting types is the following:
      // Mono-type
      .types = { TYPE_PSYCHIC, TYPE_PSYCHIC },
      // Dual-type
      .types = { TYPE_PSYCHIC, TYPE_DARK },
      
  • catchRate is how likely it is to catch a Pokémon, the lower the value, the harder it is to catch. Legendaries generally have a catch rate of 3, so we put that here.
  • expYield is the base amount of experience that a Pokémon gives when defeated/caught. In vanilla, this value caps at 255, but we've increased it to a maximum of 65535 accomodate later gen's higher experience yields. (The highest official value is Blissey's with 608, so going beyond this point may cause exponential gains that could break the system 😱)
    • If you noticed, Mew's had some #ifs, #elifs and #endif around it. This is because its yield has changed over time, and we let you choose which ones you want. This is not relevant to our Mewthree however, so we can just put a single .expYield = 255, line here.
  • evYield_HP, evYield_Attack, evYield_Defense, evYield_Speed, evYield_SpAttack and evYield_SpDefense are how many EVs does the Pokémon give when they're caught. Each of these fields can have a value of 3 at most. Officially, no Pokémon give out more than 3 EVs total, with them being determined by their evolution stage (eg, Pichu, Pikachu and Raichu give 1, 2 and 3 Speed EVs respectively), and they tend to be associated with its higher stats. Since our Mewthree is a Special Attack monster, we'll be consistent and make it give out 3 Special Attack EVs, but you're always free to assign whatever you feel like :)
    • Notice that the other evYield fields are not there. In C, numbers in a struct default to 0, so if we don't specify them, they'll be 0 all around! Less lines to worry about :D
  • itemCommon and itemRare are used to determine what items is the Pokémon holding when encountering it in the wild.
    • 50% for itemCommon and 5% for itemRare (boosted to 60%/20% when the first mon in the party has Compound Eyes or Super Luck)
    • If they're both set as the same item, the item has a 100% chance of appearing.
  • genderRatio is a fun one.
    • There are 4 ways of handling this
      • PERCENT_FEMALE is what most Pokémon use, where you define how likely it's gonna be female. It supports decimals, so you can put PERCENT_FEMALE(12.5) to have a 1 in 8 chance of your mon to be female.
      • MON_MALE guarantees that all mon of this species will be male (eg. Tauros)
      • MON_FEMALE guarantees that all mon of this species will be female (eg. Miltank)
      • MON_GENDERLESS makes your species genderless, unable to breed with anything but Ditto to produce eggs. Most Legendaries are this, so we'll be chosing this as Mewthree's gender ratio.
    • When working with evolution lines and don't want their genders to change after evolving, be sure that their gender ratios match their stages and evolution methods. Azurill is the only case where there's a mismatch, causing 1/3 of all Azurill to change from Female to Male.
    • You might be wondering why some species have multiple defines for their genders, like SPECIES_MEOWSTIC_(FE)MALE. This is because those species have different stats and data from each other, so they're defined internally as different forms with MON_MALE and MON_FEMALE as gender ratios. If your species evolves depending on its gender and the evolutions have different stats, be sure to apply the correct evolution method!
  • eggCycles determines how fast an egg of this species will hatch. Doesn't matter much for evolved species or those that can't lay eggs, but we add the field here just in case.
  • friendship determines the amount of friendship of the mon when you catch it. Most Pokémon use STANDARD_FRIENDSHIP, but this creature of chaos does not want to be your friend, starting with 0.
  • growthRate determines the amounts of experience required to reach each level. Go here for more info.
    • This should be consistent across evolution lines, otherwise levels could change upon evolution.
  • eggGroups are used for breed compatibility. Most Legendaries and Mythicals have the EGG_GROUP_NO_EGGS_DISCOVERED group, and so does our Mewthree. Go here for more info.
    • This is using the helper macro MON_EGG_GROUPS.
    • 1.9 and earlier: The format for setting egg groups is the following:
      // Mono-group
      .eggGroups = { EGG_GROUP_MONSTER, EGG_GROUP_MONSTER },
      // Dual-group
      .eggGroups = { EGG_GROUP_MONSTER, EGG_GROUP_MINERAL },
      
  • abilities determines the potential abilites of our species. Notice how I also set the ability to ABILITY_INSOMNIA, so our little monster doesn't even need to sleep anymore. You can find the abilities for example here include/constants/abilities.h.
    • When both slot 1 and 2 are defined as not being ABILITY_NONE, their starting ability will be decided on a coin flip using their personality. They can later be changed using an Ability Capsule.
      • Certain Pokémon such as Zygarde and Rockruff have different forms to add additional abilities. As such, they cannot be changed using an Ability Capsule (though the Zygarde Cube can change Zygarde's ability by changing them to their corresponding form)
    • The 3rd slot is for Hidden Abilities. If defined as ABILITY_NONE, it will default to Slot 1 (eg. Metapod doesn't have a Hidden Ability, but Caterpie and Butterfree do). Go here and here for more info.
      • If the array is defined as {ABILITY_1, ABILITY_2}, the Hidden Ability is set as ABILITY_NONE.
  • bodyColor is used in the Pokédex as a search filter.
  • noFlip is used in to prevent front sprites from being flipped horizontally and cause weird issues, like Clawitzer's big claw changing sides.

That's all the basic fields present in vanilla emerald, so now let's take a look at the new fields added by the expansion.

4. Species Name

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        .bodyColor = BODY_COLOR_PURPLE,
+       .speciesName = _("Mewthree"),
    },
 };

The _() underscore function doesn't really exist - it's a convention borrowed from GNU gettext to let preproc know this is text to be converted to the custom encoding used by the Gen 3 Pokemon games.

5. Define its cry

Time for audio! We first need to convert an existing audio file to the format supported by the expansion.

Most formats are supported for conversion, but for simplicity's sake, we're gonna use an mp3 file.

Now, let's copy the file to the sound/direct_sound_samples/cries folder. Once that's done, let's run the following command:

ffmpeg -i sound/direct_sound_samples/cries/mewthree.mp3 -c:a pcm_s8 -ac 1 -ar 13379 sound/direct_sound_samples/cries/mewthree.aif

This will convert your audio file to .aif, which is what's read by the compiler.

Let's add the cry to the ROM via sound/direct_sound_data.inc.

.if P_FAMILY_PECHARUNT == TRUE
	.align 2
Cry_Pecharunt::
	.incbin "sound/direct_sound_samples/cries/pecharunt.bin"
.endif @ P_FAMILY_PECHARUNT

+	.align 2
+Cry_Mewthree::
+	.incbin "sound/direct_sound_samples/cries/mewthree.bin"

Then we add the cry ID to include/constants/cries.h:

enum {
    CRY_NONE,
    ...
#if P_FAMILY_TERAPAGOS
    CRY_TERAPAGOS,
#endif //P_FAMILY_TERAPAGOS
#if P_FAMILY_PECHARUNT
    CRY_PECHARUNT,
#endif //P_FAMILY_PECHARUNT
+   CRY_MEWTHREE,
    CRY_COUNT,
};

And then link it in sound/cry_tables.inc. cry_reverse in particular is for reversed cries used by moves such as Growl. The order of these two tables should match the order of the cry IDs, otherwise they'll be shifted.

	cry Cry_Terapagos
	cry Cry_Pecharunt
+	cry Cry_Mewthree
	cry_reverse Cry_Terapagos
	cry_reverse Cry_Pecharunt
+	cry_reverse Cry_Mewthree

Lastly, we add the cry to our species entry

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        .speciesName = _("Mewthree"),
+       .cryId = CRY_MEWTHREE,
    },
 };

And let's see how it sounds in-game:

https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/4f7667db-4db9-4bfd-a8dd-ece26f09f327

Good! Our monster now has a mighty roar!

You can now delete the mp3 from the cries folder now once you made sure that the cry sounds like how you want it to.

6. Define its Pokédex entry

First, we will need to add new index constants for its Pokédex entry. The index constants are divided into the Hoenn Pokédex, which contains all Pokémon native to the Hoenn region, and the National Pokédex containing all known Pokémon, which can be received after entering the hall of fame for the first time.

Edit include/constants/pokedex.h:

// National Pokedex order
enum {
    NATIONAL_DEX_NONE,
    // Kanto
    NATIONAL_DEX_BULBASAUR,
...
    NATIONAL_DEX_PECHARUNT,
+   NATIONAL_DEX_MEWTHREE,
};
 #define KANTO_DEX_COUNT     NATIONAL_DEX_MEW
 #define JOHTO_DEX_COUNT     NATIONAL_DEX_CELEBI

#if P_GEN_9_POKEMON == TRUE
-   #define NATIONAL_DEX_COUNT  NATIONAL_DEX_PECHARUNT
+   #define NATIONAL_DEX_COUNT  NATIONAL_DEX_MEWTHREE

Do keep in mind that if you intend to add your new species to the Hoenn Dex, you'll also want to add a HOENN_DEX constant for it and give it a HOENN_TO_NATIONAL member, like this:

// Hoenn Pokedex order
enum {
    HOENN_DEX_NONE,
    HOENN_DEX_TREECKO,
...
    HOENN_DEX_DEOXYS,
+   HOENN_DEX_MEWTHREE,
};

- #define HOENN_DEX_COUNT (HOENN_DEX_DEOXYS + 1)
+ #define HOENN_DEX_COUNT (HOENN_DEX_MEWTHREE + 1)

Edit src/pokemon.c:

 const u16 sHoennToNationalOrder[NUM_SPECIES] = // Assigns Hoenn Dex Pokémon (Using National Dex Index)
 {
     HOENN_TO_NATIONAL(TREECKO),
     ...
     HOENN_TO_NATIONAL(DEOXYS),
+    HOENN_TO_NATIONAL(MEWTHREE),
 };

Now we can add the number and entry to our Mewthree:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        .cryId = CRY_MEWTHREE,
+       .natDexNum = NATIONAL_DEX_MEWTHREE,
+       .categoryName = _("New Species"),
+       .height = 15,
+       .weight = 330,
+       .description = COMPOUND_STRING(
+           "The rumors became true.\n"
+           "This is Mew's final form.\n"
+           "Its power level is over 9000.\n"
+           "Has science gone too far?"),
+       .pokemonScale = 256,
+       .pokemonOffset = 0,
+       .trainerScale = 290,
+       .trainerOffset = 2,
    },
 };

image

The values pokemonScale, pokemonOffset, trainerScale and trainerOffset are used for the height comparison figure in the Pokédex.

height and weight are specified in decimeters and hectograms respectively (which are meters and kilograms multiplied by 10, so 2.5 meters are 25 decimeters).

In Pokémon Emerald, you can sort the Pokédex by name, height or weight. Apparently, the Pokémon order is hardcoded in the game files and not calculated from their data. Therefore we have to include our new Pokémon species at the right places. While the correct position for the alphabetical order is easy to find, it can become quite tedious for height and weight, so we added comments to the listings in order help out were they should fit.

Edit src/data/pokemon/pokedex_orders.h:

 const u16 gPokedexOrder_Alphabetical[] =
 {
     ...
     NATIONAL_DEX_MEW,
+    NATIONAL_DEX_MEWTHREE,
     NATIONAL_DEX_MEWTWO,
     ...
 };

 const u16 gPokedexOrder_Weight[] =
 {
     ...
     // 72.8 lbs / 33.0 kg
     //NATIONAL_DEX_MEWTWO_MEGA_Y,
     NATIONAL_DEX_ESCAVALIER,
     NATIONAL_DEX_FRILLISH,
     NATIONAL_DEX_DURANT,
     NATIONAL_DEX_CINDERACE,
+    NATIONAL_DEX_MEWTHREE,
     //NATIONAL_DEX_PERSIAN_ALOLAN,
     NATIONAL_DEX_TOEDSCOOL,
     // 73.4 lbs / 33.3 kg
     NATIONAL_DEX_DUGTRIO,
     ...
 };

 const u16 gPokedexOrder_Height[] =
 {
     ...
     // 4'11" / 1.5m
     ...
     NATIONAL_DEX_GLIMMORA,
     NATIONAL_DEX_WO_CHIEN,
     NATIONAL_DEX_IRON_LEAVES,
     NATIONAL_DEX_IRON_BOULDER,
+    NATIONAL_DEX_MEWTHREE,
    // 5'03" / 1.6m
     ...
 };

mGBA_lUBfmFEKUx

The Graphics

We will start by copying the following files for Mew (not Mewtwo) and rename it to mewthree.

cp -r graphics/pokemon/mew/. graphics/pokemon/mewthree

We aren't copying Mewtwo's folder because he has those pesky Mega Evolutions that will get in the way of what we're doing, so our sample will need to be pure from the source.

1. Edit the sprites

Let's edit the sprites. Start your favourite image editor (I recommend Aseprite or its free alternative, Libresprite) and change anim_front.png and back.png to meet your expectations.

Make sure that you are using the indexed mode and you have limited yourself to 15 colors!

Put the RGB values of your colors into normal.pal between the first and the last color and the RGB values for the shiny version into shiny.pal. Edit footprint.png using two colors in indexed mode, black and white. Finally, edit icon.png. Note: the icon will use one of 6 predefined palettes instead of normal.pal. Open an icon sprite and load one of the palettes to find out which palette suits your icon sprite best.

2. Add the sprites to the rom

Sadly, just putting the image files into the graphics folder is not enough. To use the sprites we have to register them, which is kind of tedious. First, create constants for the file paths. You'll want to add the constants for your species after the constants for the last valid species.

Edit src/data/graphics/pokemon.h:

#if P_FAMILY_PECHARUNT
    const u32 gMonFrontPic_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/front.4bpp.lz");
    const u32 gMonPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/normal.gbapal.lz");
    const u32 gMonBackPic_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/back.4bpp.lz");
    const u32 gMonShinyPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/shiny.gbapal.lz");
    const u8 gMonIcon_Pecharunt[] = INCBIN_U8("graphics/pokemon/pecharunt/icon.4bpp");
#if P_FOOTPRINTS
    const u8 gMonFootprint_Pecharunt[] = INCBIN_U8("graphics/pokemon/pecharunt/footprint.1bpp");
#endif //P_FOOTPRINTS
#if OW_POKEMON_OBJECT_EVENTS
    const u32 gObjectEventPic_Pecharunt[] = INCBIN_COMP("graphics/pokemon/pecharunt/overworld.4bpp");
#if OW_PKMN_OBJECTS_SHARE_PALETTES == FALSE
    const u32 gOverworldPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/overworld_normal.gbapal.lz");
    const u32 gShinyOverworldPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/overworld_shiny.gbapal.lz");
#endif //OW_PKMN_OBJECTS_SHARE_PALETTES
#endif //OW_POKEMON_OBJECT_EVENTS
#endif //P_FAMILY_PECHARUNT

    const u32 gMonFrontPic_Egg[] = INCBIN_U32("graphics/pokemon/egg/anim_front.4bpp.lz");
    const u32 gMonPalette_Egg[] = INCBIN_U32("graphics/pokemon/egg/normal.gbapal.lz");
    const u8 gMonIcon_Egg[] = INCBIN_U8("graphics/pokemon/egg/icon.4bpp");

+   const u32 gMonFrontPic_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/anim_front.4bpp.lz");
+   const u32 gMonBackPic_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/back.4bpp.lz");
+   const u32 gMonPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/normal.gbapal.lz");
+   const u32 gMonShinyPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/shiny.gbapal.lz");
+   const u8 gMonIcon_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/icon.4bpp");
+   const u8 gMonFootprint_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/footprint.1bpp");

Please note that Pecharunt, the Pokémon that should be above your insertion for the time being, reads a front.png sprite instead of an anim_front.png sprite. This is because currently, Pecharunt lacks a 2nd frame. If the front sprite sheet of your species uses 2 frames, you should use anim_front.

3. Add the animations to the rom

You can define the animation order, in which the sprites will be shown. The first number is the sprite index (so 0 or 1) and the second number is the number of frames the sprite will be visible.

Version 1.11.0 or later

We add this data directly to the entry, so go to section 4.

Version 1.10.3 or earlier

Edit src/data/pokemon_graphics/front_pic_anims.h:

#if P_FAMILY_PECHARUNT
PLACEHOLDER_ANIM_SINGLE_FRAME(Pecharunt);
#endif //P_FAMILY_PECHARUNT

+static const union AnimCmd sAnim_Mewthree_1[] =
+{
+    ANIMCMD_FRAME(1, 30),
+    ANIMCMD_FRAME(0, 20),
+    ANIMCMD_END,
+};
#if P_FAMILY_PECHARUNT
SINGLE_ANIMATION(Pecharunt);
#endif //P_FAMILY_PECHARUNT
+SINGLE_ANIMATION(Mewthree);
SINGLE_ANIMATION(Egg);

You might be wondering what PLACEHOLDER_ANIM_SINGLE_FRAME is. Well, since Pecharun only has 1 frame, we use what's called a preprocessor macro to have in a single line what otherwise would've been this in the C file:

static const union AnimCmd sAnim_Pecharunt_1[] =
{
    ANIMCMD_FRAME(0, 1),
    ANIMCMD_END,
}

Instead, we can use the already established macro that does the same thing, replacing the value in parenthesis with what we want (in this case, Pecharunt):

#define PLACEHOLDER_ANIM_SINGLE_FRAME(name)     \
static const union AnimCmd sAnim_##name##_1[] = \
{                                               \
    ANIMCMD_FRAME(0, 1),                        \
    ANIMCMD_END,                                \
}

4. Linking graphic information to our Pokémon

Now that we have all the external data ready, we just need to add it to gSpeciesInfo plus the rest of the animation and graphical data that we want to use:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        .pokemonScale = 256,
        .pokemonOffset = 0,
        .trainerScale = 290,
        .trainerOffset = 2,
+       .frontPic = gMonFrontPic_Mewthree,
+       .frontPicSize = MON_COORDS_SIZE(64, 64),
+       .frontPicYOffset = 0,
+       .frontAnimFrames = ANIM_FRAMES(
+           ANIMCMD_FRAME(0, 1),
+       ),
+       .frontAnimId = ANIM_GROW_VIBRATE,
+       .frontAnimDelay = 15,
+       .enemyMonElevation = 6,
+       .backPic = gMonBackPic_Mewthree,
+       .backPicSize = MON_COORDS_SIZE(64, 64),
+       .backPicYOffset = 0,
+       .backAnimId = BACK_ANIM_CONCAVE_ARC_SMALL,
+       .palette = gMonPalette_Mewthree,
+       .shinyPalette = gMonShinyPalette_Mewthree,
        .iconSprite = gMonIcon_Mewthree,
        .iconPalIndex = 2,
+       FOOTPRINT(Mewthree)
    },
 };

Let's explain each of these:

  • frontPic:
    • Used to reference the front sprite, so in this case, we call for gMonFrontPic_Mewthree.
  • frontPicSize:
    • The two values (width and height) are used for defining the non-empty size of the front sprite, which is used in move animations. If you're unsure of the values, you can leave them both as 64.
  • frontPicYOffset:
    • Used to define what Y position the sprite sits at. This is used to set where they'd be "grounded". For the shadow, see enemyMonElevation.
  • frontAnimFrames:
    • We define our animation frame animations directly here. In version 1.10.3 and earlier, we add the reference to the table that we defined earlier here like this instead:
      +       .frontAnimFrames = sAnims_Mewthree,
      
  • frontAnimId:
  • frontAnimDelay:
    • Sets a delay in frame count between when the Pokémon appears and when the animation starts.
  • enemyMonElevation:
    • Used to determine the altitude from the ground. Any value above 0 will show a shadow under the Pokémon, to signify that they're floating.
  • backPic:
    • Used to reference the back sprite, so in this case, we call for gMonBackPic_Mewthree.
  • backPicSize:
    • The two values (width and height) are used for defining the non-empty size of the back sprite, which is used in move animations. If you're unsure of the values, you can leave them both as 64.
    • NOTE: Mew has a tarnary switch here in order to change values depending on if a config option is set for displaying th original Gen 3 sprites.
  • backPicYOffset:
    • Used to define what Y position of the back sprite. When working with the animation debug menu, we recommend aligning the back sprite to the white background, as it was designed to properyly align with the real battle layout.
  • backAnimId:
    • Like frontAnimId except for the back sprites and them being a single frame. The IDs listed here are used to represent 3 different animations that happen based on the the Pokémon's nature.
  • palette:
    • Used to reference the non-shiny palette, so in this case, we call for gMonPalette_Mewthree.
  • shinyPalette:
    • Used to reference the shiny palette, so in this case, we call for gMonShinyPalette_Mewthree.
  • iconSprite:
    • Used to reference the icon sprite, so in this case, we call for gMonIcon_Mewthree.
  • iconPalIndex:
    • Here, you can choose between the six icon palettes; 0, 1, 2, 3, 4 and 5. All of them located in graphics/pokemon/icon_palettes.
  • FOOTPRINT
    • We made this single field into a macro so that they can be ignored when P_FOOTPRINTS is set to false. It's also why we don't have an "," after calling it like the other macros (we add it as part of the macro itself).
      #if P_FOOTPRINTS
      #define FOOTPRINT(sprite) .footprint = gMonFootprint_## sprite,
      #else
      #define FOOTPRINT(sprite)
      #endif
      

*NOTE: In v1.7.x only, there were macros that set multiple of these fields. However, they were considered clunky to use, so they were removed in v1.8. - FRONT_PIC: For frontPic and frontPicSize. - BACK_PIC: For backPic and backPicSize. - PALETTES: For palette and shinyPalette. - ICON: For iconSprite and iconPalIndex.

The Data - Part 2

We're almost there just a bit left!

1. Species Flags

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        .abilities = { ABILITY_INSOMNIA, ABILITY_NONE, ABILITY_NONE },
        .bodyColor = BODY_COLOR_PURPLE,
+       .isLegendary = TRUE,
+       .perfectIVCount = LEGENDARY_PERFECT_IV_COUNT,
    },
 };

Each species flag provides properties to the species:

  • perfectIVCount (1.10 onwards):
    • Guarantees that the number of IVs specified here will be perfect.
  • isLegendary:
    • 1.10 onwards: Does nothing.
    • 1.9 and earlier: Guaranteed 3 perfect IVs for the species.
  • isMythical:
    • Is skipped during Pokédex evaluations.
      • Unless it also has the dexForceRequired flag.
    • Cannot obtain Gigantamax factor via ToggleGigantamaxFactor.
    • 1.9 and earlier: Guaranteed 3 perfect IVs for the species.
  • isUltraBeast:
    • Beast Ball's multiplier is set to x5 for this species.
      • All other ball multipliers are set to x0.1.
    • 1.9 and earlier: Guaranteed 3 perfect IVs for the species.
  • isParadox (isParadoxForm previous to 1.9):
    • 1.10 onwards: Makes it so that Booster Energy cannot be knocked off.
    • 1.9 and earlier: Does nothing.
  • isTotem:
    • 1.10 onwards: Does nothing.
    • 1.9 and earlier: Guaranteed 3 perfect IVs for the species.
  • isMegaEvolution:
    • A Mega indicator is added to the battle box indicating that they're Mega Evolved.
    • The species doesn't receive affection benefits.
    • Required when adding new Mega Evolutions.
  • isPrimalReversion:
    • A Primal Reversion indicator (Alpha or Omega for Kyogre/Groudon respectively) is added to the battle box indicating that they're Primal Reverted.
    • Required when adding new Primal Reversions.
  • isUltraBurst:
    • Required when adding new Ultra Burst forms.
  • isGigantamax:
    • Used to determine if Gigantamax forms should have their GMax moves or not.
    • Required when adding new Gigantamax forms.
  • isAlolanForm, isGalarianForm, isHisuianForm, isPaldeanForm:
    • 1.10.3 onwards: Used to determine breeding offspring from different parents based on their region.
    • 1.10.2 and earlier: Does nothing.
  • cannotBeTraded:
    • This species cannot be traded away (like Black/White Kyurem).
  • tmIlliterate:
    • This species will be unable to learn the universal TM or Tutor moves.
  • isFrontierBanned (1.9 onwards):
    • This species will be unable to enter Battle Frontier facilities. Replaces gFrontierBannedSpecies.

2. Delimit the moveset

Let's begin with the moves that can be learned by leveling up.

Append to src/data/pokemon/level_up_learnsets/gen_9.h: NOTE: You can ignore the warning at the top of the file if you're just adding moves to Pokemon.

#if P_FAMILY_PECHARUNT
static const struct LevelUpMove sPecharuntLevelUpLearnset[] = {
    LEVEL_UP_MOVE( 1, MOVE_SMOG),
    LEVEL_UP_MOVE( 1, MOVE_POISON_GAS),
    LEVEL_UP_MOVE( 1, MOVE_MEMENTO),
    LEVEL_UP_MOVE( 1, MOVE_ASTONISH),
    LEVEL_UP_MOVE( 8, MOVE_WITHDRAW),
    LEVEL_UP_MOVE(16, MOVE_DESTINY_BOND),
    LEVEL_UP_MOVE(24, MOVE_FAKE_TEARS),
    LEVEL_UP_MOVE(32, MOVE_PARTING_SHOT),
    LEVEL_UP_MOVE(40, MOVE_SHADOW_BALL),
    LEVEL_UP_MOVE(48, MOVE_MALIGNANT_CHAIN),
    LEVEL_UP_MOVE(56, MOVE_TOXIC),
    LEVEL_UP_MOVE(64, MOVE_NASTY_PLOT),
    LEVEL_UP_MOVE(72, MOVE_RECOVER),
    LEVEL_UP_END
};
#endif

+static const struct LevelUpMove sMewthreeLevelUpLearnset[] = {
+   LEVEL_UP_MOVE( 1, MOVE_CONFUSION),
+   LEVEL_UP_MOVE( 1, MOVE_DISABLE),
+   LEVEL_UP_MOVE(11, MOVE_BARRIER),
+   LEVEL_UP_MOVE(22, MOVE_SWIFT),
+   LEVEL_UP_MOVE(33, MOVE_PSYCH_UP),
+   LEVEL_UP_MOVE(44, MOVE_FUTURE_SIGHT),
+   LEVEL_UP_MOVE(55, MOVE_MIST),
+   LEVEL_UP_MOVE(66, MOVE_PSYCHIC),
+   LEVEL_UP_MOVE(77, MOVE_AMNESIA),
+   LEVEL_UP_MOVE(88, MOVE_RECOVER),
+   LEVEL_UP_MOVE(99, MOVE_SAFEGUARD),
+   LEVEL_UP_END
+};

NOTE: If P_LVL_UP_LEARNSETS is not set to something equal to GEN_9, the file to be edited will change to what's specified.

Again, we need to register the learnset in gSpeciesInfo:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        .palette = gMonPalette_Mewthree,
        .shinyPalette = gMonShinyPalette_Mewthree,
        .iconSprite = gMonIcon_Mewthree,
        .iconPalIndex = 2,
+       .levelUpLearnset = sMewthreeLevelUpLearnset,
    },
 };

Next we need to specify which moves can be taught via TM, HM, or Move Tutor.

Append to src/data/pokemon/teachable_learnsets.h:

#if P_FAMILY_PECHARUNT
static const u16 sPecharuntTeachableLearnset[] = {
    ...
    MOVE_UNAVAILABLE,
};
#endif //P_FAMILY_PECHARUNT

+static const u16 sMewthreeTeachableLearnset[] = {
+   MOVE_FOCUS_PUNCH,
+   MOVE_WATER_PULSE,
+   MOVE_CALM_MIND,
+   MOVE_TOXIC,
+   MOVE_HAIL,
+   MOVE_BULK_UP,
+   MOVE_HIDDEN_POWER,
+   MOVE_SUNNY_DAY,
+   MOVE_TAUNT,
+   MOVE_ICE_BEAM,
+   MOVE_BLIZZARD,
+   MOVE_HYPER_BEAM,
+   MOVE_LIGHT_SCREEN,
+   MOVE_PROTECT,
+   MOVE_RAIN_DANCE,
+   MOVE_SAFEGUARD,
+   MOVE_FRUSTRATION,
+   MOVE_SOLAR_BEAM,
+   MOVE_IRON_TAIL,
+   MOVE_THUNDERBOLT,
+   MOVE_THUNDER,
+   MOVE_EARTHQUAKE,
+   MOVE_RETURN,
+   MOVE_PSYCHIC,
+   MOVE_SHADOW_BALL,
+   MOVE_BRICK_BREAK,
+   MOVE_DOUBLE_TEAM,
+   MOVE_REFLECT,
+   MOVE_SHOCK_WAVE,
+   MOVE_FLAMETHROWER,
+   MOVE_SANDSTORM,
+   MOVE_FIRE_BLAST,
+   MOVE_ROCK_TOMB,
+   MOVE_AERIAL_ACE,
+   MOVE_TORMENT,
+   MOVE_FACADE,
+   MOVE_SECRET_POWER,
+   MOVE_REST,
+   MOVE_SKILL_SWAP,
+   MOVE_SNATCH,
+   MOVE_STRENGTH,
+   MOVE_FLASH,
+   MOVE_ROCK_SMASH,
+   MOVE_MEGA_PUNCH,
+   MOVE_MEGA_KICK,
+   MOVE_BODY_SLAM,
+   MOVE_DOUBLE_EDGE,
+   MOVE_COUNTER,
+   MOVE_SEISMIC_TOSS,
+   MOVE_MIMIC,
+   MOVE_METRONOME,
+   MOVE_DREAM_EATER,
+   MOVE_THUNDER_WAVE,
+   MOVE_SUBSTITUTE,
+   MOVE_DYNAMIC_PUNCH,
+   MOVE_PSYCH_UP,
+   MOVE_SNORE,
+   MOVE_ICY_WIND,
+   MOVE_ENDURE,
+   MOVE_MUD_SLAP,
+   MOVE_ICE_PUNCH,
+   MOVE_SWAGGER,
+   MOVE_SLEEP_TALK,
+   MOVE_SWIFT,
+   MOVE_THUNDER_PUNCH,
+   MOVE_FIRE_PUNCH,
+   MOVE_UNAVAILABLE, // This is required to determine where the array ends.
+};
#endif

NOTE: At the top of this file, you will probably see this warning:

//
// DO NOT MODIFY THIS FILE! It is auto-generated from tools/learnset_helpers/teachable.py`
//

From version 1.9 onwards, pokeemerald-expansion includes a tool called the learnset helper, which aims to automate the generation of valid teachable moves. At the time of writing, this tool only supports generating TM and Tutor learnsets. However, in the future it may be expanded to deal with level up learnsets and egg moves.

Ignore the warning shown above the first time you're adding your teachable moves (as otherwise the compiler will complain about the array not existing), but in the future (if you're using the learnset helper) simply edit what teachable moves your Pokémon can learn in one of the JSON files found in tools/learnset_helpers/porymoves_files. It doesn't really matter which one you add your new Pokémon to, as the tool pulls from all of the files in this folder.

The learnset helper is useful if you plan on changing and/or increasing the available TMs and Tutor moves in your game. As an example, Bulbasaur learns Rage by TM in Red/Blue/Yellow, but in Emerald this TM does not exist. But since tools/learnset_helpers/porymoves_files/rby.json defines "MOVE_RAGE" as a TM move for Bulbasaur, that move would automatically be added to the sBulbasaurTeachableLearnset array if you were to add a Rage TM at any point.

The learnset helper can be toggled on/off in include/config/pokemon.h:

// Learnset helper toggles
#define P_LEARNSET_HELPER_TEACHABLE TRUE        // If TRUE, teachable_learnsets.h will be populated by tools/learnset_helpers/teachable.py using the included JSON files based on available TMs and tutors.

Once more, we need to register the learnset in gSpeciesInfo:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        FOOTPRINT(Mewthree)
        .levelUpLearnset = sMewthreeLevelUpLearnset,
+       .teachableLearnset = sMewthreeTeachableLearnset,
    },
 };

If you want to create a Pokémon which can breed, you will need to edit src/data/pokemon/egg_moves.h.

3. Define the Evolutions

We want Mewthree to evolve from Mewtwo by reaching level 100.

Edit gSpeciesInfo:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTWO] =
     {
        ...
        FOOTPRINT(Mewtwo)
        .isLegendary = TRUE,
        .levelUpLearnset = sMewtwoLevelUpLearnset,
        .teachableLearnset = sMewtwoTeachableLearnset,
        .formSpeciesIdTable = sMewtwoFormSpeciesIdTable,
        .formChangeTable = sMewtwoFormChangeTable,
+       .evolutions = EVOLUTION({EVO_LEVEL, 100, SPECIES_MEWTHREE}),
    },
 };

4. Make it appear!

Now Mewthree really does slumber in the games code - but we won't know until we make him appear somewhere! The legend tells that Mewthree is hiding somewhere in Petalburg Woods...

Edit src/data/wild_encounters.json:

         {
           "map": "MAP_PETALBURG_WOODS",
           "base_label": "gPetalburgWoods",
           "land_mons": {
             "encounter_rate": 20,
             "mons": [
               {
                 "min_level": 5,
                 "max_level": 5,
                 "species": "SPECIES_POOCHYENA"
               },
               {
                 "min_level": 5,
                 "max_level": 5,
                 "species": "SPECIES_WURMPLE"
               },
               {
                 "min_level": 5,
                 "max_level": 5,
                 "species": "SPECIES_SHROOMISH"
               },
               {
-                "min_level": 6,
-                "max_level": 6,
-                "species": "SPECIES_POOCHYENA"
+                "min_level": 5,
+                "max_level": 5,
+                "species": "SPECIES_MEWTHREE"
               },
               ...
        }

Congratulations, you have created your own personal pocket monster! You may call yourself a mad scientist now.

Optional data

Now that you now have all the essential pieces to create a base species, there are some aspects that you might want to know if you want to do other stuff with your custom Pokémon.

1. Form tables

Found in src/data/pokemon/form_species_tables.h.

These are introduced to have a reference of what forms correspond to what Species of Pokémon. For example, we have Pikachu's table:

#if P_FAMILY_PIKACHU
static const u16 sPikachuFormSpeciesIdTable[] = {
    SPECIES_PIKACHU,
    SPECIES_PIKACHU_COSPLAY,
    SPECIES_PIKACHU_ROCK_STAR,
    SPECIES_PIKACHU_BELLE,
    SPECIES_PIKACHU_POP_STAR,
    SPECIES_PIKACHU_PH_D,
    SPECIES_PIKACHU_LIBRE,
    SPECIES_PIKACHU_ORIGINAL_CAP,
    SPECIES_PIKACHU_HOENN_CAP,
    SPECIES_PIKACHU_SINNOH_CAP,
    SPECIES_PIKACHU_UNOVA_CAP,
    SPECIES_PIKACHU_KALOS_CAP,
    SPECIES_PIKACHU_ALOLA_CAP,
    SPECIES_PIKACHU_PARTNER_CAP,
    SPECIES_PIKACHU_WORLD_CAP,
    FORM_SPECIES_END,
};
#endif //P_FAMILY_PIKACHU

We register the table for each form in gSpeciesInfo.

    [SPECIES_PIKACHU] =
    {
        ...
        .teachableLearnset = sPikachuTeachableLearnset,
+       .formSpeciesIdTable = sPikachuFormSpeciesIdTable,
        .evolutions = EVOLUTION({EVO_ITEM, ITEM_THUNDER_STONE, SPECIES_RAICHU},
                                {EVO_NONE, 0, SPECIES_RAICHU_ALOLAN}),
    },

    [SPECIES_PIKACHU_COSPLAY] =
    {
        ...
        .teachableLearnset = sPikachuTeachableLearnset,
+       .formSpeciesIdTable = sPikachuFormSpeciesIdTable,
    },

...and so on.

What this allows us to do is to be able to get all forms of a Pokémon in our code by using the GetSpeciesFormTable function.

For example, in the HGSS dex, it lets us browse between the entries of every form available.:

hgssdex1 image

In addition, we have the GET_BASE_SPECIES_ID macro, which returns the first entry of the table (or return the species itself if it doesn't have a table registered). With this, you can check if a Pokémon is any form of a species. For example, making it so that the Light Ball affects all Pikachu forms:

    case HOLD_EFFECT_LIGHT_BALL:
        if (GET_BASE_SPECIES_ID(gBattleMons[battlerAtk].species) == SPECIES_PIKACHU && IS_MOVE_SPECIAL(move))
            modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(2.0));
        break;

2. Form change tables

Found in src/data/pokemon/form_species_tables.h.

These tables, unlike the regular form tables, registers how Pokémon can switch between forms.

#if P_FAMILY_GASTLY
static const struct FormChange sGengarFormChangeTable[] = {
    {FORM_CHANGE_BATTLE_MEGA_EVOLUTION_ITEM, SPECIES_GENGAR_MEGA, ITEM_GENGARITE},
    {FORM_CHANGE_BATTLE_GIGANTAMAX,          SPECIES_GENGAR_GIGANTAMAX},
    {FORM_CHANGE_TERMINATOR},
};
#endif //P_FAMILY_GASTLY

The first value is the type of form change. In the case of Gengar, we have both Mega Evolution and Gigantamax form changes.

The second value is the target form, to which the Pokémon will change into.

Values after that are referred as arguments, and needs to be put there depends on the type of form change, detailed in include/constants/form_change_types.h.

3. Gender differences

gender_diffs

You may have seen that there's a couple of duplicate fields with a "Female" suffix.

    [SPECIES_FRILLISH] =
    {
        ...
        .frontPic = gMonFrontPic_Frillish,
        .frontPicSize = MON_COORDS_SIZE(56, 56),
        .frontPicYOffset = 5,
        .frontAnimFrames = ANIM_FRAMES(
            ANIMCMD_FRAME(1, 30),
            ANIMCMD_FRAME(0, 30),
            ANIMCMD_FRAME(1, 30),
            ANIMCMD_FRAME(0, 30),
        ),
        .frontAnimId = ANIM_RISING_WOBBLE,
        .backPic = gMonBackPic_Frillish,
        .backPicSize = MON_COORDS_SIZE(40, 56),
        .backPicYOffset = 7,
        .backAnimId = BACK_ANIM_CONVEX_DOUBLE_ARC,
        .palette = gMonPalette_Frillish,
        .shinyPalette = gMonShinyPalette_Frillish,
        .iconSprite = gMonIcon_Frillish,
        .iconPalIndex = 0,
+#if P_GENDER_DIFFERENCES
+       .frontPicFemale = gMonFrontPic_FrillishF,
+       .frontPicSizeFemale = MON_COORDS_SIZE(56, 56),
+       .backPicFemale = gMonBackPic_FrillishF,
+       .backPicSizeFemale = MON_COORDS_SIZE(40, 56),
+       .paletteFemale = gMonPalette_FrillishF,
+       .shinyPaletteFemale = gMonShinyPalette_FrillishF,
+       .iconSpriteFemale = gMonIcon_FrillishF,
+       .iconPalIndexFemale = 1,
+#endif //P_GENDER_DIFFERENCES
        FOOTPRINT(Frillish)
        .levelUpLearnset = sFrillishLevelUpLearnset,
        .teachableLearnset = sFrillishTeachableLearnset,
        .evolutions = EVOLUTION({EVO_LEVEL, 40, SPECIES_JELLICENT}),
    },

These are used to change the graphics of the Pokémon if they're female. If they're not registered, they default to the male values.

However, iconPalIndexFemale is a special case, where it's doesn't read the male icon palette if its iconSpriteFemale is set, so if you're setting a female icon, be sure to set their palette index as well.

4. Overworld Data (v1.9 onwards)

If you have OW_POKEMON_OBJECT_EVENTS in your hack, you can add Overworld sprite data of your new species. Naturally, these can also be used for followers.

First, since you copied the contents from Mew's folder previously, you should also have copied its overworld sprites. Edit those to your liking, as we have done before, making sure to update the palettes

Secondly, in src/data/graphics/pokemon.h, add the following:

    const u8 gMonIcon_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/icon.4bpp");
    const u8 gMonFootprint_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/footprint.1bpp");
+   const u32 gObjectEventPic_Mewthree[] = INCBIN_COMP("graphics/pokemon/mewthree/overworld.4bpp");
+   const u32 gOverworldPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/overworld_normal.gbapal.lz");
+   const u32 gShinyOverworldPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/overworld_shiny.gbapal.lz");

Thirdly, in spritesheet_rules.mk

$(POKEMONGFXDIR)/mewtwo/overworld.4bpp: %.4bpp: %.png
    $(GFX) $< $@ -mwidth 4 -mheight 4

+$(POKEMONGFXDIR)/mewthree/overworld.4bpp: %.4bpp: %.png
+	$(GFX) $< $@ -mwidth 4 -mheight 4

$(POKEMONGFXDIR)/mew/overworld.4bpp: %.4bpp: %.png
    $(GFX) $< $@ -mwidth 4 -mheight 4

Fourthly, in src/data/object_events/object_event_pic_tables_followers.h:

#if P_FAMILY_PECHARUNT
/*static const struct SpriteFrameImage sPicTable_Pecharunt[] = {
    overworld_ascending_frames(gObjectEventPic_Pecharunt, 4, 4),
};*/
#endif //P_FAMILY_PECHARUNT

+static const struct SpriteFrameImage sPicTable_Mewthree[] = {
+    overworld_ascending_frames(gObjectEventPic_Mewthree, 4, 4),
+};

And finally, in gSpeciesInfo:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     ...
     [SPECIES_MEWTHREE] =
     {
        ...
        FOOTPRINT(Mewthree)
+       OVERWORLD(
+           sPicTable_Mewthree,
+           SIZE_32x32,
+           SHADOW_SIZE_M,
+           TRACKS_FOOT,
+           sAnimTable_Following,
+           gOverworldPalette_Mewthree,
+           gShinyOverworldPalette_Mewthree
+       )
        .levelUpLearnset = sMewthreeLevelUpLearnset,
        .teachableLearnset = sMewthreeTeachableLearnset,
    },
 };

Note: In versions previous to 1.11, sAnimTable_Following is not added here.

Sprite Size

Depending on your species, you might want to use different sizes for it. For example, certain species known for being big like Steelix use sprites that fit a 64x64 frame instead of 32x32, and as such have SIZE_64x64 in their data instead of SIZE_32x32 to accomodate for them.

Also, in spritesheet_rules.mk, -mwidth and -mheight need to be set to 8 instead of 4 for such cases.

Shadows

You have 4 options for their shadow, between Small, Medium, Large and None:

  • SHADOW_SIZE_NONE
  • SHADOW_SIZE_S shadow_small
  • SHADOW_SIZE_M shadow_medium
  • SHADOW_SIZE_L shadow_large

Tracks

You have 4 options for the tracks that your species will leave behind on sand.

  • TRACKS_NONE
  • TRACKS_FOOT sand_footprints
  • TRACKS_SLITHER slither_tracks
  • TRACKS_SPOT spot_tracks
  • TRACKS_BUG bug_tracks

...though technically you can also use TRACKS_BIKE_TIRE if you wish to.

bike_tire_tracks

Asymmetric sprites (Version 1.10.0 onwards)

scovillain

You can set up an east-west asymetric overworld sprite by adding the East frames at the end of the sheet and changing the following:

Version 1.11.0 onwards

        OVERWORLD(
            sPicTable_Mewthree,
            SIZE_32x32,
            SHADOW_SIZE_M,
            TRACKS_FOOT,
-           sAnimTable_Following,
+           sAnimTable_Following_Asym,
            gOverworldPalette_Mewthree,
            gShinyOverworldPalette_Mewthree
        )

Version 1.10.x

-       OVERWORLD(
+       OVERWORLD_SET_ANIM(
            sPicTable_Mewthree,
            SIZE_32x32,
            SHADOW_SIZE_M,
            TRACKS_FOOT,
+           sAnimTable_Following_Asym,
            gOverworldPalette_Mewthree,
            gShinyOverworldPalette_Mewthree
        )

Either way, you may also create custom animation tables and use them here appropiately.

How to add the Pokémon Object Events to map

In Porymap, select the object you want to set the sprite to. Then, change the field "Sprite" to use OBJ_EVENT_GFX_SPECIES(SPECIES), replacing SPECIES with the name of the species you want to use. If you get a compiler error, it's because it used the species define as part of the macro, so it needs to match how you defined it all the way back in Declare a species constant. charizard overworld_data

If you want to use their shiny and/or female versions, use one of the following macros:

  • OBJ_EVENT_GFX_SPECIES_SHINY(name)
  • OBJ_EVENT_GFX_SPECIES_FEMALE(name)
  • OBJ_EVENT_GFX_SPECIES_SHINY_FEMALE(name)

5. In-battle shadows (v1.10 onwards)

Gen 4-style shadows are defined by the SHADOW macro which takes the following arguments:

  • X offset
  • Y offset
  • Shadow size: you have 4 options for their shadow, between Small, Medium, Large and Extra Large:
    • SHADOW_SIZE_S
    • SHADOW_SIZE_M
    • SHADOW_SIZE_L
    • SHADOW_SIZE_XL_BATTLE_ONLY

To make the Pokémon have no shadow, use the NO_SHADOW macro instead of SHADOW.

6. Limiting species allowed as followers

You may use the following configs in include/config/overworld.h

#define OW_FOLLOWERS_ALLOWED_SPECIES (0)
#define OW_FOLLOWERS_ALLOWED_MET_LVL (0)
#define OW_FOLLOWERS_ALLOWED_MET_LOC (0)

Examples:

// Yellow Pikachu:
#define OW_FOLLOWERS_ALLOWED_SPECIES (SPECIES_PIKACHU)
#define OW_FOLLOWERS_ALLOWED_MET_LVL (0)
#define OW_FOLLOWERS_ALLOWED_MET_LOC (MAPSEC_PALLET_TOWN)
// Hoenn Starter:
#define OW_FOLLOWERS_ALLOWED_SPECIES (0)
#define OW_FOLLOWERS_ALLOWED_MET_LVL (5)
#define OW_FOLLOWERS_ALLOWED_MET_LOC (MAPSEC_ROUTE_101)
// Species set in VAR_XXXX:
#define OW_FOLLOWERS_ALLOWED_SPECIES (VAR_XXXX)
#define OW_FOLLOWERS_ALLOWED_MET_LVL (0)
#define OW_FOLLOWERS_ALLOWED_MET_LOC (0)

This is a modified version of the original tutorial about adding new Pokémon species available in Pokeemerald's wiki.

Despite the persistent rumors about an incredibly strong third form of Mew hiding somewhere, it actually wasn't possible to catch it... OR WAS IT? In this tutorial, we will add a new Pokémon species to the game.

IMPORTANT: This tutorial applies to Version 1.6.2 and lower.

Changes compared to vanilla

The main things that the Expansion changes are listed here.

  • Still Front Pics (gMonStillFrontPic_YourPokemon) and by extension src/anim_mon_front_pics.c have been removed.
  • src/data/pokemon/cry_ids.h doesn't exist anymore.

Content

The Graphics

We will start by copying the folder containing the sprites for Mewtwo and rename it to mewthree (pretty meta huh?):

cp -r graphics/pokemon/mewtwo graphics/pokemon/mewthree

1. Edit the sprites

Let's edit the sprites. Start your favourite image editor (I have used GIMP) and change anim_front.png, front.png and back.png to meet your expectations. Make sure that you are using the indexed mode and you have limited yourself to 15 colors! Put the RGB values of your colors into normal.pal between the first and the last color and the RGB values for the shiny version into shiny.pal. Edit footprint.png using two colors in indexed mode, black and white. Finally, edit icon.png. Notice, that the icon will use one of three predefined palettes instead of normal.pal.

2. Register the sprites

Sadly, just putting the image files into the graphics folder is not enough. To use the sprites we have to register them, which is kind of tedious. First, create constants for the file paths. You'll want to add the constants for your species after the constants for the last valid species. Edit include/graphics.h:

 extern const u32 gMonFrontPic_Calyrex[];
+extern const u32 gMonFrontPic_Mewthree[];
 extern const u32 gMonBackPic_Calyrex[];
+extern const u32 gMonBackPic_Mewthree[];
 extern const u32 gMonPalette_Calyrex[];
+extern const u32 gMonPalette_Mewthree[];
 extern const u32 gMonShinyPalette_Calyrex[];
+extern const u32 gMonShinyPalette_Mewthree[];
 //extern const u8 gMonIcon_Calyrex[];
+extern const u8 gMonIcon_Mewthree[];
 extern const u8 gMonFootprint_Calyrex[];
+extern const u8 gMonFootprint_Mewthree[];

Now link the graphic files.

Edit src/data/graphics/pokemon.h:

 const u32 gMonFrontPic_Calyrex[] = INCBIN_U32("graphics/pokemon/calyrex/front.4bpp.lz");
+const u32 gMonFrontPic_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/front.4bpp.lz");
 const u32 gMonBackPic_Calyrex[] = INCBIN_U32("graphics/pokemon/calyrex/back.4bpp.lz");
+const u32 gMonBackPic_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/back.4bpp.lz");
 const u32 gMonPalette_Calyrex[] = INCBIN_U32("graphics/pokemon/calyrex/normal.gbapal.lz");
+const u32 gMonPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/normal.gbapal.lz");
 const u32 gMonShinyPalette_Calyrex[] = INCBIN_U32("graphics/pokemon/calyrex/shiny.gbapal.lz");
+const u32 gMonShinyPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/shiny.gbapal.lz");
 //const u8 gMonIcon_Calyrex[] = INCBIN_U8("graphics/pokemon/calyrex/icon.4bpp");
+const u8 gMonIcon_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/icon.4bpp");
 const u8 gMonFootprint_Calyrex[] = INCBIN_U8("graphics/pokemon/calyrex/footprint.1bpp");
+const u8 gMonFootprint_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/footprint.1bpp");

Please note that Calyrex, the Pokémon that should be above your insertion for the time being, reads a "front.png" sprite instead of an "anim_front.png" sprite. This is because currently, Calyrex lacks a 2nd frame. If the front sprite sheet of your species uses 2 frames, you should use "anim_front".

It is also worth to mention that Calyrex's icon sprite is commented out simply because it's currently missing. If you do have an icon sprite sheet present inside your species' folder at graphics/pokemon, by all means do not comment entries involving the gMonIcon constants.

3. Animate the sprites

You can define the animation order, in which the sprites will be shown. The first number is the sprite index (so 0 or 1) and the second number is the number of frames the sprite will be visible.

Edit src/data/pokemon_graphics/front_pic_anims.h:

static const union AnimCmd sAnim_Enamorus_1[] =
{
    ANIMCMD_FRAME(0, 1),
    ANIMCMD_END,
};

+static const union AnimCmd sAnim_Mewthree_1[] =
+{
+    ANIMCMD_FRAME(1, 30),
+    ANIMCMD_FRAME(0, 20),
+    ANIMCMD_END,
+};
#endif
SINGLE_ANIMATION(Enamorus);
+SINGLE_ANIMATION(Mewthree);
#endif
 const union AnimCmd *const *const gMonFrontAnimsPtrTable[] =
 {
    [SPECIES_NONE]        = sAnims_None,
    [SPECIES_BULBASAUR]   = sAnims_Bulbasaur,
     ...
    [SPECIES_ENAMORUS] = sAnims_Enamorus,
+   [SPECIES_MEWTHREE] = sAnims_Mewthree,
#endif
     ...
 };

Because you are limited to two frames, there are already predefined front sprite animations, describing translations, rotations, scalings or color changes.

Edit src/pokemon.c:

 static const u8 sMonFrontAnimIdsTable[] =
 {
     [SPECIES_BULBASAUR - 1]     = ANIM_V_JUMPS_H_JUMPS,
     ...
     [SPECIES_DEOXYS_SPEED - 1]           = ANIM_GROW_VIBRATE,
+    [SPECIES_MEWTHREE - 1]               = ANIM_GROW_VIBRATE,
 };

There are also predefined back sprite animations for the back sprites as well.

Edit src/pokemon_animation.c:

 static const u8 sSpeciesToBackAnimSet[] =
 {
     [SPECIES_BULBASAUR]                    = BACK_ANIM_DIP_RIGHT_SIDE,
     ...
     [SPECIES_CHIMECHO]                     = BACK_ANIM_CONVEX_DOUBLE_ARC,
+    [SPECIES_MEWTHREE]                     = BACK_ANIM_GROW_STUTTER,
 };

If you want to delay the time between when the Pokémon appears and when the animation starts, you can add an entry to sMonAnimationDelayTable

Edit src/pokemon.c:

 static const u8 sMonAnimationDelayTable[NUM_SPECIES - 1] =
 {
     [SPECIES_BLASTOISE - 1]  = 50,
     ...
    [SPECIES_KYOGRE - 1]     = 60,
    [SPECIES_RAYQUAZA - 1]   = 60,
+   [SPECIES_MEWTHREE - 1]   = 15,
 };

If you want your Pokémon to fly above the ground, you can add an entry to gEnemyMonElevation.

Edit src/data/pokemon_graphics/enemy_mon_elevation.h:

 const u8 gEnemyMonElevation[NUM_SPECIES] =
 {
     [SPECIES_BUTTERFREE] = 10,
     ...
     [SPECIES_REGIDRAGO] = 5,
+    [SPECIES_MEWTHREE] = 6,
 };

4. Update the tables

Edit src/data/pokemon_graphics/front_pic_table.h:

 const struct CompressedSpriteSheet gMonFrontPicTable[] =
 {
     SPECIES_SPRITE(NONE, gMonFrontPic_CircledQuestionMark),
     SPECIES_SPRITE(BULBASAUR, gMonFrontPic_Bulbasaur),
     ...
     SPECIES_SPRITE(ENAMORUS, gMonFrontPic_Enamorus),
+    SPECIES_SPRITE(MEWTHREE, gMonFrontPic_Mewthree),
#endif
     ...
};

Edit src/data/pokemon_graphics/front_pic_coordinates.h:

 const struct MonCoords gMonFrontPicCoords[] =
 {
     ...
    [SPECIES_ENAMORUS]                     = { .size = MON_COORDS_SIZE(64, 64), .y_offset =  0 },
+   [SPECIES_MEWTHREE]                     = { .size = MON_COORDS_SIZE(64, 64), .y_offset =  0 },
#endif
     ...
 };

Edit src/data/pokemon_graphics/back_pic_table.h:

 const struct CompressedSpriteSheet gMonBackPicTable[] =
 {
     SPECIES_SPRITE(NONE, gMonBackPic_CircledQuestionMark),
     SPECIES_SPRITE(BULBASAUR, gMonBackPic_Bulbasaur),
     ...
    SPECIES_SPRITE(ENAMORUS, gMonBackPic_Enamorus),
+   SPECIES_SPRITE(MEWTHREE, gMonBackPic_Mewthree),
#endif
     ...
 };

Edit src/data/pokemon_graphics/back_pic_coordinates.h:

 const struct MonCoords gMonBackPicCoords[] =
 {
     ...
    [SPECIES_ENAMORUS]                     = { .size = MON_COORDS_SIZE(64, 64), .y_offset =  0 },
+   [SPECIES_MEWTHREE]                     = { .size = MON_COORDS_SIZE(56, 64), .y_offset = 1  },
#endif
     ...
 };

Edit src/data/pokemon_graphics/footprint_table.h:

 const u8 *const gMonFootprintTable[] =
 {
     [SPECIES_NONE] = gMonFootprint_Bulbasaur,
     [SPECIES_BULBASAUR] = gMonFootprint_Bulbasaur,
     ...
     [SPECIES_CALYREX] = gMonFootprint_Calyrex,
+    [SPECIES_MEWTHREE] = gMonFootprint_Mewthree,
#endif
     [SPECIES_EGG] = gMonFootprint_Bulbasaur,
 };

Edit src/data/pokemon_graphics/palette_table.h:

 const struct CompressedSpritePalette gMonPaletteTable[] =
 {
    SPECIES_PAL(NONE, gMonPalette_CircledQuestionMark),
    SPECIES_PAL(BULBASAUR, gMonPalette_Bulbasaur),
    ...
    SPECIES_PAL(ENAMORUS, gMonPalette_Enamorus),
+   SPECIES_PAL(MEWTHREE, gMonPalette_Mewthree),
#endif
    ...
};

Edit src/data/pokemon_graphics/shiny_palette_table.h:

const struct CompressedSpritePalette gMonShinyPaletteTable[] =
{
     SPECIES_SHINY_PAL(NONE, gMonShinyPalette_CircledQuestionMark),
     SPECIES_SHINY_PAL(BULBASAUR, gMonShinyPalette_Bulbasaur),
     ...
    SPECIES_SHINY_PAL(ENAMORUS, gMonShinyPalette_Enamorus),
+   SPECIES_SHINY_PAL(MEWTHREE, gMonShinyPalette_Mewthree),
#endif
     ...
};

Edit src/pokemon_icon.c:

 const u8 *const gMonIconTable[] =
 {
     [SPECIES_NONE] = gMonIcon_Bulbasaur,
     ...
    [SPECIES_ENAMORUS] = gMonIcon_Enamorus,
+   [SPECIES_MEWTHREE] = gMonIcon_Mewthree,
#endif
     ...
 };
 const u8 gMonIconPaletteIndices[] =
 {
     [SPECIES_NONE] = 0,
     ...
    [SPECIES_ENAMORUS] = 1,
+   [SPECIES_MEWTHREE] = 2,
    [SPECIES_VENUSAUR_MEGA] = 1,
     ...
 };

Here, you can choose between the six icon palettes; 0, 1, 2, 3, 4 and 5. All of them located in graphics/pokemon/icon_palettes.

Open an icon sprite and load one of the palettes to find out which palette suits your icon sprite best.

The Data

Our plan is as simple as it is brilliant: clone Mewtwo... and make it even stronger!

1. Declare a species constant

Our first step towards creating a new digital lifeform is to define its own species constant.

Edit include/constants/species.h:

 #define SPECIES_NONE 0
 #define SPECIES_BULBASAUR 1
 ...
 #define SPECIES_ENAMORUS 905
+#define SPECIES_MEWTHREE 906

-#define FORMS_START SPECIES_ENAMORUS
+#define FORMS_START SPECIES_MEWTHREE

2. Devise a name

This name will be displayed in the game. It may be different than the identifier of the species constant, especially when there are special characters involved.

Edit src/data/text/species_names.h:

 const u8 gSpeciesNames[][POKEMON_NAME_LENGTH + 1] = {
     [SPECIES_NONE] = _("??????????"),
     [SPECIES_BULBASAUR] = _("Bulbasaur"),
     ...
     [SPECIES_ENAMORUS] = _("Enamorus"),
+    [SPECIES_MEWTHREE] = _("Mewthree"),
 };

The _() underscore function doesn't really exist - it's a convention borrowed from GNU gettext to let preproc know this is text to be converted to the custom encoding used by the Gen 3 Pokemon games.

3. Define its Pokédex entry

First, we will need to add new index constants for its Pokédex entry. The index constants are divided into the Hoenn Pokédex, which contains all Pokémon native to the Hoenn region, and the National Pokédex containing all known Pokémon, which can be received after entering the hall of fame for the first time.

Edit include/constants/pokedex.h:

// National Pokedex order
enum {
    NATIONAL_DEX_NONE,
    // Kanto
    NATIONAL_DEX_BULBASAUR,
...
    NATIONAL_DEX_ENAMORUS,
+   NATIONAL_DEX_MEWTHREE,
};
 #define KANTO_DEX_COUNT     NATIONAL_DEX_MEW
 #define JOHTO_DEX_COUNT     NATIONAL_DEX_CELEBI
#if P_GEN_8_POKEMON == TRUE
-   #define NATIONAL_DEX_COUNT  NATIONAL_DEX_ENAMORUS
+   #define NATIONAL_DEX_COUNT  NATIONAL_DEX_MEWTHREE

Do keep in mind that if you intend to add your new species to the Hoenn Dex, you'll also want to add a HOENN_DEX constant for it, like this:

// Hoenn Pokedex order
enum {
    HOENN_DEX_NONE,
    HOENN_DEX_TREECKO,
...
    HOENN_DEX_DEOXYS,
+   HOENN_DEX_MEWTHREE,
...
};
- #define HOENN_DEX_COUNT (HOENN_DEX_DEOXYS + 1)
+ #define HOENN_DEX_COUNT (HOENN_DEX_MEWTHREE + 1)

Edit src/pokemon.c:

 // Assigns all species to the National Dex Index (Summary No. for National Dex)
 static const u16 sSpeciesToNationalPokedexNum[NUM_SPECIES - 1] =
 {
     SPECIES_TO_NATIONAL(ENAMORUS),
+    SPECIES_TO_NATIONAL(MEWTHREE),
 };

Just like before, if we want to insert our new species in the Hoenn Dex, we'll have to do a few extra steps:

 // Assigns all species to the Hoenn Dex Index (Summary No. for Hoenn Dex)
 static const u16 sSpeciesToHoennPokedexNum[NUM_SPECIES - 1] =
 {
     SPECIES_TO_HOENN(TREECKO),
     ...
     SPECIES_TO_HOENN(DEOXYS),
+    SPECIES_TO_HOENN(MEWTHREE),
 };
 const u16 gHoennToNationalOrder[NUM_SPECIES] = // Assigns Hoenn Dex Pokémon (Using National Dex Index)
 {
     HOENN_TO_NATIONAL(TREECKO),
     ...
     HOENN_TO_NATIONAL(DEOXYS),
+    HOENN_TO_NATIONAL(MEWTHREE),
 };

Now we can define the actual text of the Pokédex entry.

Append to src/data/pokemon/pokedex_text.h:

 const u8 gEnamorusPokedexText[] = _(
     "Its arrival brings an end to the\n"
     "winter. According to legend, this\n"
     "Pokémon's love gives rise to the\n"
     "budding of fresh life across the land.");

+const u8 gMewthreePokedexText[] = _(
+    "The rumors became true.\n"
+    "This is Mews final form.\n"
+    "Its power level is over 9000.\n"
+    "Has science gone too far?");

Finally, we will add the Pokédex entry for Mewthree and link the text to it.

Edit src/data/pokemon/pokedex_entries.h:

 const struct PokedexEntry gPokedexEntries[] =
 {
     ...
     [NATIONAL_DEX_ENAMORUS] =
     {
         .categoryName = _("Love-Hate"),
         .height = 16,
         .weight = 480,
         .description = gEnamorusPokedexText,
         .pokemonScale = 259,
         .pokemonOffset = 1,
         .trainerScale = 296,
         .trainerOffset = 1,
     },

+    [NATIONAL_DEX_MEWTHREE] =
+    {
+        .categoryName = _("NEW SPECIES"),
+        .height = 15,
+        .weight = 330,
+        .description = gMewthreePokedexText,
+        .pokemonScale = 256,
+        .pokemonOffset = 0,
+        .trainerScale = 290,
+        .trainerOffset = 2,
+    },
 #endif
 };

The values pokemonScale, pokemonOffset, trainerScale and trainerOffset are used for the height comparison figure in the Pokédex. Height and weight are specified in meters and kilograms respectively, while the last digit is the first decimal place.

In Pokémon Emerald, you can sort the Pokédex by name, height or weight. Apparently, the Pokémon order is hardcoded in the game files and not calculated from their data. Therefore we have to include our new Pokémon species at the right places. While the correct position for the alphabetical order is easy to find, it can become quite tedious for height and weight. To find the right position for your Pokémon, you may look at the tables sorted by height and weight respectively in the appendix.

Edit src/data/pokemon/pokedex_orders.h:

 const u16 gPokedexOrder_Alphabetical[] =
 {
     ...
     NATIONAL_DEX_MEW,
+    NATIONAL_DEX_MEWTHREE,
     NATIONAL_DEX_MEWTWO,
     ...
 };

 const u16 gPokedexOrder_Weight[] =
 {
     ...
     NATIONAL_DEX_ESCAVALIER,
     NATIONAL_DEX_FRILLISH,
     NATIONAL_DEX_DURANT,
     NATIONAL_DEX_CINDERACE,
+    NATIONAL_DEX_MEWTHREE,
     //NATIONAL_DEX_PERSIAN, // Alolan Form
     NATIONAL_DEX_DUGTRIO,
     ...
 };

 const u16 gPokedexOrder_Height[] =
 {
     ...
     NATIONAL_DEX_ZERAORA,
     NATIONAL_DEX_GRIMMSNARL,
     NATIONAL_DEX_MR_RIME,
+    NATIONAL_DEX_MEWTHREE,
     // 5'03" / 1.6m
     ...
 };

4. Define its species information

Edit src/data/pokemon/species_info.h:

 const struct SpeciesInfo gSpeciesInfo[] =
 {
     [SPECIES_NONE] = {0},
     ...

      [SPECIES_ENAMORUS] =
      {
         .baseHP        = 74,
         .baseAttack    = 115,
         .baseDefense   = 70,
         .baseSpeed     = 106,
         .baseSpAttack  = 135,
         .baseSpDefense = 80,
         .types = { TYPE_FAIRY, TYPE_FLYING},
         .catchRate = 3,
         .expYield = 261,
         .evYield_SpAttack    = 3,
         .genderRatio = MON_FEMALE,
         .eggCycles = 120,
         .friendship = 90,
         .growthRate = GROWTH_SLOW,
         .eggGroups = { EGG_GROUP_UNDISCOVERED, EGG_GROUP_UNDISCOVERED},
         .abilities = {ABILITY_HEALER, ABILITY_NONE, ABILITY_CONTRARY},
         .bodyColor = BODY_COLOR_PINK,
         .noFlip = FALSE,
         .flags = SPECIES_FLAG_LEGENDARY,
     },

+     [SPECIES_MEWTHREE] =
+     {
+        .baseHP        = 106,
+        .baseAttack    = 150,
+        .baseDefense   = 70,
+        .baseSpeed     = 140,
+        .baseSpAttack  = 194,
+        .baseSpDefense = 120,
+        .types = { TYPE_PSYCHIC, TYPE_PSYCHIC},
+        .catchRate = 3,
+        .expYield = 255,
+        .evYield_SpAttack  = 3,
+        .genderRatio = MON_GENDERLESS,
+        .eggCycles = 120,
+        .friendship = 0,
+        .growthRate = GROWTH_SLOW,
+        .eggGroups = { EGG_GROUP_UNDISCOVERED, EGG_GROUP_UNDISCOVERED},
+        .abilities = {ABILITY_INSOMNIA, ABILITY_NONE},
+        .safariZoneFleeRate = 0,
+        .bodyColor = BODY_COLOR_PURPLE,
+        .noFlip = FALSE,
+     },
#endif
 };

The . is the structure reference operator in C to refer to the member object of the structure SpeciesInfo.

Notice how I also set the ability to ABILITY_INSOMNIA, so our little monster doesn't even need to sleep anymore. You can find the abilities for example here include/constants/abilities.h and here src/data/text/abilities.h.

You can also incorporate a 3rd ability to your species, which is intended to be a Hidden Ability!

5. Delimit the moveset

Let's begin with the moves that can be learned by leveling up.

Append to src/data/pokemon/level_up_learnsets.h:

static const struct LevelUpMove sEnamorusLevelUpLearnset[] = {
    LEVEL_UP_MOVE( 1, MOVE_TACKLE),
    LEVEL_UP_MOVE( 7, MOVE_BITE),
    LEVEL_UP_MOVE(11, MOVE_TWISTER),
    LEVEL_UP_MOVE(14, MOVE_DRAINING_KISS),
    LEVEL_UP_MOVE(22, MOVE_IRON_DEFENSE),
    LEVEL_UP_MOVE(31, MOVE_EXTRASENSORY),
    LEVEL_UP_MOVE(41, MOVE_CRUNCH),
    LEVEL_UP_MOVE(47, MOVE_MOONBLAST),
    LEVEL_UP_MOVE( 1, MOVE_SPRINGTIDE_STORM),
    LEVEL_UP_END
};

+static const struct LevelUpMove sMewthreeLevelUpLearnset[] = {
+   LEVEL_UP_MOVE( 1, MOVE_CONFUSION),
+   LEVEL_UP_MOVE( 1, MOVE_DISABLE),
+   LEVEL_UP_MOVE(11, MOVE_BARRIER),
+   LEVEL_UP_MOVE(22, MOVE_SWIFT),
+   LEVEL_UP_MOVE(33, MOVE_PSYCH_UP),
+   LEVEL_UP_MOVE(44, MOVE_FUTURE_SIGHT),
+   LEVEL_UP_MOVE(55, MOVE_MIST),
+   LEVEL_UP_MOVE(66, MOVE_PSYCHIC),
+   LEVEL_UP_MOVE(77, MOVE_AMNESIA),
+   LEVEL_UP_MOVE(88, MOVE_RECOVER),
+   LEVEL_UP_MOVE(99, MOVE_SAFEGUARD),
+   LEVEL_UP_END
+};
#endif

Again, we need to register the learnset.

Edit src/data/pokemon/level_up_learnset_pointers.h:

 const struct LevelUpMove *const gLevelUpLearnsets[NUM_SPECIES] =
 {
     [SPECIES_NONE] = sBulbasaurLevelUpLearnset,
     [SPECIES_BULBASAUR] = sBulbasaurLevelUpLearnset,
     ...
     [SPECIES_ENAMORUS] = sEnamorusLevelUpLearnset,
+    [SPECIES_MEWTHREE] = sMewthreeLevelUpLearnset,
 };

Next we need to specify which moves can be taught via TM, HM, or Move Tutor.

Append to src/data/pokemon/teachable_learnsets.h:

static const u16 sEnamorusTeachableLearnset[] = {
    MOVE_UNAVAILABLE,
};

+static const u16 sMewthreeTeachableLearnset[] = {
+   MOVE_FOCUS_PUNCH,
+   MOVE_WATER_PULSE,
+   MOVE_CALM_MIND,
+   MOVE_TOXIC,
+   MOVE_HAIL,
+   MOVE_BULK_UP,
+   MOVE_HIDDEN_POWER,
+   MOVE_SUNNY_DAY,
+   MOVE_TAUNT,
+   MOVE_ICE_BEAM,
+   MOVE_BLIZZARD,
+   MOVE_HYPER_BEAM,
+   MOVE_LIGHT_SCREEN,
+   MOVE_PROTECT,
+   MOVE_RAIN_DANCE,
+   MOVE_SAFEGUARD,
+   MOVE_FRUSTRATION,
+   MOVE_SOLAR_BEAM,
+   MOVE_IRON_TAIL,
+   MOVE_THUNDERBOLT,
+   MOVE_THUNDER,
+   MOVE_EARTHQUAKE,
+   MOVE_RETURN,
+   MOVE_PSYCHIC,
+   MOVE_SHADOW_BALL,
+   MOVE_BRICK_BREAK,
+   MOVE_DOUBLE_TEAM,
+   MOVE_REFLECT,
+   MOVE_SHOCK_WAVE,
+   MOVE_FLAMETHROWER,
+   MOVE_SANDSTORM,
+   MOVE_FIRE_BLAST,
+   MOVE_ROCK_TOMB,
+   MOVE_AERIAL_ACE,
+   MOVE_TORMENT,
+   MOVE_FACADE,
+   MOVE_SECRET_POWER,
+   MOVE_REST,
+   MOVE_SKILL_SWAP,
+   MOVE_SNATCH,
+   MOVE_STRENGTH,
+   MOVE_FLASH,
+   MOVE_ROCK_SMASH,
+   MOVE_MEGA_PUNCH,
+   MOVE_MEGA_KICK,
+   MOVE_BODY_SLAM,
+   MOVE_DOUBLE_EDGE,
+   MOVE_COUNTER,
+   MOVE_SEISMIC_TOSS,
+   MOVE_MIMIC,
+   MOVE_METRONOME,
+   MOVE_DREAM_EATER,
+   MOVE_THUNDER_WAVE,
+   MOVE_SUBSTITUTE,
+   MOVE_DYNAMIC_PUNCH,
+   MOVE_PSYCH_UP,
+   MOVE_SNORE,
+   MOVE_ICY_WIND,
+   MOVE_ENDURE,
+   MOVE_MUD_SLAP,
+   MOVE_ICE_PUNCH,
+   MOVE_SWAGGER,
+   MOVE_SLEEP_TALK,
+   MOVE_SWIFT,
+   MOVE_THUNDER_PUNCH,
+   MOVE_FIRE_PUNCH,
+   MOVE_UNAVAILABLE,
+};
#endif

Once more, we need to register the learnset.

Edit src/data/pokemon/teachable_learnset_pointers.h:

const u16 *const gTeachableLearnsets[NUM_SPECIES] =
 {
     [SPECIES_NONE] = sBulbasaurTeachableLearnset,
     [SPECIES_BULBASAUR] = sBulbasaurTeachableLearnset,
     ...
     [SPECIES_ENAMORUS] = sEnamorusTeachableLearnset,
+    [SPECIES_MEWTHREE] = sMewthreeTeachableLearnset,
 };

If you want to create a Pokémon which can breed, you will need to edit src/data/pokemon/egg_moves.h.

6. Define its cry

First run these command to copy the Mewtwo sound files:

cp -r sound/direct_sound_samples/cries/mewtwo.bin sound/direct_sound_samples/cries/mewthree.bin
cp -r sound/direct_sound_samples/cries/mewtwo.aif sound/direct_sound_samples/cries/mewthree.aif

In sound/direct_sound_data.inc.

    .align 2
Cry_Enamorus::
    .incbin "sound/direct_sound_samples/cries/enamorus.bin"

+   .align 2
+Cry_Mewthree::
+   .incbin "sound/direct_sound_samples/cries/mewthree.bin"

.endif

And linking it in sound/cry_tables.inc. cry_reverse in particular is for reversed cries used by moves such as Growl.

...
    cry Cry_Enamorus
+   cry Cry_Mewthree
.else
    cry_reverse Cry_Overqwil
+   cry_reverse Cry_Mewthree
.else

Mon cries are 10512Hz. Make sure to put the aif file in the directory sound/direct_sound_samples/cries

Higher frequencies may be ruined by compression. To have the cries uncompressed, follow this , then clear out the old sound bins

7. Define the Evolutions

We want Mewthree to evolve from Mewtwo by reaching level 100.

Edit src/data/pokemon/evolution.h:

    [SPECIES_SNEASEL_HISUIAN]       = {{EVO_ITEM_DAY, ITEM_RAZOR_CLAW, SPECIES_SNEASLER},
                                       {EVO_ITEM_HOLD_DAY, ITEM_RAZOR_CLAW, SPECIES_SNEASLER}},
+   [SPECIES_MEWTWO]                = {{EVO_LEVEL, 100, SPECIES_MEWTHREE}},
#endif

8. Easy Chat about your Pokémon

Edit src/data/easy_chat/easy_chat_words_by_letter.h:

 const u16 gEasyChatWordsByLetter_M[] = {
     EC_MOVE2(MACH_PUNCH),
     ...
     EC_POKEMON_NATIONAL(MEW),
+    EC_POKEMON_NATIONAL(MEWTHREE),
     EC_POKEMON_NATIONAL(MEWTWO),
     ...
     EC_WORD_MYSTERY,
 };

9. Make it appear!

Now Mewthree really does slumber in the games code - but we won't know until we make him appear somewhere! The legend tells that Mewthree is hiding somewhere in Petalburg Woods...

Edit src/data/wild_encounters.json:

         {
           "map": "MAP_PETALBURG_WOODS",
           "base_label": "gPetalburgWoods",
           "land_mons": {
             "encounter_rate": 20,
             "mons": [
               {
                 "min_level": 5,
                 "max_level": 5,
                 "species": "SPECIES_POOCHYENA"
               },
               {
                 "min_level": 5,
                 "max_level": 5,
                 "species": "SPECIES_WURMPLE"
               },
               {
                 "min_level": 5,
                 "max_level": 5,
                 "species": "SPECIES_SHROOMISH"
               },
               {
-                "min_level": 6,
-                "max_level": 6,
-                "species": "SPECIES_POOCHYENA"
+                "min_level": 5,
+                "max_level": 5,
+                "species": "SPECIES_MEWTHREE"
               },
               ...
        }

Congratulations, you have created your own personal pocket monster! You may call yourself a mad scientist now.

Appendix

Available Front Animations

Only 65 are used in-game, but you can use any animation from this list.

  1. ANIM_V_SQUISH_AND_BOUNCE
  2. ANIM_CIRCULAR_STRETCH_TWICE
  3. ANIM_H_VIBRATE
  4. ANIM_H_SLIDE
  5. ANIM_V_SLIDE
  6. ANIM_BOUNCE_ROTATE_TO_SIDES
  7. ANIM_V_JUMPS_H_JUMPS
  8. ANIM_ROTATE_TO_SIDES
  9. ANIM_ROTATE_TO_SIDES_TWICE
  10. ANIM_GROW_VIBRATE
  11. ANIM_ZIGZAG_FAST
  12. ANIM_SWING_CONCAVE
  13. ANIM_SWING_CONCAVE_FAST
  14. ANIM_SWING_CONVEX
  15. ANIM_SWING_CONVEX_FAST
  16. ANIM_H_SHAKE
  17. ANIM_V_SHAKE
  18. ANIM_CIRCULAR_VIBRATE
  19. ANIM_TWIST
  20. ANIM_SHRINK_GROW
  21. ANIM_CIRCLE_C_CLOCKWISE
  22. ANIM_GLOW_BLACK
  23. ANIM_H_STRETCH
  24. ANIM_V_STRETCH
  25. ANIM_RISING_WOBBLE
  26. ANIM_V_SHAKE_TWICE
  27. ANIM_TIP_MOVE_FORWARD
  28. ANIM_H_PIVOT
  29. ANIM_V_SLIDE_WOBBLE
  30. ANIM_H_SLIDE_WOBBLE
  31. ANIM_V_JUMPS_BIG
  32. ANIM_SPIN_LONG
  33. ANIM_GLOW_ORANGE
  34. ANIM_GLOW_RED
  35. ANIM_GLOW_BLUE
  36. ANIM_GLOW_YELLOW
  37. ANIM_GLOW_PURPLE
  38. ANIM_BACK_AND_LUNGE
  39. ANIM_BACK_FLIP
  40. ANIM_FLICKER
  41. ANIM_BACK_FLIP_BIG
  42. ANIM_FRONT_FLIP
  43. ANIM_TUMBLING_FRONT_FLIP
  44. ANIM_FIGURE_8
  45. ANIM_FLASH_YELLOW
  46. ANIM_SWING_CONCAVE_FAST_SHORT
  47. ANIM_SWING_CONVEX_FAST_SHORT
  48. ANIM_ROTATE_UP_SLAM_DOWN
  49. ANIM_DEEP_V_SQUISH_AND_BOUNCE
  50. ANIM_H_JUMPS
  51. ANIM_H_JUMPS_V_STRETCH
  52. ANIM_ROTATE_TO_SIDES_FAST
  53. ANIM_ROTATE_UP_TO_SIDES
  54. ANIM_FLICKER_INCREASING
  55. ANIM_TIP_HOP_FORWARD
  56. ANIM_PIVOT_SHAKE
  57. ANIM_TIP_AND_SHAKE
  58. ANIM_VIBRATE_TO_CORNERS
  59. ANIM_GROW_IN_STAGES
  60. ANIM_V_SPRING
  61. ANIM_V_REPEATED_SPRING
  62. ANIM_SPRING_RISING
  63. ANIM_H_SPRING
  64. ANIM_H_REPEATED_SPRING_SLOW
  65. ANIM_H_SLIDE_SHRINK
  66. ANIM_LUNGE_GROW
  67. ANIM_CIRCLE_INTO_BG
  68. ANIM_RAPID_H_HOPS
  69. ANIM_FOUR_PETAL
  70. ANIM_V_SQUISH_AND_BOUNCE_SLOW
  71. ANIM_H_SLIDE_SLOW
  72. ANIM_V_SLIDE_SLOW
  73. ANIM_BOUNCE_ROTATE_TO_SIDES_SMALL
  74. ANIM_BOUNCE_ROTATE_TO_SIDES_SLOW
  75. ANIM_BOUNCE_ROTATE_TO_SIDES_SMALL_SLOW
  76. ANIM_ZIGZAG_SLOW
  77. ANIM_H_SHAKE_SLOW
  78. ANIM_V_SHAKE_SLOW
  79. ANIM_TWIST_TWICE
  80. ANIM_CIRCLE_C_CLOCKWISE_SLOW
  81. ANIM_V_SHAKE_TWICE_SLOW
  82. ANIM_V_SLIDE_WOBBLE_SMALL
  83. ANIM_V_JUMPS_SMALL
  84. ANIM_SPIN
  85. ANIM_TUMBLING_FRONT_FLIP_TWICE
  86. ANIM_DEEP_V_SQUISH_AND_BOUNCE_TWICE
  87. ANIM_H_JUMPS_V_STRETCH_TWICE
  88. ANIM_V_SHAKE_BACK
  89. ANIM_V_SHAKE_BACK_SLOW
  90. ANIM_V_SHAKE_H_SLIDE_SLOW
  91. ANIM_V_STRETCH_BOTH_ENDS_SLOW
  92. ANIM_H_STRETCH_FAR_SLOW
  93. ANIM_V_SHAKE_LOW_TWICE
  94. ANIM_H_SHAKE_FAST
  95. ANIM_H_SLIDE_FAST
  96. ANIM_H_VIBRATE_FAST
  97. ANIM_H_VIBRATE_FASTEST
  98. ANIM_V_SHAKE_BACK_FAST
  99. ANIM_V_SHAKE_LOW_TWICE_SLOW
  100. ANIM_V_SHAKE_LOW_TWICE_FAST
  101. ANIM_CIRCLE_C_CLOCKWISE_LONG
  102. ANIM_GROW_STUTTER_SLOW
  103. ANIM_V_SHAKE_H_SLIDE
  104. ANIM_V_SHAKE_H_SLIDE_FAST
  105. ANIM_TRIANGLE_DOWN_SLOW
  106. ANIM_TRIANGLE_DOWN
  107. ANIM_TRIANGLE_DOWN_TWICE
  108. ANIM_GROW
  109. ANIM_GROW_TWICE
  110. ANIM_H_SPRING_FAST
  111. ANIM_H_SPRING_SLOW
  112. ANIM_H_REPEATED_SPRING_FAST
  113. ANIM_H_REPEATED_SPRING
  114. ANIM_SHRINK_GROW_FAST
  115. ANIM_SHRINK_GROW_SLOW
  116. ANIM_V_STRETCH_BOTH_ENDS
  117. ANIM_V_STRETCH_BOTH_ENDS_TWICE
  118. ANIM_H_STRETCH_FAR_TWICE
  119. ANIM_H_STRETCH_FAR
  120. ANIM_GROW_STUTTER_TWICE
  121. ANIM_GROW_STUTTER
  122. ANIM_CONCAVE_ARC_LARGE_SLOW
  123. ANIM_CONCAVE_ARC_LARGE
  124. ANIM_CONCAVE_ARC_LARGE_TWICE
  125. ANIM_CONVEX_DOUBLE_ARC_SLOW
  126. ANIM_CONVEX_DOUBLE_ARC
  127. ANIM_CONVEX_DOUBLE_ARC_TWICE
  128. ANIM_CONCAVE_ARC_SMALL_SLOW
  129. ANIM_CONCAVE_ARC_SMALL
  130. ANIM_CONCAVE_ARC_SMALL_TWICE
  131. ANIM_H_DIP
  132. ANIM_H_DIP_FAST
  133. ANIM_H_DIP_TWICE
  134. ANIM_SHRINK_GROW_VIBRATE_FAST
  135. ANIM_SHRINK_GROW_VIBRATE
  136. ANIM_SHRINK_GROW_VIBRATE_SLOW
  137. ANIM_JOLT_RIGHT_FAST
  138. ANIM_JOLT_RIGHT
  139. ANIM_JOLT_RIGHT_SLOW
  140. ANIM_SHAKE_FLASH_YELLOW_FAST
  141. ANIM_SHAKE_FLASH_YELLOW
  142. ANIM_SHAKE_FLASH_YELLOW_SLOW
  143. ANIM_SHAKE_GLOW_RED_FAST
  144. ANIM_SHAKE_GLOW_RED
  145. ANIM_SHAKE_GLOW_RED_SLOW
  146. ANIM_SHAKE_GLOW_GREEN_FAST
  147. ANIM_SHAKE_GLOW_GREEN
  148. ANIM_SHAKE_GLOW_GREEN_SLOW
  149. ANIM_SHAKE_GLOW_BLUE_FAST
  150. ANIM_SHAKE_GLOW_BLUE
  151. ANIM_SHAKE_GLOW_BLUE_SLOW

Available Back Animations

  1. BACK_ANIM_NONE
  2. BACK_ANIM_H_VIBRATE
  3. BACK_ANIM_H_SLIDE
  4. BACK_ANIM_H_SPRING
  5. BACK_ANIM_H_SPRING_REPEATED
  6. BACK_ANIM_SHRINK_GROW
  7. BACK_ANIM_GROW
  8. BACK_ANIM_CIRCLE_COUNTERCLOCKWISE
  9. BACK_ANIM_H_SHAKE
  10. BACK_ANIM_V_SHAKE
  11. BACK_ANIM_V_SHAKE_H_SLIDE
  12. BACK_ANIM_V_STRETCH
  13. BACK_ANIM_H_STRETCH
  14. BACK_ANIM_GROW_STUTTER
  15. BACK_ANIM_V_SHAKE_LOW
  16. BACK_ANIM_TRIANGLE_DOWN
  17. BACK_ANIM_CONCAVE_ARC_LARGE
  18. BACK_ANIM_CONVEX_DOUBLE_ARC
  19. BACK_ANIM_CONCAVE_ARC_SMALL
  20. BACK_ANIM_DIP_RIGHT_SIDE
  21. BACK_ANIM_SHRINK_GROW_VIBRATE
  22. BACK_ANIM_JOLT_RIGHT
  23. BACK_ANIM_SHAKE_FLASH_YELLOW
  24. BACK_ANIM_SHAKE_GLOW_RED
  25. BACK_ANIM_SHAKE_GLOW_GREEN
  26. BACK_ANIM_SHAKE_GLOW_BLUE

Pokémon ordered by height

Pokemonheight (m)
Diglett0.2
Natu0.2
Azurill0.2
Caterpie0.3
Weedle0.3
Pidgey0.3
Rattata0.3
Spearow0.3
Paras0.3
Magnemite0.3
Shellder0.3
Ditto0.3
Eevee0.3
Pichu0.3
Cleffa0.3
Igglybuff0.3
Togepi0.3
Sunkern0.3
Wurmple0.3
Taillow0.3
Roselia0.3
Castform0.3
Jirachi0.3
Pikachu0.4
Nidoran_f0.4
Meowth0.4
Geodude0.4
Krabby0.4
Exeggcute0.4
Cubone0.4
Horsea0.4
Omanyte0.4
Mew0.4
Bellossom0.4
Marill0.4
Hoppip0.4
Wooper0.4
Swinub0.4
Smoochum0.4
Torchic0.4
Mudkip0.4
Zigzagoon0.4
Ralts0.4
Shroomish0.4
Aron0.4
Plusle0.4
Minun0.4
Gulpin0.4
Cacnea0.4
Swablu0.4
Barboach0.4
Clamperl0.4
Squirtle0.5
Nidoran_m0.5
Jigglypuff0.5
Oddish0.5
Mankey0.5
Voltorb0.5
Kabuto0.5
Cyndaquil0.5
Spinarak0.5
Chinchou0.5
Murkrow0.5
Unown0.5
Qwilfish0.5
Phanpy0.5
Treecko0.5
Poochyena0.5
Linoone0.5
Lotad0.5
Seedot0.5
Surskit0.5
Nincada0.5
Sableye0.5
Torkoal0.5
Baltoy0.5
Charmander0.6
Kakuna0.6
Sandshrew0.6
Clefairy0.6
Vulpix0.6
Poliwag0.6
Koffing0.6
Goldeen0.6
Totodile0.6
Togetic0.6
Mareep0.6
Skiploom0.6
Pineco0.6
Snubbull0.6
Shuckle0.6
Teddiursa0.6
Corsola0.6
Remoraid0.6
Houndour0.6
Porygon20.6
Elekid0.6
Larvitar0.6
Celebi0.6
Silcoon0.6
Wingull0.6
Whismur0.6
Skitty0.6
Mawile0.6
Meditite0.6
Electrike0.6
Illumise0.6
Corphish0.6
Feebas0.6
Shuppet0.6
Chimecho0.6
Wynaut0.6
Luvdisc0.6
Bagon0.6
Beldum0.6
Bulbasaur0.7
Metapod0.7
Raticate0.7
Dugtrio0.7
Growlithe0.7
Bellsprout0.7
Hoothoot0.7
Misdreavus0.7
Slugma0.7
Tyrogue0.7
Magby0.7
Marshtomp0.7
Cascoon0.7
Swellow0.7
Volbeat0.7
Numel0.7
Spoink0.7
Trapinch0.7
Anorith0.7
Snorunt0.7
Raichu0.8
Nidorina0.8
Zubat0.8
Gloom0.8
Psyduck0.8
Machop0.8
Farfetchd0.8
Staryu0.8
Jolteon0.8
Porygon0.8
Sentret0.8
Flaaffy0.8
Azumarill0.8
Jumpluff0.8
Aipom0.8
Sunflora0.8
Magcargo0.8
Kirlia0.8
Masquerain0.8
Slakoth0.8
Ninjask0.8
Shedinja0.8
Carvanha0.8
Duskull0.8
Spheal0.8
Nidorino0.9
Abra0.9
Tentacool0.9
Grimer0.9
Magikarp0.9
Flareon0.9
Chikorita0.9
Quilava0.9
Espeon0.9
Sneasel0.9
Octillery0.9
Delibird0.9
Grovyle0.9
Combusken0.9
Lairon0.9
Grumpig0.9
Whiscash0.9
Ivysaur1.0
Wartortle1.0
Beedrill1.0
Sandslash1.0
Wigglytuff1.0
Parasect1.0
Venonat1.0
Persian1.0
Primeape1.0
Poliwhirl1.0
Weepinbell1.0
Graveler1.0
Ponyta1.0
Magneton1.0
Drowzee1.0
Marowak1.0
Rhyhorn1.0
Tangela1.0
Vaporeon1.0
Omastar1.0
Ledyba1.0
Umbreon1.0
Mightyena1.0
Beautifly1.0
Nuzleaf1.0
Loudred1.0
Makuhita1.0
Nosepass1.0
Lunatone1.0
Lileep1.0
Kecleon1.0
Relicanth1.0
Charmeleon1.1
Butterfree1.1
Pidgeotto1.1
Ninetales1.1
Seel1.1
Chansey1.1
Starmie1.1
Electabuzz1.1
Croconaw1.1
Ariados1.1
Politoed1.1
Gligar1.1
Piloswine1.1
Donphan1.1
Delcatty1.1
Spinda1.1
Vibrava1.1
Altaria1.1
Crawdaunt1.1
Banette1.1
Sealeo1.1
Shelgon1.1
Fearow1.2
Vileplume1.2
Slowpoke1.2
Muk1.2
Electrode1.2
Lickitung1.2
Weezing1.2
Seadra1.2
Bayleef1.2
Lanturn1.2
Sudowoodo1.2
Yanma1.2
Forretress1.2
Smeargle1.2
Miltank1.2
Pupitar1.2
Dustox1.2
Lombre1.2
Pelipper1.2
Breloom1.2
Solrock1.2
Absol1.2
Metang1.2
Nidoqueen1.3
Clefable1.3
Poliwrath1.3
Kadabra1.3
Gastly1.3
Kingler1.3
Seaking1.3
Mr_mime1.3
Magmar1.3
Kabutops1.3
Wobbuffet1.3
Shiftry1.3
Medicham1.3
Cacturne1.3
Zangoose1.3
Nidoking1.4
Golem1.4
Doduo1.4
Hitmonchan1.4
Jynx1.4
Tauros1.4
Ledian1.4
Ampharos1.4
Quagsire1.4
Granbull1.4
Houndoom1.4
Stantler1.4
Hitmontop1.4
Vigoroth1.4
Walrein1.4
Latias1.4
Pidgeot1.5
Venomoth1.5
Alakazam1.5
Machoke1.5
Cloyster1.5
Gengar1.5
Hitmonlee1.5
Scyther1.5
Pinsir1.5
Xatu1.5
Girafarig1.5
Dunsparce1.5
Heracross1.5
Blissey1.5
Swampert1.5
Ludicolo1.5
Exploud1.5
Manectric1.5
Claydol1.5
Cradily1.5
Armaldo1.5
Glalie1.5
Salamence1.5
Blastoise1.6
Golbat1.6
Machamp1.6
Tentacruel1.6
Slowbro1.6
Haunter1.6
Hypno1.6
Zapdos1.6
Noctowl1.6
Gardevoir1.6
Dusclops1.6
Metagross1.6
Charizard1.7
Golduck1.7
Victreebel1.7
Rapidash1.7
Dewgong1.7
Articuno1.7
Typhlosion1.7
Skarmory1.7
Sceptile1.7
Swalot1.7
Huntail1.7
Regirock1.7
Deoxys1.7
Dodrio1.8
Aerodactyl1.8
Dratini1.8
Meganium1.8
Furret1.8
Crobat1.8
Scizor1.8
Ursaring1.8
Kingdra1.8
Sharpedo1.8
Gorebyss1.8
Regice1.8
Arcanine1.9
Rhydon1.9
Raikou1.9
Blaziken1.9
Camerupt1.9
Registeel1.9
Venusaur2.0
Ekans2.0
Exeggutor2.0
Moltres2.0
Mewtwo2.0
Slowking2.0
Suicune2.0
Tyranitar2.0
Slaking2.0
Wailmer2.0
Flygon2.0
Tropius2.0
Latios2.0
Snorlax2.1
Mantine2.1
Entei2.1
Aggron2.1
Kangaskhan2.2
Dragonite2.2
Feraligatr2.3
Hariyama2.3
Lapras2.5
Seviper2.7
Arbok3.5
Groudon3.5
Ho_oh3.8
Dragonair4.0
Kyogre4.5
Lugia5.2
Milotic6.2
Gyarados6.5
Rayquaza7.0
Onix8.8
Steelix9.2
Wailord14.5

Pokémon ordered by weight

Pokemonweight (kg)
Gastly0.1
Haunter0.1
Hoppip0.5
Diglett0.8
Castform0.8
Igglybuff1.0
Koffing1.0
Skiploom1.0
Chimecho1.0
Misdreavus1.0
Jirachi1.1
Swablu1.2
Shedinja1.2
Togepi1.5
Surskit1.7
Pidgey1.8
Sunkern1.8
Barboach1.9
Natu2.0
Azurill2.0
Spearow2.0
Pichu2.0
Roselia2.0
Murkrow2.1
Taillow2.3
Shuppet2.3
Exeggcute2.5
Torchic2.5
Lotad2.6
Caterpie2.9
Cleffa3.0
Jumpluff3.0
Weedle3.2
Togetic3.2
Dratini3.3
Rattata3.5
Wurmple3.6
Masquerain3.6
Qwilfish3.9
Shellder4.0
Ditto4.0
Mew4.0
Seedot4.0
Bellsprout4.0
Meowth4.2
Plusle4.2
Minun4.2
Shroomish4.5
Unown5.0
Treecko5.0
Corsola5.0
Celebi5.0
Spinda5.0
Paras5.4
Oddish5.4
Jigglypuff5.5
Nincada5.5
Bellossom5.8
Magnemite6.0
Pikachu6.0
Smoochum6.0
Sentret6.0
Chikorita6.4
Weepinbell6.4
Eevee6.5
Krabby6.5
Cubone6.5
Swinub6.5
Ralts6.6
Bulbasaur6.9
Ekans6.9
Nidoran_f7.0
Pineco7.2
Feebas7.4
Omanyte7.5
Clefairy7.5
Zubat7.5
Mudkip7.6
Mareep7.8
Snubbull7.8
Cyndaquil7.9
Horsea8.0
Marill8.5
Wooper8.5
Spinarak8.5
Charmander8.5
Sunflora8.5
Gloom8.6
Luvdisc8.7
Teddiursa8.8
Squirtle9.0
Nidoran_m9.0
Totodile9.5
Wingull9.5
Weezing9.5
Vulpix9.9
Metapod9.9
Kakuna10.0
Silcoon10.0
Magikarp10.0
Gulpin10.3
Voltorb10.4
Houndour10.8
Ledyba10.8
Sableye11.0
Skitty11.0
Meditite11.2
Kabuto11.5
Mawile11.5
Corphish11.5
Cascoon11.5
Aipom11.5
Chinchou12.0
Sandshrew12.0
Remoraid12.0
Ninjask12.0
Wigglytuff12.0
Poliwag12.4
Anorith12.5
Banette12.5
Venomoth12.5
Ivysaur13.0
Flaaffy13.3
Poochyena13.6
Wynaut14.0
Dunsparce14.0
Goldeen15.0
Trapinch15.0
Farfetchd15.0
Duskull15.0
Xatu15.0
Electrike15.2
Vibrava15.3
Victreebel15.5
Bayleef15.8
Delibird16.0
Whismur16.3
Dragonair16.5
Snorunt16.8
Zigzagoon17.5
Illumise17.7
Volbeat17.7
Raticate18.5
Vileplume18.6
Growlithe19.0
Quilava19.0
Charmeleon19.0
Machop19.5
Nidorino19.5
Abra19.5
Combusken19.5
Psyduck19.6
Swellow19.8
Ninetales19.9
Geodude20.0
Nidorina20.0
Poliwhirl20.0
Kirlia20.2
Shuckle20.5
Altaria20.6
Carvanha20.8
Tyrogue21.0
Hoothoot21.2
Magby21.4
Baltoy21.5
Grovyle21.6
Kecleon22.0
Wartortle22.5
Lanturn22.5
Gorebyss22.6
Relicanth23.4
Elekid23.5
Whiscash23.6
Lileep23.8
Numel24.0
Slakoth24.0
Jolteon24.5
Flareon25.0
Croconaw25.0
Seadra25.0
Espeon26.5
Umbreon27.0
Huntail27.0
Mankey28.0
Marshtomp28.0
Sneasel28.0
Nuzleaf28.0
Pelipper28.0
Beautifly28.4
Azumarill28.5
Octillery28.5
Wobbuffet28.5
Vaporeon29.0
Beedrill29.5
Sandslash29.5
Parasect29.5
Raichu30.0
Grimer30.0
Venonat30.0
Ponyta30.0
Pidgeotto30.0
Electabuzz30.0
Muk30.0
Spoink30.6
Dusclops30.6
Medicham31.5
Dustox31.6
Persian32.0
Primeape32.0
Butterfree32.0
Drowzee32.4
Linoone32.5
Porygon232.5
Lombre32.5
Furret32.5
Delcatty32.6
Crawdaunt32.8
Dugtrio33.3
Phanpy33.5
Ariados33.5
Politoed33.9
Staryu34.5
Chansey34.6
Slugma35.0
Tangela35.0
Omastar35.0
Houndoom35.0
Ledian35.6
Slowpoke36.0
Porygon36.5
Mightyena37.0
Fearow38.0
Sudowoodo38.0
Yanma38.0
Seaking39.0
Breloom39.2
Doduo39.2
Spheal39.5
Pidgeot39.5
Clefable40.0
Latias40.0
Manectric40.2
Zangoose40.3
Loudred40.5
Kabutops40.5
Gengar40.5
Jynx40.6
Noctowl40.8
Girafarig41.5
Bagon42.1
Magmar44.5
Marowak45.0
Tentacool45.5
Vigoroth46.5
Blissey46.8
Absol47.0
Hitmontop48.0
Alakazam48.0
Gardevoir48.4
Granbull48.7
Hitmonlee49.8
Hitmonchan50.2
Skarmory50.5
Cacnea51.3
Blaziken52.0
Sceptile52.2
Clamperl52.5
Seviper52.5
Zapdos52.6
Poliwrath54.0
Heracross54.0
Mr_mime54.5
Magcargo55.0
Pinsir55.0
Ludicolo55.0
Golbat55.0
Tentacruel55.0
Articuno55.4
Piloswine55.8
Scyther56.0
Kadabra56.5
Smeargle58.0
Aerodactyl59.0
Shiftry59.6
Aron60.0
Magneton60.0
Nidoqueen60.0
Kingler60.0
Moltres60.0
Latios60.0
Cradily60.4
Deoxys60.8
Ampharos61.5
Nidoking62.0
Gligar64.8
Arbok65.0
Lickitung65.5
Electrode66.6
Armaldo68.2
Machoke70.5
Stantler71.2
Grumpig71.5
Larvitar72.0
Quagsire75.0
Crobat75.0
Miltank75.5
Hypno75.6
Golduck76.6
Cacturne77.4
Slowbro78.5
Typhlosion79.5
Slowking79.5
Starmie80.0
Swalot80.0
Kangaskhan80.0
Torkoal80.4
Swampert81.9
Flygon82.0
Exploud84.0
Dodrio85.2
Blastoise85.5
Makuhita86.4
Sealeo87.6
Tauros88.4
Sharpedo88.8
Feraligatr88.8
Seel90.0
Charizard90.5
Rapidash95.0
Beldum95.2
Nosepass97.0
Venusaur100.0
Tropius100.0
Meganium100.5
Salamence102.6
Graveler105.0
Claydol108.0
Shelgon110.5
Rhyhorn115.0
Scizor118.0
Lairon120.0
Donphan120.0
Dewgong120.0
Rhydon120.0
Exeggutor120.0
Mewtwo122.0
Forretress125.8
Ursaring125.8
Machamp130.0
Wailmer130.0
Slaking130.5
Cloyster132.5
Walrein150.6
Pupitar152.0
Kingdra152.0
Solrock154.0
Arcanine155.0
Milotic162.0
Lunatone168.0
Regice175.0
Raikou178.0
Suicune187.0
Entei198.0
Ho_oh199.0
Tyranitar202.0
Metang202.5
Registeel205.0
Rayquaza206.5
Dragonite210.0
Onix210.0
Lugia216.0
Camerupt220.0
Mantine220.0
Lapras220.0
Regirock230.0
Gyarados235.0
Hariyama253.8
Glalie256.5
Golem300.0
Kyogre352.0
Aggron360.0
Wailord398.0
Steelix400.0
Snorlax460.0
Metagross550.0
Groudon950.0

Making this easier

If you have multiple species that you want to add to pokeemerald but don't want to copy and paste or type everything out multiple times, just use this handy program to generate text with the species name in there! https://github.com/smithk200/making-a-new-pokemon-species-in-pokeemerald

How to use the Testing System

Running Tests

To run all the tests use: make check -j To run specific tests, e.g. Spikes ones, use: make check TESTS="Spikes" To build a ROM (pokemerald-test.elf) that can be opened in mgba to view specific tests, e.g. Spikes ones, use: make pokeemerald-test.elf TESTS="Spikes"

How to Write Tests

Manually testing a battle mechanic often follows this pattern:

  1. Create a party which can activate the mechanic.
  2. Start a battle and play a few turns which activate the mechanic.
  3. Look at the UI outputs to decide if the mechanic works.

Automated testing follows the same pattern:

  1. Initialize the party in GIVEN.
  2. Play the turns in WHEN.
  3. Check the UI outputs in SCENE.

Example 1

As a concrete example, to manually test EFFECT_PARALYZE, e.g. the effect of Stun Spore you might:

  1. Put a Wobbuffet that knows Stun Spore in your party.
  2. Battle a wild Wobbuffet.
  3. Use Stun Spore.
  4. Check that the Wobbuffet is paralyzed.

This can be translated to an automated test as follows:

ASSUMPTIONS
{
    ASSUME(GetMoveEffect(MOVE_STUN_SPORE) == EFFECT_PARALYZE);
}

SINGLE_BATTLE_TEST("Stun Spore inflicts paralysis")
{
    GIVEN {
        PLAYER(SPECIES_WOBBUFFET); // 1.
        OPPONENT(SPECIES_WOBBUFFET); // 2.
    } WHEN {
        TURN { MOVE(player, MOVE_STUN_SPORE); } // 3.
    } SCENE {
        ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player);
        MESSAGE("The opposing Wobbuffet is paralyzed, so it may be unable to move!"); // 4
        STATUS_ICON(opponent, paralysis: TRUE); // 4.
    }
}

The ASSUMPTIONS block documents that Stun Spore has EFFECT_PARALYZE. If Stun Spore did not have that effect it would cause the tests in the file to be skipped. We write our tests like this so that hackers can change the effects of moves without causing tests to fail.

SINGLE_BATTLE_TEST defines the name of the test. Related tests should start with the same prefix, e.g. Stun Spore tests should start with "Stun Spore", this allows just the Stun Spore-related tests to be run with: make check TESTS="Stun Spore"

GIVEN initializes the parties, PLAYER and OPPONENT add a Pokémon to their respective parties. They can both accept a block which further customizes the Pokémon's stats, moves, item, ability, etc.

WHEN describes the turns, and TURN describes the choices made in a single turn. MOVE causes the player to use Stun Spore and adds the move to the Pokémon's moveset if an explicit Moves was not specified. Pokémon that are not mentioned in a TURN use Celebrate. The test runner rigs the RNG so that unless otherwise specified, moves always hit, never critical hit, always activate their secondary effects, and always roll the same damage modifier.

SCENE describes the player-visible output of the battle. In this case ANIMATION checks that the Stun Spore animation played, MESSAGE checks the paralysis message was shown, and STATUS_ICON checks that the opponent's HP bar shows a PRZ icon.

Example 2

As a second example, to manually test that Stun Spore does not effect Grass-types you might:

  1. Put a Wobbuffet that knows Stun Spore in your party.
  2. Battle a wild Oddish.
  3. Use Stun Spore.
  4. Check that the move animation does not play.
  5. Check that a "It doesn't affect Foe Oddish…" message is shown.

This can again be translated as follows:

SINGLE_BATTLE_TEST("Stun Spore does not affect Grass-types")
{
    GIVEN {
        ASSUME(IsPowderMove(MOVE_STUN_SPORE));
        ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_GRASS);
        PLAYER(SPECIES_ODDISH); // 1.
        OPPONENT(SPECIES_ODDISH); // 2.
    } WHEN {
        TURN { MOVE(player, MOVE_STUN_SPORE); } // 3.
    } SCENE {
        NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); // 4.
        MESSAGE("It doesn't affect the opposing Oddish…"); // 5.
    }
}

The ASSUME commands are documenting the reasons why Stun Spore does not affect Oddish, namely that Stun Spore is a powder move, and Oddish is a Grass-type. These ASSUME statements function similarly to the ones in ASSUMPTIONS but apply only to the one test. NOT inverts the meaning of a SCENE check, so applying it to ANIMATION requires that the Stun Spore animation does not play. MESSAGE checks that the message was shown. The checks in SCENE are ordered, so together this says "The doesn't affect message is shown, and the Stun Spore animation does not play at any time before that". Normally you would only test one or the other, or even better, just NOT STATUS_ICON(opponent, paralysis: TRUE); to say that Oddish was not paralyzed without specifying the exact outputs which led to that.

Example 3

As a final example, to test that Meditate works you might:

  1. Put a Wobbuffet that knows Meditate and Tackle in your party.
  2. Battle a wild Wobbuffet.
  3. Use Tackle and note the amount the HP bar reduced.
  4. Battle a wild Wobbuffet.
  5. Use Meditate and that the stat change animation and message play.
  6. Use Tackle and check that the HP bar reduced by more than in 3.

This can be translated to an automated test as follows:

SINGLE_BATTLE_TEST("Meditate raises Attack", s16 damage)
{
    bool32 raiseAttack;
    PARAMETRIZE { raiseAttack = FALSE; }
    PARAMETRIZE { raiseAttack = TRUE; }
    GIVEN {
        ASSUME(GetMoveCategory(MOVE_TACKLE) == DAMAGE_CATEGORY_PHYSICAL);
        PLAYER(SPECIES_WOBBUFFET);
        OPPONENT(SPECIES_WOBBUFFET);
    } WHEN {
        if (raiseAttack) TURN { MOVE(player, MOVE_MEDITATE); } // 5.
        TURN { MOVE(player, MOVE_TACKLE); } // 3 & 6.
    } SCENE {
        if (raiseAttack) {
            ANIMATION(ANIM_TYPE_MOVE, MOVE_MEDITATE, player);
            ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); // 5.
            MESSAGE("Wobbuffet's attack rose!"); // 5.
        }
        ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
        HP_BAR(opponent, captureDamage: &results[i].damage); // 3 & 6.
    } FINALLY {
        EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); // 6.
    }
}

PARAMETRIZE causes a test to run multiple times, once per PARAMETRIZE block (e.g. once with raiseAttack = FALSE and once with raiseAttack = TRUE). The HP_BAR command's captureDamage causes the change in HP to be stored in a variable, and the variable chosen is results[i].damage. results[i] contains all the variables defined at the end of SINGLE_BATTLE_TEST, i is the current PARAMETRIZE index. FINALLY runs after the last parameter has finished, and uses EXPECT_MUL_EQ to check that the second battle deals 1.5× the damage of the first battle (with a small tolerance to account for rounding).

You might notice that all the tests check the outputs the player could see rather than the internal battle state. e.g. the Meditate test could have used gBattleMons[B_POSITION_OPPONENT_LEFT].hp instead of using HP_BAR to capture the damage. This is a deliberate choice, by checking what the player can observe the tests are more robust to refactoring, e.g. if gBattleMons got moved into gBattleStruct then any test that used it would need to be updated.

Note on Overworld Tests

The overworld is not available, so it is only possible to test commands which don't affect the overworld itself, e.g. givemon can be tested because it only alters gPlayerParty, but addobject cannot because it affects object events (which aren't loaded).

REFERENCE

ASSUME

ASSUME(cond) Causes the test to be skipped if cond is false. Used to document any prerequisites of the test, e.g. to test Burn reducing the Attack of a Pokémon we can observe the damage of a physical attack with and without the burn. To document that this test assumes the attack is physical we can use: ASSUME(GetMoveCategory(MOVE_WHATEVER) == DAMAGE_CATEGORY_PHYSICAL);

ASSUMPTIONS

ASSUMPTIONS
{
    ...
}

Should be placed immediately after any #includes and contain any ASSUME statements which should apply to the whole file, e.g. to test EFFECT_POISON_HIT we need to choose a move with that effect, if we chose to use Poison Sting in every test then the top of move_effect_poison_hit.c should be:

ASSUMPTIONS
{
    ASSUME(GetMoveEffect(MOVE_POISON_STING) == EFFECT_POISON_HIT);
}

SINGLE_BATTLE_TEST

SINGLE_BATTLE_TEST(name, results...) and DOUBLE_BATTLE_TEST(name, results...) Define single- and double- battles. The names should start with the name of the mechanic being tested so that it is easier to run all the related tests. results contains variable declarations to be placed into the results array which is available in tests using PARAMETRIZE commands. The main differences for doubles are:

  • Move targets sometimes need to be explicit.
  • Instead of player and opponent there is playerLeft, playerRight, opponentLeft, and opponentRight.

AI_SINGLE_BATTLE_TEST

AI_SINGLE_BATTLE_TEST(name, results...) and AI_DOUBLE_BATTLE_TEST(name, results...) Define battles where opponent mons are controlled by AI, the same that runs when battling regular Trainers. The flags for AI should be specified by the AI_FLAGS command. The rules remain the same as with the SINGLE and DOUBLE battle tests with some differences:

  • opponent's action is specified by the EXPECT_MOVE / EXPECT_SEND_OUT / EXPECT_SWITCH commands
  • we don't control what opponent actually does, instead we make sure the opponent does what we expect it to do
  • we still control the player's action the same way
  • apart from the EXPECTED commands, there's also a new SCORE_ and SCORE__VAL commands

KNOWN_FAILING

KNOWN_FAILING; Marks a test as not passing due to a bug. If there is an issue number associated with the bug it should be included in a comment. If the test passes the developer will be notified to remove KNOWN_FAILING. For example:

SINGLE_BATTLE_TEST("Jump Kick has no recoil if no target")
{
    KNOWN_FAILING; // #2596.
    ...
}

PARAMETRIZE

PARAMETERIZE { parameter; } Runs a test multiple times. i will be set to which parameter is running, and results will contain an entry for each parameter, e.g.:

SINGLE_BATTLE_TEST("Blaze boosts Fire-type moves in a pinch", s16 damage)
{
    u16 hp;
    PARAMETRIZE { hp = 99; }
    PARAMETRIZE { hp = 33; }
    GIVEN {
        ASSUME(GetMoveType(MOVE_EMBER) == TYPE_FIRE);
        PLAYER(SPECIES_CHARMANDER) { Ability(ABILITY_BLAZE); MaxHP(99); HP(hp); }
        OPPONENT(SPECIES_WOBBUFFET);
    } WHEN {
        TURN { MOVE(player, MOVE_EMBER); }
    } SCENE {
        HP_BAR(opponent, captureDamage: &results[i].damage);
    } FINALLY {
        EXPECT(results[1].damage > results[0].damage);
    }
}

PASSES_RANDOMLY

PASSES_RANDOMLY(successes, trials, [tag]) Checks that the test passes successes/trials. If tag is provided, the test is run for each value that the tag can produce. For example, to check that Paralysis causes the turn to be skipped 25/100 times, we can write the following test that passes only if the Pokémon is fully paralyzed and specify that we expect it to pass 25/100 times when RNG_PARALYSIS varies:

SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn")
{
    PASSES_RANDOMLY(25, 100, RNG_PARALYSIS);
    GIVEN {
        PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); }
        OPPONENT(SPECIES_WOBBUFFET);
    } WHEN {
        TURN { MOVE(player, MOVE_CELEBRATE); }
    } SCENE {
        MESSAGE("Wobbuffet couldn't move because it's paralyzed!");
    }
}

All BattleRandom calls involving tag will return the same number, so this cannot be used to have two moves independently hit or miss, for example.

If the tag is not provided, runs the test 50 times and computes an approximate pass ratio. PASSES_RANDOMLY(GetMoveAccuracy(move), 100); Note that this mode of PASSES_RANDOMLY makes the tests run very slowly and should be avoided where possible. If the mechanic you are testing is missing its tag, you should add it.

GIVEN

Given {
    ...
}

Contains the initial state of the parties before the battle.

RNGSeed

RNGSeed(seed) Explicitly sets the RNG seed. Try to avoid using this because it is a very fragile tool. Example:

GIVEN {
    RNGSeed(0xC0DEIDEA);
    ...
}

FLAG_SET

FLAG_SET(flagId) Sets the specified flag. Can currently only set one flag at a time. Cleared between parameters and at the end of the test. Example:

GIVEN {
    FLAG_SET(FLAG_SYS_EXAMPLE_FLAG);
    ...
}

PLAYER and OPPONENT

PLAYER(species) and OPPONENT(species Adds the species to the player's or opponent's party respectively. The Pokémon can be further customized with the following functions:

  • Gender(MON_MALE | MON_FEMALE)
  • Nature(nature)
  • Ability(ability)
  • Level(level)
  • MaxHP(n), HP(n), Attack(n), Defense(n), SpAttack(n), SpDefense(n), Speed(n)
  • Item(item)
  • Moves(moves...)
  • Friendship(friendship)
  • Status1(status1) For example to create a level 42 Wobbuffet that is poisoned: PLAYER(SPECIES_WOBBUFFET) { Level(42); Status1(STATUS1_POISON); } Note if Speed is specified for any Pokémon then it must be specified for all Pokémon. Note if Moves is specified then MOVE will not automatically add moves to the moveset.

AI_FLAGS

AI_FLAGS(flags) Specifies which AI flags are run during the test. Has use only for AI tests. The most common combination is AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT) which is the general 'smart' AI.

WHEN

    ...
} WHEN {
    ...
}

Contains the choices that battlers make during the battle.

TURN

TURN { ... } Groups the choices made by the battlers on a single turn. If Speeds have not been explicitly specified then the order of the MOVE commands in the TURN will be used to infer the Speeds of the Pokémon, e.g.:

     // player's speed will be greater than opponent's speed.
     TURN { MOVE(player, MOVE_SPLASH); MOVE(opponent, MOVE_SPLASH); }
     // opponent's speed will be greater than player's speed.
     TURN { MOVE(opponent, MOVE_SPLASH); MOVE(player, MOVE_SPLASH); }

The inference process is naive, if your test contains anything that modifies the speed of a battler you should specify them explicitly.

MOVE

MOVE(battler, move | moveSlot:, [megaEvolve:], [hit:], [criticalHit:], [target:], [allowed:], [WITH_RNG(tag, value]) Used when the battler chooses Fight. Either the move ID (e.g. MOVE_TACKLE or move slot must be specified.

  • megaEvolve: TRUE causes the battler to Mega Evolve if able
  • hit: FALSE causes the move to miss
  • criticalHit: TRUE causes the move to land a critical hit
  • target: is used in double battles to choose the target (when necessary)
  • allowed: FALSE is used to reject an illegal move e.g. a Disabled one
  • WITH_RNG allows the move to specify an explicit outcome for an RNG tag
     MOVE(playerLeft, MOVE_TACKLE, target: opponentRight);

If the battler does not have an explicit Moves specified the moveset will be populated based on the MOVEs it uses.

FORCED_MOVE

FORCED_MOVE(battler) Used when the battler chooses Fight and then their move is chosen for them, e.g. when affected by Encore.

     FORCED_MOVE(player);

SWITCH

SWITCH(battler, partyIndex) Used when the battler chooses Switch.

     SWITCH(player, 1);

SKIP_TURN

SKIP_TURN(battler) Used when the battler cannot choose an action, e.g. when locked into Thrash.

     SKIP_TURN(player);

SEND_OUT

SEND_OUT(battler, partyIndex) Used when the battler chooses to switch to another Pokémon but not via Switch, e.g. after fainting or due to a U-turn.

     SEND_OUT(player, 1);

USE_ITEM

USE_ITEM(battler, itemId, [partyIndex:], [move:]) Used when the battler chooses to use an item from the Bag. The item ID (e.g. ITEM_POTION) must be specified, and party index and move slot if applicable, e.g:

      USE_ITEM(player, ITEM_X_ATTACK);
      USE_ITEM(player, ITEM_POTION, partyIndex: 0);
      USE_ITEM(player, ITEM_LEPPA_BERRY, partyIndex: 0, move: MOVE_TACKLE);

SCENE

    ...
} SCENE {
    ...
}

Contains an abridged description of the UI during the THEN. The order of the description must match too, e.g.

} SCENE {
     // ABILITY_POPUP followed by a MESSAGE
     ABILITY_POPUP(player, ABILITY_STURDY);
     MESSAGE("Geodude was protected by Sturdy!");
}

ABILITY_POPUP

ABILITY_POPUP(battler, [ability]) Causes the test to fail if the battler's ability pop-up is not shown. If specified, ability is the ability shown in the pop-up.

     ABILITY_POPUP(opponent, ABILITY_MOLD_BREAKER);

ANIMATION

ANIMATION(type, animId, [battler], [target:]) Causes the test to fail if the animation does not play. A common use of this command is to check if a move was successful, e.g.:

     ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);

target can only be specified for ANIM_TYPE_MOVE.

EXPERIENCE_BAR

EXPERIENCE_BAR(battler, [exp: | captureGainedExp:]) If exp: is used, causes the test to fail if that amount of experience is not gained, e.g.:

     EXPERIENCE_BAR(player, exp: 0);

If captureGainedExp: is used, causes the test to fail if the Experience bar does not change, and then writes that change to the pointer, e.g.:

     u32 exp;
     EXPERIENCE_BAR(player, captureGainedExp: &exp);

If none of the above are used, causes the test to fail if the Exp does not change at all. Please note that due to nature of tests, this command is only usable in WILD_BATTLE_TEST and will fail elsewhere.

HP_BAR

HP_BAR(battler, [damage: | hp: | captureDamage: | captureHP:]) If hp: or damage: are used, causes the test to fail if that amount of damage is not dealt, e.g.:

     HP_BAR(player, hp: 0);

If captureDamage: or captureHP: are used, causes the test to fail if the HP bar does not change, and then writes that change to the pointer, e.g.:

     s16 damage;
     HP_BAR(player, captureDamage: &damage);

If none of the above are used, causes the test to fail if the HP does not change at all.

MESSAGE

MESSAGE(pattern) Causes the test to fail if the message in pattern is not displayed. Spaces in pattern match newlines (\n, \l, and \p) in the message. Often used to check that a battler took its turn but it failed, e.g.:

     MESSAGE("Wobbuffet used Dream Eater!");
     MESSAGE("The opposing Wobbuffet wasn't affected!");

STATUS_ICON

STATUS_ICON(battler, status1 | none: | sleep: | poison: | burn: | freeze: | paralysis:, badPoison:) Causes the test to fail if the battler's status is not changed to the specified status.

     STATUS_ICON(player, badPoison: TRUE);

If the expected status icon is parametrized the corresponding STATUS1 constant can be provided, e.g.:

     u32 status1;
     PARAMETRIZE { status1 = STATUS1_NONE; }
     PARAMETRIZE { status1 = STATUS1_BURN; }
     ...
     STATUS_ICON(player, status1);

NOT

NOT sceneCommand Causes the test to fail if the SCENE command succeeds before the following command succeeds.

     // Our Wobbuffet does not Celebrate before the foe's.
     NOT MESSAGE("Wobbuffet used Celebrate!");
     MESSAGE("The opposing Wobbuffet used Celebrate!");

NOTE: If this condition fails, the viewable ROM freezes at the NOT command. WARNING: NOT is an alias of NONE_OF, so it behaves surprisingly when applied to multiple commands wrapped in braces.

ONE_OF

    ONE_OF {
        ...
    }

Causes the test to fail unless one of the SCENE commands succeeds.

     ONE_OF {
         MESSAGE("Wobbuffet used Celebrate!");
         MESSAGE("Wobbuffet couldn't move because it's paralyzed!");
     }

NONE_OF

    NONE_OF {
        ...
    }

Causes the test to fail if one of the SCENE commands succeeds before the command after the NONE_OF succeeds.

     // Our Wobbuffet does not move before the foe's.
     NONE_OF {
         MESSAGE("Wobbuffet used Celebrate!");
         MESSAGE("Wobbuffet couldn't move because it's paralyzed!");
     }
     MESSAGE("The opposing Wobbuffet used Celebrate!");

PLAYER_PARTY

Refer to the party members defined in GIVEN, e.g.:

     s32 maxHP = GetMonData(&PLAYER_PARTY[0], MON_DATA_MAX_HP);
     HP_BAR(player, damage: maxHP / 2);

OPPONENT_PARTY

Refer to the party members defined in GIVEN, e.g.:

     s32 maxHP = GetMonData(&OPPONENT_PARTY[0], MON_DATA_MAX_HP);
     HP_BAR(opponent, damage: maxHP / 2);

THEN

    ...
} THEN {
    ...
}

Contains code to run after the battle has finished. If the test is using PARAMETRIZE commands then EXPECT commands between the results should go here. Is also occasionally used to check the internal battle state when checking the behavior via a SCENE is too difficult, verbose, or error-prone.

FINALLY

    ...
} FINALLY {
    ...
}

Contains checks to run after all PARAMETERIZE commands have run. Prefer to write your checks in THEN where possible, because a failure in THEN will be tagged with which parameter it corresponds to.

EXPECT

EXPECT(cond) Causes the test to fail if cond is false.

EXPECT_XX

EXPECT_EQ(a, b) a == b

EXPECT_NE(a, b) a != b

EXPECT_LT(a, b) a < b

EXPECT_LE(a, b) a <= b

EXPECT_GT(a, b) a > b

EXPECT_GE(a, b) a >= b

Causes the test to fail if a and b compare incorrectly, e.g.

     EXPECT_EQ(results[0].damage, results[1].damage);

EXPECT_MUL_EQ

EXPECT_MUL_EQ(a, m, b) Causes the test to fail if a*m != b (within a threshold), e.g.

     // Expect results[0].damage * 1.5 == results[1].damage.
     EXPECT_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);

FORCE_MOVE_ANIM

FORCE_MOVE_ANIM(TRUE) Forces the moves in the current test to do their animations in headless mode. Useful for debugging animations.

Overworld Command Reference

OVERWORLD_SCRIPT

OVERWORLD_SCRIPT(instructions...) Returns a pointer to a compiled overworld script. Cannot be used to initialize global const data, although the pointer IS to const data. Note that each script command must be followed by a ;, e.g.:

const u8 *myScript = OVERWORLD_SCRIPT(
    random 2;
    addvar VAR_RESULT, 1;
);

RUN_OVERWORLD_SCRIPT

RUN_OVERWORLD_SCRIPT(instructions...) Runs an overworld script in the immediate script context, which means that commands like waitstate are not supported.

     RUN_OVERWORLD_SCRIPT(
         setvar VAR_RESULT, 3;
     );
     EXPECT_EQ(GetVar(VAR_RESULT), 3);

Adding New Trainer Slides

Define Slides Per Trainer

We are going to add a Trainer Slide to Wally's first Victory Road battle, before he Mega Evolves his Gallade. This battle takes place outside a Battle Facility, so sTrainerSlides must be edited.

src/trainer_slide.c

+ const u8 gText_ThatsTheWay[] = _("That's the way, Gallade! Go!{PAUSE_UNTIL_PRESS}");

static const u8* const sTrainerSlides[DIFFICULTY_COUNT][TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
{
    [DIFFICULTY_NORMAL] =
    {
+        [TRAINER_WALLY_VR_1] = // use the Trainer's Id from include/constants/opponents.h
+        {
+            [TRAINER_SLIDE_MEGA_EVOLUTION] = COMPOUND_STRING("That's the way, Gallade! Go!{PAUSE_UNTIL_PRESS}"), // find the id for the slide to be used.
+            //[TRAINER_SLIDE_MEGA_EVOLUTION] = gText_ThatsTheWay, // You can use globals or COMPOUND_STRING to define text here.
+        }
    },
};

If we were to edit a Trainer that appears in a Battle Facility, sFrontierTrainerSlides would be edited instead. Here, we'll give Anabel a line before she uses a Z-Move.

src/trainer_slide.c

static const u8* const sFrontierTrainerSlides[DIFFICULTY_COUNT][FRONTIER_TRAINERS_COUNT][TRAINER_SLIDE_COUNT] =
{
    [DIFFICULTY_NORMAL] =
    {
+        [TRAINER_ANABEL] =
+        {
+            [TRAINER_SLIDE_Z_MOVE] = COMPOUND_STRING("Victory...is ours!"), //{PAUSE_UNTIL_PRESS} is omitted, so the battle will continue as soon as the next is finished printing.
+        }
    },
};

Add New Slides

include/constants/trainer_slide.h

enum TrainerSlideType
{
    TRAINER_SLIDE_BEFORE_FIRST_TURN,
    TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT,
+   TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT, // Each Trainer Slide has a unqiue id. 
    TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT,
    TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE,
    TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN,
    TRAINER_SLIDE_ENEMY_MON_UNAFFECTED,
    TRAINER_SLIDE_LAST_SWITCHIN,
    TRAINER_SLIDE_LAST_HALF_HP,
    TRAINER_SLIDE_LAST_LOW_HP,
    TRAINER_SLIDE_MEGA_EVOLUTION,
    TRAINER_SLIDE_Z_MOVE,
    TRAINER_SLIDE_DYNAMAX,
    TRAINER_SLIDE_COUNT,
};

Each Trainer Slide has a unique id and needs to be added to this list.

include/trainer_slide.h

If your new Trainer Slide needs to check for beforen initalized, a function is declared here to be used externally. Critical hits are used to initalize this Trainer Slide but the slide doesn't play instantly, so we will create an function to initialize it.

void SetTrainerSlideMessage(enum DifficultyLevel, u32, u32);
void TryInitializeFirstSTABMoveTrainerSlide(u32, u32, u32);
void TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(u32);
+ void TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(u32);
void TryInitializeTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32);
void TryInitializeTrainerSlideEnemyMonUnaffected(u32);
bool32 IsTrainerSlideInitialized(enum TrainerSlideType);

src/trainer_slide.c

     return IsTrainerSlideInitialized(slideId);
 }
 
+static bool32 ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(enum TrainerSlideType slideId)
+{
+    return IsTrainerSlideInitialized(slideId);
+}
+
 static bool32 ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32 battler, enum TrainerSlideType slideId)
 {
     if (!IsTrainerSlideInitialized(slideId))
         case TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT:
             shouldRun = ShouldRunTrainerSlidePlayerLandsFirstCriticalHit(slideId);
             break;
+        case TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT:
+            shouldRun = ShouldRunTrainerSlideEnemyLandsFirstCriticalHit(slideId);
+            break;
         case TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT:
             shouldRun = ShouldRunTrainerSlidePlayerLandsFirstSuperEffectiveHit(battler, slideId);
             break;

The function that determines if a Slide should play has different function for most Slides. We need to check if this Slide was initialized by a critical hit previously, so a function is created here. This function and the Id and then added to the switch statement in ShouldDoTrainerSlide.

     InitalizeTrainerSlide(slideId);
 }
 
+void TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(u32 target)
+{
+    enum TrainerSlideType slideId = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT;
+
+    if (IsSlideInitalizedOrPlayed(slideId))
+        return;
+
+    if (!IsOnPlayerSide(target))
+        return;
+
+    InitalizeTrainerSlide(slideId);
+}
+

The function to check if this slide SHOULD be initalized is added to the bottom of this file.

src/battle_main.c

         BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
     else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT)))
         BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
+    else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT)))
+        BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
     else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT)))
         BattleScriptExecute(i == 1 ? BattleScript_TrainerASlideMsgEnd2 : BattleScript_TrainerBSlideMsgEnd2);
     else if ((i = ShouldDoTrainerSlide(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE)))
diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c

In BattleTurnPassed, most Trainer Slides are checked to see if they should run, so our new call is added here.

src/battle_script_commands.c

         {
             PrepareStringBattle(STRINGID_CRITICALHIT, gBattlerAttacker);
 
+            TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(gBattlerTarget);
             TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(gBattlerTarget);
 
             gBattleCommunication[MSG_DISPLAY] = 1;

The actual usage of TryInitializeTrainerSlideEnemyLandsFirstCriticalHit is added and is checked whenever a critical hit is scored.

test/battle/trainer_slides.c

     }
 }
 
+SINGLE_BATTLE_TEST("Trainer Slide: Enemy Lands First Critical Hit")
+{
+    gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT;
+
+    GIVEN {
+        ASSUME(GetMoveEffect(MOVE_LASER_FOCUS) == EFFECT_LASER_FOCUS);
+        PLAYER(SPECIES_WOBBUFFET);
+        OPPONENT(SPECIES_WOBBUFFET);
+    } WHEN {
+        TURN { MOVE(opponent, MOVE_LASER_FOCUS); }
+        TURN { MOVE(opponent, MOVE_TACKLE); }
+    } SCENE {
+        ANIMATION(ANIM_TYPE_MOVE, MOVE_LASER_FOCUS, opponent);
+        ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent);
+        MESSAGE("A critical hit!");
+        MESSAGE("This message plays after the enemy lands their first critical hit.{PAUSE_UNTIL_PRESS}");
+    }
+}
+
 SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First STAB Hit")
 {
     gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE;
diff --git a/test/battle/trainer_slides.h b/test/battle/trainer_slides.h

test/battle/trainer_slides.h


         [TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT] = COMPOUND_STRING("This message plays after the player lands their first critical hit.{PAUSE_UNTIL_PRESS}"),
     },
+    [TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] =
+    {
+        [TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] = COMPOUND_STRING("This message plays after the enemy lands their first critical hit.{PAUSE_UNTIL_PRESS}"),
+    },
     [TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] =
     {
         [TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] = COMPOUND_STRING("This message plays after the player lands their first super effective hit.{PAUSE_UNTIL_PRESS}"),

Tests are added to make sure the new Trainer Slide works. A test is added to the c file, and the trainer to run the entry in the test is added to sTestTrainerSlides.

Day/Night system FAQ

Q: How do I disable DNS?

A: Set OW_ENABLE_DNS to FALSE in include/config/overworld.h.

Q: What map changes should be made for DNS?

A: By default, the only Hoenn map changes that need to be made are to edit the Lavaridge Town map to change the metatiles the two old ladies are on in the hot springs to be the normal hot spring water tile. This is to avoid a visual bug from when OW_OBJECT_VANILLA_SHADOWS is FALSE.

However, by default no maps have lighting effects of any kind. The rest of this tutorial is to aid in adding lighting effects.

If you intend to use vanilla maps and have not already edited them, revert commit a5b079d833f18f66ebd53ac77f00227ae4a1f389. This commit includes a lot of tileset, metatile, and palette changes to accommodate light-blending, and applies OBJ_EVENT_GFX_LIGHT_SPRITE where they make sense.

If you have edited vanilla maps, the merge conflicts from reverting that commit will cause problems. If you are using vanilla maps, manually copy some of the tileset changes, .pal, and .pla files in your branch, and begin rebuilding your metatiles to have windows use the palettes that have a .pla for light blending the correct color slots. Triple-layer metatiles are highly recommended.

You will also want to add the lighting object events from that commit.

If you are not using Hoenn maps, the primary concern is that you do not use the exact same palette indices for colors you want to be darkened during night time and colors you want to light up. Err towards not light blending a color if you aren't sure how to avoid conflicts.

When writing map scripts, fadescreenswapbuffers should be preferred over fadescreen. This is to avoid odd behavior from the GBA's limitations in alpha blending.

Q: How do I make lightbulbs glow?

Rustboro before adding lamp object events Rustboro after adding lamp object events

A: Making lamps glow is not part of the tileset itself. Instead, place certain object events on top of where you desire a glowing effect.

These object events should use OBJ_EVENT_GFX_LIGHT_SPRITE and then as their trainer_sight_or_berry_tree_id (called Sight Radius/Berry Tree ID in porymap), use LIGHT_TYPE_BALL for round lights (such as candles or gas lamps), LIGHT_TYPE_PKMN_CENTER_SIGN over a Pokémon Center sign, or LIGHT_TYPE_POKE_MART_SIGN over a Pokémart sign.

Q: How do I mark certain colors in a palette as light-blended?

A: Create a .pla file in the same folder as the .pal with the same name. This can be done on any kind of palette; the commit to revert listed up above only applies it to tilesets, but you could easily do it for object events as well. Of note, there is a commit reverted for being out of scope to make a follower Ampharos's tail glow.

In this file you can enter color indices [0,15] on separate lines to mark those colors as being light-blended, i.e:

06.pla:

# A comment
0 # if color 0 is listed, uses it to blend with instead of the default!
1
9
10

During the day time, these color indices appear as normal, but will be blended with either yellow or the 0 index at night. These indices should only be used for things you expect to light up. If you are using porytiles, palette overrides and using slight alterations to a color will aid you in avoiding color conflicts where the wrong index is assigned.

Rustboro gym after light-blending the windows

The windows appear as normal during the day time (blue) and light up in the night. These use the default color.

Q: How do I return to using regular shadows?

A: Set OW_OBJECT_VANILLA_SHADOWS to TRUE in include/config/overworld.h.

Q: What graphical errors are likely to occur while using DNS?

A: If you have OW_POPUP_GENERATION set to GEN_5 and OW_POPUP_BW_ALPHA_BLEND set to TRUE, you may have color errors during map popups. This is due to the GBA being severely limited in use of color blending and having both the overworld blended and the popup blended is difficult.

If you have OW_OBJECT_VANILLA_SHADOWS set to TRUE, this will also cause visual errors.

Any other graphical error should be reported.

Q: How do I disable shadows for certain locations?

A: Shadows can be disabled for certain locations by modifying the CurrentMapHasShadows function in src/overworld.c.

Pokeemerald-Expansion Changelogs

1.10.x

1.9.x

1.8.x

1.7.x

1.6.x

1.5.x

1.4.x

1.3.x

1.2.x

1.1.x

1.0.x

Pre-1.0.x:

## How to update
- If you haven't set up a remote, run the command `git remote add RHH https://github.com/rh-hideout/pokeemerald-expansion`.
- Once you have your remote set up, run the command `git pull RHH expansion/1.12.0
`.

🌋 REFACTORS 🌋

📜 = Uses a migration script.

  • Minor Recoil Effect refactor by @AlexOn1ine in #6409
  • Refactor HasWeatherEffect and IsUnnerveAbilityOnOpposingSide by @AlexOn1ine in #6441
  • Refactor the function IsAbilityPreventingEscape by @AlexOn1ine in #6439
  • Unifies dynamic move category checks by @AlexOn1ine in #6443
  • Refactors faint target abilities and adds Battle Bond config by @AlexOn1ine in #6519
  • Refactor protect to allow at least 126 different types of protect eff… by @AlexOn1ine in #6506
  • Refactor / Simlify Cmd_adjustdamage and remove redundancy by @AlexOn1ine in #6499
  • Refactors Move absorb / block function calls to remove redundancy by @AlexOn1ine in #6490
  • Refactor fixed damage moves by @AlexOn1ine in #6449
  • EndTurnEffectOrder Refactor by @AlexOn1ine in #6224
  • Refactor hit escape moves to use moveend by @AlexOn1ine in #6671
  • 📜 Evolution Refactor by @khbsd, @AsparagusEduardo for everything he did, including #6530! and @AlexOn1ine for writing the migration script! in #6556
    • make sure to back up your alcremie evolution data if you have it implemented already: pulling this will overwrite it
  • Centralizes non volatile status effect checks by @AlexOn1ine in #6533
  • Refactor battler flee and watch options + clean up by @AlexOn1ine in #6724
  • Refactor AI flags to u64 by @Pawkkie and @DizzyEggg, @AlexOn1ine, @hedara90 in #6753
  • Refactor battler message ids by @AlexOn1ine in #6765
  • Refactor stockpile resets by @AlexOn1ine in #6810
  • Move out ai structs out of BattleResources by @AlexOn1ine in #6741
  • Consolidates a bunch of battle controller functions by @Bassoonian in #6838
  • Further moveend changes (move blocks) by @AlexOn1ine in #6820
  • Added weather accuracy move flags by @AsparagusEduardo in #6857
    • Removed EFFECT_THUNDER, EFFECT_BLIZZARD and EFFECT_RAIN_ALWAYS_HIT effects in favor of alwaysHitsInRain, alwaysHitsInHailSnow and accuracy50InSun move flags.
  • Remove non-volatile status EFFECTs by @AsparagusEduardo in #6772
  • 📜 Remove compressed palettes by @hedara90 in #6455
    • All handling for compressed palettes has been removed, use u16 * for palettes instead.
    • Has migration script to convert palettes to u16, struct SpritePalette and palette loading calls from their compressed variants.

🧬 General 🧬

Added

  • Low Health Beeps Configuration by @khbsd in #6328
  • Adds SHOW_TYPES_SEEN to the B_SHOW_TYPES option by @khbsd in #6561
    • this slightly alters how the flag FLAG_GET_SEEN is set, so keep that in mind if you have any functionality that relies on it being at the start of the battle.
  • Type Effectiveness Indicators when selecting moves by @agsmgmaster64 in #6559
  • FakeRtc datetime and ResetRtcScreen day increments by @cawtds in #5695
  • Battle Transition: mugshots for multibattles by @grintoul1 and @AlexOn1ine for advice on simplifying the conditions that check for 2nd opponent and presence of battle partner, and general advice/troubleshooting in #6567
  • Time menu for RTC in debug menu. by @RubyRaven6, @cawtds for RTC refactors that makes all of this easier, @HashtagMarky for helping me understand how the debug menu works, especially with destroying menus, @zetraphes, for helping with refactoring the redundant functions into a single function. and @makosear for originally getting me into the RTC hellhole in #6634
    • In order to apply the new time the map is reloaded through The LoadMap Callback (CB2_LoadMap). As such, this will also do everything else that comes with loading map in.
  • Pokedex Plus HGSS move configs by @PCG06 in #6687 Adds three configs for Pokedex Plus HGSS.
    • To sort TM moves by num.
    • To show egg moves for evolved Pokémon.
    • Creating a dynamic tutor moves array to use for tutor moves counting.
  • Allow different flags for Badge boost by @AsparagusEduardo and @ghoulslash, @klemniops, whose' code I was using for comparison when I found these in #6805
  • Timemacros by @RubyRaven6 in #6696

Changed

  • Rename absentBattlerFlags to absent by @AlexOn1ine in #6432
  • Removed unused debug EWRAM variables by @hedara90 in #6468
  • Removes unused Special Status members by @AlexOn1ine in #6479
  • Remove potential pitfall with saved damage for recoil moves by @AlexOn1ine in #6485
  • Add CONTRIBUTING.md and STYLEGUIDE.md by @pkmnsnfrn and @Pawkkie, @garakmon in #6340
  • Alcremie Evolution clean up follow up by @AlexOn1ine in #6557
  • Fix broken links in merge_checklist.md by @pkmnsnfrn and @grintoul1 in #6570
  • Converts some defines to enums by @Bassoonian in #6592
  • Removes vestigial EWRAM variable from debug menu. by @RubyRaven6 in #6686
  • Keep Steven Multi Debug Option at the Bottom Forever by @Pawkkie in #6694
  • Better handling of battle coords by @AsparagusEduardo and @ghoulslash, @klemniops for the original code. in #6787
  • battle_main enum cleanup by @AsparagusEduardo and @ghoulslash, @klemniops, whose' code I was using for comparison when I found these in #6788
  • Add missing uses of IsBattlerAlly by @AsparagusEduardo and @ghoulslash, @klemniops, whose' code I was using for comparison when I found these in #6793
  • Adds partyState to BattleStruct by @AlexOn1ine in #6783
  • Rename GetPartyBattlerData to GetBattlerMon by @AsparagusEduardo and @ghoulslash, @klemniops, whose' code I was using for comparison when I found these in #6800
  • Added JSON as the preferred data format by @hedara90 in #6801
  • Remove non-existent functions from header files by @Bassoonian in #6825
  • Added IsOnPlayerSide by @AsparagusEduardo in #6832
  • Battle TV expansion by @AsparagusEduardo in #6826
  • Prevent scaninc touching tests when make-ing non-test builds by @mudskipper13 in #6812
  • Reword move and ability descriptions to improve clarity by @jfb1337 in #6861
    • Updated move and ability descriptions to increase clarity.
  • Optimized PokemonUseItemEffects function by @AsparagusEduardo in #6878
  • Expand trainerproc to support additional battle types by @AsparagusEduardo in #6770
  • Debug menu cleanup by @AsparagusEduardo in #6881
  • Commenting in config files to explain limits of map popups and wild AI flags by @surskitty in #6923
  • Documented usage of createsprite gSimplePaletteBlendSpriteTemplate by @AsparagusEduardo in #6956
  • Documented usage of createsprite gDirtPlumeSpriteTemplate by @AsparagusEduardo in #6954
  • Updated CONTRIBUTING.md and Merge Freeze definition by @pkmnsnfrn in #6952
  • Documented usage of createsprite gBasicHitSplatSpriteTemplate by @AsparagusEduardo in #6950
  • Documented usage of createsprite gSparkElectricitySpriteTemplate by @AsparagusEduardo in #6955
  • Documented usage of createsprite gSlideMonToOffsetSpriteTemplate by @AsparagusEduardo in #6953
  • Remove trailing whitespace by @AsparagusEduardo in #6968
  • Beefing up the DNS tutorial for some of the common questions/errors. by @surskitty in #6922
  • Clarifies when to use each merge type for maintainers by @pkmnsnfrn in #6999
  • Moved time constants to dedicated files by @pkmnsnfrn in #7019

Fixed

  • fix: missing break statement by @khbsd in #6572
  • Fix dynamax message by @hedara90 in #6589
  • Fix pc mon pic by @hedara90 in #6709
  • Fixed upcoming not compiling in gcc 11.4.0 by @AsparagusEduardo in #6710
  • Fix the Move Item option in the PC still expecting compressed palettes by @hedara90 in #6739
  • Fix upcoming compile by @AsparagusEduardo in #6804
  • Added missing root folder check in trainer battle type migration script by @AsparagusEduardo in #6944
  • Fixed forfeit money for B_WHITEOUT_MONEY <= GEN_3 by @AsparagusEduardo in #6942
  • Fix UB when using time menu by @cawtds in #7023

🗺️ Overworld 🗺️

Added