How to docs and fixes to be added to the mdbook documentation site (#5070)

* Added most of the documentation from the wiki to the mdbook site directory and fixed some errors

* Removed the infinite repel documentation file

* Update docs/SUMMARY.md

---------

Co-authored-by: Eduardo Quezada <eduardo602002@gmail.com>
This commit is contained in:
toon 2024-08-05 15:26:14 +02:00 committed by GitHub
parent 41a79e3833
commit fd3cb6f96b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 6143 additions and 0 deletions

View file

@ -4,6 +4,17 @@
- [Installation](./INSTALL.md)
- [Setting up WSL1 (Legacy Portion)](./legacy_WSL1_INSTALL.md)
- [AI Flags](./ai_flags.md)
- [Tutorials]()
- [How to add new AI Flags](./ai_logic.md)
- [How to add new battle script commands/macros](./how_to_battle_script_command_macro.md)
- [How to add a new move](./how_to_new_move.md)
- [How to add a new trainer class](./how_to_trainer_class.md)
- [How to add a new Pokémon]()
- [v1.9.0](./how_to_new_pokemon_1_9_0.md)
- [v1.8.0](./how_to_new_pokemon_1_8_0.md)
- [v1.7.0](./how_to_new_pokemon_1_7_0.md)
- [v1.6.0](./how_to_new_pokemon_1_6_0.md)
- [How to use the Testing System](./how_to_testing_system.md)
- [Changelog](./CHANGELOG.md)
- [1.9.x]()
- [Version 1.9.0](changelogs/1.9.x/1.9.0.md)

31
docs/ai_logic.md Normal file
View file

@ -0,0 +1,31 @@
# 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:
```c
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!

View file

@ -0,0 +1,51 @@
## How to add new Battle Script Commands/Macros
To preface this tutorial, the battle engine upgrade has exhausted all battle script command IDs, and instead uses the `various` command to effectively add new commands. 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 `various` command is set up.
```
.macro various battler:req, param1:req
.byte 0x76
.byte \battler
.byte \param1
.endm
```
`various` is 3 bytes in size, so if we wanted to advance to the next battle script command, we would write `gBattlescriptCurrInstr += 3`. Coincidentally, this is found at the end of `Cmd_Various` in `src/battle_script_commands.c`.
Now, how might we add a custom various command case? Here are the steps. We will use `VARIOUS_SET_SIMPLE_BEAM` as an example.
### 1. Add a definition to `include/constants/battle_script_commands.h`.
For example, `#define VARIOUS_SET_SIMPLE_BEAM 39`
### 2. Create a macro in `asm/macros/battle_script.inc`. For example:
```c
.macro setabilitysimple battler:req, ptr:req
various \battler VARIOUS_SET_SIMPLE_BEAM
.4byte \ptr
.endm
```
### 3. Add your new various command ID to `Cmd_Various`. For example:
```c
case VARIOUS_SET_SIMPLE_BEAM:
if (IsEntrainmentTargetOrSimpleBeamBannedAbility(gBattleMons[gBattlerTarget].ability)
|| gBattleMons[gBattlerTarget].ability == ABILITY_SIMPLE)
{
gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3);
}
else
{
gBattleMons[gBattlerTarget].ability = ABILITY_SIMPLE;
RecordAbilityBattle(gActiveBattler, ABILITY_SIMPLE);
gBattlescriptCurrInstr += 7;
}
return;
```
The macros' `battler` argument is the battler who will be affected/considered by your command. In our case, which battler we will try to give `ABILITY_SIMPLE`. Note that `gActiveBattler` is always set to this battler at the beginning of `Cmd_Various`.
The `ptr` argument is an extra argument that, in this case, provides a battle script to jump to in the event that we fail to set `ABILITY_SIMPLE`. We must add the `.4byte \ptr` inside our macro. So now when we want to advance to the next battle script command in our script, we must increment `gBattlescriptCurrInstr` by `7` because our overall macro is 3 bytes for the various command, and 4 bytes for the pointer. *IMPORTANT* the `return` at the end of the switch case is required because remember that `various` always defaults to `gBattlescriptCurrInstr += 3` at the very end of the function, so if we included `gBattlescriptCurrInstr += 7` with a `break`, we would end up effectively doing `gBattlescriptCurrInstr += 10`.
This behavior can be found under the `else` statement in the example above, corresponding to `ABILITY_SIMPLE` being correctly applied. If we are unable to set `ABILITY_SIMPLE`, however, notice the following `gBattlescriptCurrInstr = T1_READ_PTR(gBattlescriptCurrInstr + 3);`. This means we are jumping to the battle script provided by the pointer 3 bytes after our various command (which is the `ptr` argument described previously). We still must `return` or else we would actually jump to 3 bytes after the `ptr` battle script begins.

197
docs/how_to_new_move.md Normal file
View file

@ -0,0 +1,197 @@
*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_xxx`s.
- 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](#key-files-and-definitions)
- [Header files](#header-files)
- [C files](#c-files)
- [Script files](#script-files)
2. [Editing a move](#editing-a-move)
3. [Adding a new move](#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:
```c
[MOVE_THUNDER_SHOCK] =
{
.name = HANDLE_EXPANDED_MOVE_NAME("ThunderShock", "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},
},
```
The `HANDLE_EXPANDED_MOVE_NAME` allows the usage of a name of extended character length, so long as the `B_EXPANDED_MOVE_NAMES` is set to `TRUE`, whereas by default it's limited in Gen 3 to 12 characters. 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`](#databattle_scripts_1s), and any *variable* characteristics such as typing or power are defined in either [`src/battle_script_commands.c`](#srcbattle_script_commandsc) or [`src/battle_util.c`](#srcbattle_utilc), 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`](#includeconstantsbattleh) and encoded under `SetMoveEffect` in [`src/battle_script_commands.c`](#srcbattle_script_commandsc). 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`](#databattle_scripts_1s). The indices/names of the effects (e.g. `EFFECT_FIRST_TURN_ONLY`) are enums defined in [`include/constants/battle_move_effects.h`](#includeconstantsbattle_move_effectsh).
### include/battle_scripts.h
Contains references to scripts [`data/battle_scripts_1.s`](#databattle_scripts_1s), 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`](#srcdatabattle_move_effectsh).
### 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`](#srcbattle_messagec).
### 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`](#databattle_scripts_1s) and [`src/battle_script_commands.c`](#srcbattle_script_commandsc). 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`](#srcbattle_script_commandsc). 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`](#srcbattle_script_commandsc) 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`](#srcbattle_script_commandsc), whereas `callnative` will let you directly call a function in [`src/battle_script_commands.c`](#srcbattle_script_commandsc) 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`](#srcdatamoves_infoh). 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`](#srcdatabattle_move_effectsh). 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`](#databattle_scripts_1s) and any relevant functions in [`src/battle_script_commands.c`](#srcbattle_script_commandsc). To change how a move's dynamic power, accuracy and are calculated, then you need to modify the following functions:
- For power: `CalcMoveBasePowerAfterModifiers` in [`src/battle_util.c`](#srcbattle_utilc)
- For accuracy: `AccuracyCalcHelper` in [`src/battle_script_commands.c`](#srcbattle_script_commandsc)
- For type: `SetTypeBeforeUsingMove` in [`src/battle_main.c`](#srcbattle_mainc)
Note: A generic function for calculating category does not currently exist - Photon Geyser's script in [`data/battle_scripts_1.s`](#databattle_scripts_1s) uses a special `callnative` function `BS_SetPhotonGeyserCategory`.
### Changing a move's additional effects
If you look at the example [here](#srcdatamoves_infoh), 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_RAPID_SPIN,
},{
.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:
- a define in [`include/constants/moves.h`](#includeconstantsmovesh)
- an info entry in [`src/data/battle_moves.h`](#srcdatamoves_infoh)
- an animation entry in [data/battle_anim_scripts.s](#databattle_anim_scriptss)
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.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,578 @@
# 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(gMovesInfo[MOVE_STUN_SPORE].effect == 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("Foe Wobbuffet is paralyzed! 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(gMovesInfo[MOVE_STUN_SPORE].powderMove);
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 Foe 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(gMovesInfo[MOVE_TACKLE].category == 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(gMovesInfo[MOVE_WHATEVER].category == 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(gMovesInfo[MOVE_POISON_STING].effect == 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(gMovesInfo[MOVE_EMBER].type == 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 is paralyzed! It can't move!");
}
}
```
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(gMovesInfo[move].accuracy, 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 `MOVE`s 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("Foe 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("Foe 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 is paralyzed! It can't move!");
}
```
### `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 is paralyzed! It can't move!");
}
MESSAGE("Foe 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);
```
## 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);
```

View file

@ -0,0 +1,178 @@
# How to add a new trainer class
## Content
* [Quick Summary](#quick-summary)
* [The Graphics](#the-graphics)
* [1. Edit the sprites](#2-edit-the-sprites)
* [2. Register the sprites](#2-register-the-sprites)
* [3. The Animation](#2-the-animation)
* [4. Connecting pictures to the data](#2-connecting-pictures-to-the-data)
* [The Data](#the-data)
* [5. Defining the trainer class](#2-defining-the-trainer-class)
* [Usage](#usage)
## Quick Summary
(Page contains out of date information, [new instructions for Sprites here](https://github.com/rh-hideout/pokeemerald-expansion/pull/3597).)
If you've done this before and just need a quick lookup, here's what files you need:
1. GFX into [graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics)
2. Palette into [graphics/trainers/palettes](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/palettes)
3. Register sprites to [include/graphics.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/graphics.h)
4. Point game to where graphic files are found: [src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h)
5. Add animation to: [src/data/trainer_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_anims.h)
6. Add the trainer to all three structs in: [src/data/trainer_graphics/front_pic_table.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_table.h)
7. Add trainer to [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h)
## The Graphics
### 1. Edit the sprites
We will start with a graphic that we want to use for our new trainer class. Unlike with adding Pokémon, the trainer sprites aren't sorted in individual folders, but rather in one folder:
[graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics)
**Remember to limit yourself to 16 colors including transparency in the first slot!**
Export the pallette and place into the same folder.
### 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.
Edit [include/graphics.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/graphics.h):
```diff
extern const u32 gTrainerFrontPic_RubySapphireMay[];
+ extern const u32 gTrainerFrontPic_myTrainerClass[];
extern const u32 gTrainerPalette_Hiker[];
...
...
extern const u32 gTrainerPalette_RubySapphireMay[];
+ extern const u32 gTrainerPalette_myTrainerClass[];
extern const u8 gTrainerBackPic_Brendan[];
```
Now link the graphic files.
[src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h):
```diff
const u32 gTrainerPalette_RubySapphireBrendan[] = INCBIN_U32("graphics/trainers/palettes/ruby_sapphire_brendan.gbapal.lz");
const u32 gTrainerFrontPic_RubySapphireMay[] = INCBIN_U32("graphics/trainers/front_pics/ruby_sapphire_may_front_pic.4bpp.lz");
const u32 gTrainerPalette_RubySapphireMay[] = INCBIN_U32("graphics/trainers/palettes/ruby_sapphire_may.gbapal.lz");
+ const u32 gTrainerFrontPic_Sheriff[] = INCBIN_U32("graphics/trainers/front_pics/myTrainerClass_front_pic.4bpp.lz");
+ const u32 gTrainerPalette_Sheriff[] = INCBIN_U32("graphics/trainers/palettes/myTrainerClass.gbapal.lz");
const u8 gTrainerBackPic_Brendan[] = INCBIN_U8("graphics/trainers/back_pics/brendan_back_pic.4
```
### 3. The Animation
Add the Animation of the trainer here:
[src/data/trainer_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_anims.h)
The trainers don't really move, but in theory they could, it's just that the animation defined for each trainer just shows one frame:
```diff
static const union AnimCmd *const sAnims_RubySapphireMay[] ={
sAnim_GeneralFrame0,
};
+ static const union AnimCmd *const sAnims_MyTrainerClass[] ={
+ sAnim_GeneralFrame0,
+ };
const union AnimCmd *const *const gTrainerFrontAnimsPtrTable[] =
{
[TRAINER_PIC_HIKER] = sAnims_Hiker,
[TRAINER_PIC_AQUA_GRUNT_M] = sAnims_AquaGruntM,
[TRAINER_PIC_POKEMON_BREEDER_F] = sAnims_PokemonBreederF,
...
...
[TRAINER_PIC_RS_BRENDAN] = sAnims_RubySapphireBrendan,
[TRAINER_PIC_RS_MAY] = sAnims_RubySapphireMay,
+ [TRAINER_PIC_MYTRAINERCLASS] = sAnims_MyTrainerClass,
};
```
### 4. Connecting the Pictures to the Data
The last few things we have to do is prepare the graphics for usage. In [src/data/trainer_graphics/front_pic_table.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_table.h) you'll find the structs, we need to add the trainer to all of these. You can just copy the last trainer type defined and edit it, but as far as I understand, these are what they do:
1. gTrainerFrontPicCoords: Pretty self explanatory. Coordinates like size and offset on the y-axis to position the sprite on screen.
2. gTrainerFrontPicTable: Connects the trainer type with the image we defined earlier.
3. gTrainerFrontPicPaletteTable: Connects the trainer type with the palette we defined earlier.
So, finally, it needs to look like this:
```diff
const struct MonCoords gTrainerFrontPicCoords[] =
{
[TRAINER_PIC_HIKER] = {.size = 8, .y_offset = 1},
[TRAINER_PIC_AQUA_GRUNT_M] = {.size = 8, .y_offset = 1},
...
...
[TRAINER_PIC_RS_BRENDAN] = {.size = 8, .y_offset = 1},
[TRAINER_PIC_RS_MAY] = {.size = 8, .y_offset = 1},
+ [TRAINER_PIC_MYTRAINERCLASS] = {.size = 8, .y_offset = 1},
};
#define TRAINER_SPRITE(trainerPic, sprite, size) [TRAINER_PIC_##trainerPic] = {sprite, size, TRAINER_PIC_##trainerPic}
const struct CompressedSpriteSheet gTrainerFrontPicTable[] =
{
TRAINER_SPRITE(HIKER, gTrainerFrontPic_Hiker, 0x800),
TRAINER_SPRITE(AQUA_GRUNT_M, gTrainerFrontPic_AquaGruntM, 0x800),
TRAINER_SPRITE(POKEMON_BREEDER_F, gTrainerFrontPic_PokemonBreederF, 0x800),
TRAINER_SPRITE(COOLTRAINER_M, gTrainerFrontPic_CoolTrainerM, 0x800),
...
...
TRAINER_SPRITE(RS_BRENDAN, gTrainerFrontPic_RubySapphireBrendan, 0x800),
TRAINER_SPRITE(RS_MAY, gTrainerFrontPic_RubySapphireMay, 0x800),
+ TRAINER_SPRITE(MYTRAINERCLASS, gTrainerFrontPic_MyTrainerClass, 0x800),
};
#define TRAINER_PAL(trainerPic, pal) [TRAINER_PIC_##trainerPic] = {pal, TRAINER_PIC_##trainerPic}
const struct CompressedSpritePalette gTrainerFrontPicPaletteTable[] =
{
TRAINER_PAL(HIKER, gTrainerPalette_Hiker),
TRAINER_PAL(AQUA_GRUNT_M, gTrainerPalette_AquaGruntM),
TRAINER_PAL(POKEMON_BREEDER_F, gTrainerPalette_PokemonBreederF),
...
...
TRAINER_PAL(RS_BRENDAN, gTrainerPalette_RubySapphireBrendan),
TRAINER_PAL(RS_MAY, gTrainerPalette_RubySapphireMay),
+ TRAINER_PAL(MYTRAINERCLASS, gTrainerPalette_MyTrainerClass),
};
```
### The Data
#### 5. Defining the trainer class
Finally, let's bring it all together by defining our new trainer class in [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h):
```diff
#define TRAINER_PIC_RS_MAY 92
+ #define TRAINER_PIC_MYTRAINERCLASS 93
#define TRAINER_BACK_PIC_BRENDAN 0
#define TRAINER_BACK_PIC_MAY 1
```
Remember to count the number next to the trainer class up by one!
## Usage
You can test your trainer type by going to [src/data/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainers.h) and changing a trainer type. For example:
```diff
[TRAINER_BRENDAN_PLACEHOLDER] =
{
.partyFlags = 0,
.trainerClass = TRAINER_CLASS_RS_PROTAG,
.encounterMusic_gender = TRAINER_ENCOUNTER_MUSIC_MALE,
- .trainerPic = TRAINER_PIC_RS_BRENDAN,
+ .trainerPic = TRAINER_PIC_MYTRAINERCLASS,
.trainerName = _("BRENDAN"),
.items = {},
.doubleBattle = FALSE,
.aiFlags = 0,
.partySize = ARRAY_COUNT(sParty_BrendanLinkPlaceholder),
.party = {.NoItemDefaultMoves = sParty_BrendanLinkPlaceholder},
},
```