This tutorial is for how to add a new trainer class. As an example, we'll add the Parasol Lady class. ## Contents 1. [Define a trainer class constant](#1-define-a-trainer-class-constant) 2. [Give them a name](#2-give-them-a-name) 3. [Define their attributes](#3-define-their-attributes) 4. [Define their DVs](#4-define-their-dvs) 5. [Define their encounter music](#5-define-their-encounter-music) 6. [Design their sprite](#6-design-their-sprite) 7. [Define their individual parties](#7-define-their-individual-parties) 8. [Define their Battle Tower sprite and gender](#8-define-their-battle-tower-sprite-and-gender) 9. [Fix bank overflow errors](#9-fix-bank-overflow-errors) ## 1. Define a trainer class constant Edit [constants/trainer_constants.asm](../blob/master/constants/trainer_constants.asm): ```diff ; trainer class ids ; `trainerclass` indexes are for: ; - TrainerClassNames (see data/trainers/class_names.asm) ; - TrainerClassAttributes (see data/trainers/attributes.asm) ; - TrainerClassDVs (see data/trainers/dvs.asm) ; - TrainerGroups (see data/trainers/party_pointers.asm) ; - TrainerEncounterMusic (see data/trainers/encounter_music.asm) ; - TrainerPicPointers (see data/trainers/pic_pointers.asm) ; - TrainerPalettes (see data/trainers/palettes.asm) ; - BTTrainerClassSprites (see data/trainers/sprites.asm) ; - BTTrainerClassGenders (see data/trainers/genders.asm) ; trainer constants are Trainers indexes, for the sub-tables of TrainerGroups (see data/trainers/parties.asm) CHRIS EQU __trainer_class__ trainerclass TRAINER_NONE ; 0 ... KRIS EQU __trainer_class__ trainerclass FALKNER ; 1 const FALKNER1 ... trainerclass MYSTICALMAN ; 43 const EUSINE + trainerclass PARASOL_LADY + const SUE NUM_TRAINER_CLASSES EQU __trainer_class__ - 1 ``` The `trainerclass` macro defines the next trainer class constant, and prepares to define a sequence of constants for individual trainers. Here we've defined `SUE` as the only Parasol Lady. Note the `CHRIS` and `KRIS` constants, equal to 0 and 1 respectively; they're used for getting the correct color palette when displaying the player's sprite in the introduction and on the trainer card. `KRIS` is equal to `FALKNER`, which is why they share a palette. Be careful when naming trainer constants; either make them unique, or make them unambiguous (like `BUG_CATCHER_BENNY` and `BIKER_BENNY`). I once made a trainer constant `SPARK` and caused a bug because `SPARK` was already a move constant. (That's why we have suffixes for `BLACKBELT_T` [trainer] and `BLACKBELT_I` [item], or `PSYCHIC_T` [trainer], `PSYCHIC_M` [move], and `PSYCHIC_TYPE` [type].) Next we'll add data for the new `PARASOL_LADY` class to all those tables mentioned in the top comment. ## 2. Give them a name Edit [data/trainers/class_names.asm](../blob/master/data/trainers/class_names.asm): ```diff TrainerClassNames:: ; entries correspond to trainer classes (see constants/trainer_constants.asm) list_start TrainerClassNames li "LEADER" ... li "MYSTICALMAN" + li "PARASOL LADY" assert_list_length NUM_TRAINER_CLASSES ``` A name can be up to 12 characters long. Note that the trainer class and individual name will get printed on one line in phrases like "PARASOL LADY SUE wants to battle!" so make sure the whole phrase will fit in 18 characters. ## 3. Define their attributes Edit [data/trainers/attributes.asm](../blob/master/data/trainers/attributes.asm): ```diff TrainerClassAttributes: ; entries correspond to trainer classes (see constants/trainer_constants.asm) ; Falkner db NO_ITEM, NO_ITEM ; items db 25 ; base reward dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY dw CONTEXT_USE | SWITCH_SOMETIMES ... ; Mysticalman db NO_ITEM, NO_ITEM ; items db 25 ; base reward dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY dw CONTEXT_USE | SWITCH_SOMETIMES + +; Parasol Lady + db NO_ITEM, NO_ITEM ; items + db 10 ; base reward + dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_STATUS + dw CONTEXT_USE | SWITCH_SOMETIMES assert_table_length NUM_TRAINER_CLASSES ``` "Attributes" encompass a number of different properties: - **items:** Two items, each of which a trainer can use once. They may be the same item or `NO_ITEM`. The AI engine has to know how to use the items, as defined by `AI_Items` in [engine/battle/ai/items.asm](../blob/master/engine/battle/ai/items.asm). - **base reward:** The base monetary reward for beating a trainer. The reward amount is 4×*B*×*L*, where *B* is the base reward and *L* is the level of the last Pokémon they used. - **AI flags (move weights):** Bit flags that control how the trainer's AI chooses a move to use. Valid flags, with descriptions from [engine/battle/ai/scoring.asm](../blob/master/engine/battle/ai/scoring.asm): - `NO_AI`: - `AI_BASIC`: Don't do anything redundant: - Using status-only moves if the player can't be statused (see [data/battle/ai/status_only_effects.asm](../blob/master/data/battle/ai/status_only_effects.asm)) - Using moves that fail if they've already been used - `AI_SETUP`: Use stat-modifying moves on turn 1: - 50% chance to greatly encourage stat-up moves during the first turn of enemy's Pokémon - 50% chance to greatly encourage stat-down moves during the first turn of player's Pokémon - Almost 90% chance to greatly discourage stat-modifying moves otherwise - `AI_TYPES`: Dismiss any move that the player is immune to. Encourage super-effective moves. Discourage not-very-effective moves unless all damaging moves are of the same type. - `AI_OFFENSIVE`: Greatly discourage non-damaging moves. - `AI_SMART`: Context-specific scoring. Special cases for many different move effects - `AI_OPPORTUNIST`: Discourage stall moves when the enemy's HP is low (see [data/battle/ai/stall_moves.asm](../blob/master/data/battle/ai/stall_moves.asm)). - `AI_AGGRESSIVE`: Discourage all damaging moves but the one that does the most damage. Reckless moves are not discouraged (see [data/battle/ai/reckless_moves.asm](../blob/master/data/battle/ai/reckless_moves.asm)). - `AI_CAUTIOUS`: 90% chance to discourage moves with residual effects after the first turn (see [data/battle/ai/residual_moves.asm](../blob/master/data/battle/ai/residual_moves.asm)). - `AI_STATUS`: Dismiss status moves that don't affect the player. - `AI_RISKY`: Use any move that will KO the target. Risky moves will often be an exception (see [data/battle/ai/risky_effects.asm](../blob/master/data/battle/ai/risky_effects.asm)). - **AI flags (item/switch):** Bit flags that control how the trainer's AI chooses to use an item or switch Pokémon instead of attacking. Combine one `*_USE` flag and one `SWITCH_*` flag. Valid flags: - `CONTEXT_USE`: - `UNKNOWN_USE`: - `ALWAYS_USE`: - `SWITCH_SOMETIMES`: - `SWITCH_RARELY`: - `SWITCH_OFTEN`: ## 4. Define their DVs Edit [data/trainers/dvs.asm](../blob/master/data/trainers/dvs.asm): ```diff TrainerClassDVs: ; entries correspond to trainer classes (see constants/trainer_constants.asm) ; atk,def,spd,spc dn 9, 10, 7, 7 ; FALKNER ... dn 9, 8, 8, 8 ; MYSTICALMAN + dn 7, 8, 8, 8 ; parasol lady assert_table_length NUM_TRAINER_CLASSES ``` The four numbers define, in order, the Attack, Defense, Speed, and Special DVs for all the trainer class's Pokémon. Each DV can be from 0 to 15. (Remember, in Gen 2 there was one DV for both Special Attack and Special Defense; and bits from all four DVs were combined to calculate HP.) Female trainer classes tend to have low Attack DVs so that their Pokémon will usually be female (since gender in Gen 2 was determined by the Attack and Speed DVs, primarily Attack). Gym Leaders are mostly an exception to this rule. You may also want to choose DVs that give some important Parasol Lady the right Hidden Power type, if that's relevant. Or follow [this tutorial](Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-nicknames,-variable-teams,-etc.) to give individual trainers unique DVs (as well as stat experience and nicknames!). ## 5. Define their encounter music Edit [data/trainers/encounter_music.asm](../blob/master/data/trainers/encounter_music.asm): ```diff TrainerEncounterMusic:: ; entries correspond to trainer classes (see constants/trainer_constants.asm) db MUSIC_HIKER_ENCOUNTER ; none ... db MUSIC_HIKER_ENCOUNTER ; mysticalman + db MUSIC_BEAUTY_ENCOUNTER ; parasol lady assert_table_length NUM_TRAINER_CLASSES + 1 - db MUSIC_HIKER_ENCOUNTER - db MUSIC_HIKER_ENCOUNTER - db MUSIC_HIKER_ENCOUNTER ``` There aren't many to choose from; it's usually `MUSIC_YOUNGSTER_ENCOUNTER` or `MUSIC_HIKER_ENCOUNTER` for male trainers, `MUSIC_LASS_ENCOUNTER` or `MUSIC_BEAUTY_ENCOUNTER` for female ones, plus some more specialized `MUSIC_*_ENCOUNTER` tunes. Notice that we removed three extra `MUSIC_HIKER_ENCOUNTER`s at the end without any corresponding trainer classes. If you want different music to play *during* battle, you'll need to edit `PlayBattleMusic` in [engine/battle/start_battle.asm](../blob/master/engine/battle/start_battle.asm). It's a series of [hard-coded logic](Hard-coded-logic#trainer-classes-with-different-battle-music) checks for various conditions under which to play special battle music. Decide carefully where to place a check for your new condition—like whether `[wOtherTrainerClass]` is `PARASOL_LADY`, or whether `[wOtherTrainerClass]` is `PARASOL_LADY` *and* `[wOtherTrainerID]` is `SUE`—so that you don't break assumptions made by the other checks. ## 6. Design their sprite Create **gfx/trainers/parasol_lady.png**: ![gfx/trainers/parasol_lady.png](screenshots/gfx-trainers-parasol_lady.png) Trainer sprites are all 56x56 pixels, and use four colors: white, black, and two arbitrary hues. Next edit [data/trainers/pic_pointers.asm](../blob/master/data/trainers/pic_pointers.asm): ```diff TrainerPicPointers:: ; entries correspond to trainer classes (see constants/trainer_constants.asm) dba_pic FalknerPic ... dba_pic MysticalmanPic + dba_pic ParasolLadyPic assert_table_length NUM_TRAINER_CLASSES ``` We have to use `dba_pic` here instead of a standard `dba`—declaring the bank and address of `ParasolLadyPic`—because of [this design flaw](../blob/master/docs/design_flaws.md#pic-banks-are-offset-by-pics_fix). I strongly recommend removing the whole `FixPicBank` routine from [engine/gfx/load_pics.asm](../blob/master/engine/gfx/load_pics.asm), including all four calls to it in that file, and just using `dba` here; then you'll be able to `INCBIN` sprites in arbitrary banks. Edit [gfx/pics.asm](../blob/master/gfx/pics.asm): ```diff SECTION "Pics 19", ROMX -; Seems to be an accidental copy of the previous bank - -INCBIN "gfx/pokemon/spinarak/back.2bpp.lz" -... -INCBIN "gfx/pokemon/unown_r/back.2bpp.lz" +ParasolLadyPic: INCBIN "gfx/trainers/parasol_lady.2bpp.lz" ``` (If you *don't* fix the `dba_pic` design flaw, you'll have to put your sprites in the "Pics *N*" sections, which are compatible with `dba_pic`. "Pics 19" isn't used for anything useful—all its contents are unused duplicates of "Pics 18"—and it has a whole bank to itself, so it's the easiest place to start adding new sprites. (The other sections, includng "Pics 20" through "Pics 24", have limited remaining space since they already contain some sprites and/or share their banks with other sections.) But if you have a lot of new sprites to add, you risk overflowing the banks, and it's hard to fit sprites within fixed bank limits. By using just `dba`, you can create new sections with a few sprites each, that will automatically be placed wherever they can fit in the ROM.) Anyway, edit [data/trainers/palettes.asm](../blob/master/data/trainers/palettes.asm): ```diff TrainerPalettes: ; entries correspond to trainer classes ; Each .gbcpal is generated from the corresponding .png, and ; only the middle two colors are included, not black or white. PlayerPalette: ; Chris uses the same colors as Cal INCBIN "gfx/trainers/cal.gbcpal", middle_colors KrisPalette: ; Kris shares Falkner's palette INCBIN "gfx/trainers/falkner.gbcpal", middle_colors ... INCBIN "gfx/trainers/mysticalman.gbcpal", middle_colors +INCBIN "gfx/trainers/parasol_lady.gbcpal", middle_colors assert_table_length NUM_TRAINER_CLASSES + 1 ``` parasol_lady.2bpp.lz and parasol_lady.gbcpal will be automatically generated from parasol_lady.png when you run `make`. (Older versions of pokecrystal would `INCLUDE "parasol_lady.pal"` instead of `INCBIN "parasol_lady.gbcpal", middle_colors`. This is because they generated .pal files instead of .gbcpal, but this was redundant work and could mislead users into editing .pal files directly, so as of September 2018 they were removed.) ## 7. Define their individual parties Edit [data/trainers/party_pointers.asm](../blob/master/data/trainers/party_pointers.asm): ```diff TrainerGroups: ; entries correspond to trainer classes (see constants/trainer_constants.asm) dw FalknerGroup ... dw MysticalmanGroup + dw ParasolLadyGroup assert_table_length NUM_TRAINER_CLASSES ``` Then edit [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm): ```diff FalknerGroup: ; FALKNER (1) db "FALKNER@", TRAINERTYPE_MOVES db 7, PIDGEY, TACKLE, MUD_SLAP, NO_MOVE, NO_MOVE db 9, PIDGEOTTO, TACKLE, MUD_SLAP, GUST, NO_MOVE db -1 ; end ... MysticalmanGroup: ; MYSTICALMAN (1) db "EUSINE@", TRAINERTYPE_MOVES db 23, DROWZEE, DREAM_EATER, HYPNOSIS, DISABLE, CONFUSION db 23, HAUNTER, LICK, HYPNOSIS, MEAN_LOOK, CURSE db 25, ELECTRODE, SCREECH, SONICBOOM, THUNDER, ROLLOUT db -1 ; end + +ParasolLadyGroup: + ; PARASOL_LADY (1) + db "SUE@", TRAINERTYPE_NORMAL + db 28, GOLDEEN + db 30, GOLDUCK + db -1 ; end ``` The comment at the top of parties.asm explains the data structure: ``` ; Trainer data structure: ; - db "NAME@", TRAINERTYPE_* constant ; - 1 to 6 Pokémon: ; * for TRAINERTYPE_NORMAL: db level, species ; * for TRAINERTYPE_MOVES: db level, species, 4 moves ; * for TRAINERTYPE_ITEM: db level, species, item ; * for TRAINERTYPE_ITEM_MOVES: db level, species, item, 4 moves ; - db -1 ; end ``` ## 8. Define their Battle Tower sprite and gender If a trainer class is used in Battle Tower, as defined in [data/battle_tower/classes.asm](../blob/master/data/battle_tower/classes.asm), it will need a defined sprite and gender. The sprite is used when the trainer walks into the room, and their gender determines what they say. Even if you don't add a Parasol Lady to the Battle Tower roster, it's helpful to keep the data tables up-to-date. Edit [data/trainers/sprites.asm](../blob/master/data/trainers/sprites.asm): ```diff BTTrainerClassSprites: ; entries correspond to trainer classes db SPRITE_FALKNER ... db SPRITE_ROCKET_GIRL - assert_table_length NUM_TRAINER_CLASSES - 1 ; exclude MYSTICALMAN + db SPRITE_SUPER_NERD + db SPRITE_TEACHER + assert_table_length NUM_TRAINER_CLASSES ``` Valid sprites are in [constants/sprite_constants.asm](../blob/master/constants/sprite_constants.asm). They're for the 16x16 overworld sprites, not the 56x56 battle sprites. Adding a custom sprite is beyond the scope of this tutorial. Notice that there was no sprite for `MYSTICALMAN`, so we had to add one so that `SPRITE_TEACHER` would correspond with `PARASOL_LADY`. Anyway, edit [data/trainers/genders.asm](../blob/master/data/trainers/genders.asm): ```diff BTTrainerClassGenders: ; entries correspond to trainer classes table_width 1, BTTrainerClassGenders db MALE ; FALKNER ... db FEMALE ; GRUNTF - assert_table_length NUM_TRAINER_CLASSES - 1 ; exclude MYSTICALMAN + db MALE ; MYSTICALMAN + db FEMALE ; PARASOL_LADY + assert_table_length NUM_TRAINER_CLASSES ``` Again, we had to add data for `MYSTICALMAN` to reach the slot for `PARASOL_LADY`. Also edit [data/trainers/gendered_trainers.asm](../blob/master/data/trainers/gendered_trainers.asm): ```diff FemaleTrainers: db MEDIUM db LASS db BEAUTY db SKIER db TEACHER db SWIMMERF db PICNICKER db KIMONO_GIRL db POKEFANF db COOLTRAINERF + db PARASOL_LADY .End ``` For whatever reason, trainers' gender is checked in two redundant ways, so we just have to keep both tables up-to-date. ## 9. Fix bank overflow errors We're done adding the Parasol Lady data, but `make` won't compile the ROM: ``` error: Unable to place 'Pics 3' (ROMX section) at $40CC in bank $4A ``` But we didn't change anything in "Pics 3", so why is this happening? As defined in [layout.link](../blob/master/layout.link), the "Trainer Pic Pointers" and "Pics 3" sections are both in bank $4A: ``` ROMX $4a "Trainer Pic Pointers" "Pics 3" ``` It turns out that adding `dba_pic ParasolLadyPic` to the `TrainerPicPointers` table was a little too much data for that bank. To fix this, we'll need to move some data out of bank $4A. "Trainer Pic Pointers" has to be as large as it is, but "Pics 3" is an arbitrary set of sprites, so we can move one of those. Edit [gfx/pics.asm](../blob/master/gfx/pics.asm) again: ```diff SECTION "Pics 3", ROMX SteelixFrontpic: INCBIN "gfx/pokemon/steelix/front.animated.2bpp.lz" AlakazamFrontpic: INCBIN "gfx/pokemon/alakazam/front.animated.2bpp.lz" GyaradosFrontpic: INCBIN "gfx/pokemon/gyarados/front.animated.2bpp.lz" KangaskhanFrontpic: INCBIN "gfx/pokemon/kangaskhan/front.animated.2bpp.lz" RhydonFrontpic: INCBIN "gfx/pokemon/rhydon/front.animated.2bpp.lz" GolduckFrontpic: INCBIN "gfx/pokemon/golduck/front.animated.2bpp.lz" RhyhornFrontpic: INCBIN "gfx/pokemon/rhyhorn/front.animated.2bpp.lz" PidgeotFrontpic: INCBIN "gfx/pokemon/pidgeot/front.animated.2bpp.lz" SlowbroFrontpic: INCBIN "gfx/pokemon/slowbro/front.animated.2bpp.lz" ButterfreeFrontpic: INCBIN "gfx/pokemon/butterfree/front.animated.2bpp.lz" WeezingFrontpic: INCBIN "gfx/pokemon/weezing/front.animated.2bpp.lz" CloysterFrontpic: INCBIN "gfx/pokemon/cloyster/front.animated.2bpp.lz" SkarmoryFrontpic: INCBIN "gfx/pokemon/skarmory/front.animated.2bpp.lz" DewgongFrontpic: INCBIN "gfx/pokemon/dewgong/front.animated.2bpp.lz" VictreebelFrontpic: INCBIN "gfx/pokemon/victreebel/front.animated.2bpp.lz" RaichuFrontpic: INCBIN "gfx/pokemon/raichu/front.animated.2bpp.lz" PrimeapeFrontpic: INCBIN "gfx/pokemon/primeape/front.animated.2bpp.lz" -OmastarBackpic: INCBIN "gfx/pokemon/omastar/back.2bpp.lz" ... SECTION "Pics 19", ROMX ParasolLadyPic: INCBIN "gfx/trainers/parasol_lady.2bpp.lz" +OmastarBackpic: INCBIN "gfx/pokemon/omastar/back.2bpp.lz" ``` Now we're done! You can write an event script that uses Parasol Lady Sue just like any other trainer. ![Screenshot](screenshots/parasol-lady-sue.png)