-
Notifications
You must be signed in to change notification settings - Fork 811
Add a new trainer class
This tutorial is for how to add a new trainer class. As an example, we'll add the Parasol Lady class.
- Define a trainer class constant
- Give them a name
- Define their attributes
- Define their DVs
- Define their encounter music
- Design their sprite
- Define their individual parties
- Define their Battle Tower sprite and gender
- Fix bank overflow errors
Edit constants/trainer_constants.asm:
; 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)
DEF CHRIS EQU __trainer_class__
trainerclass TRAINER_NONE ; 0
...
DEF KRIS EQU __trainer_class__
trainerclass FALKNER ; 1
const FALKNER1
...
trainerclass MYSTICALMAN ; 43
const EUSINE
+ trainerclass PARASOL_LADY
+ const SUE
DEF 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.
Edit data/trainers/class_names.asm:
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.
Edit data/trainers/attributes.asm:
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 byAI_Items
in 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:
-
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)
- 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). -
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). -
AI_CAUTIOUS
: 90% chance to discourage moves with residual effects after the first turn (see 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).
-
-
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 oneSWITCH_*
flag. Valid flags:-
CONTEXT_USE
: -
UNKNOWN_USE
: -
ALWAYS_USE
: -
SWITCH_SOMETIMES
: -
SWITCH_RARELY
: -
SWITCH_OFTEN
:
-
Edit data/trainers/dvs.asm:
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 to give individual trainers unique DVs (as well as stat experience and nicknames!).
Edit data/trainers/encounter_music.asm:
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. It's a series of hard-coded logic 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.
Create 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:
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. I strongly recommend removing the whole FixPicBank
routine from 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:
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:
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.)
Edit data/trainers/party_pointers.asm:
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:
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
If a trainer class is used in Battle Tower, as defined in 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:
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. 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:
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:
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.
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, 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 again:
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.