Add a new trainer class - pret/pokecrystal GitHub Wiki

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
  2. Give them a name
  3. Define their attributes
  4. Define their DVs
  5. Define their encounter music
  6. Design their sprite
  7. Define their individual parties
  8. Define their Battle Tower sprite and gender
  9. Fix bank overflow errors

1. Define a trainer class constant

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.

2. Give them a name

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.

3. Define their attributes

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 by AI_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:
    • 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 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:

 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!).

5. Define their encounter music

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_ENCOUNTERs 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.

6. Design their sprite

Create gfx/trainers/parasol_lady.png:

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.)

7. Define their individual parties

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

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, 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.

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, 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.

Screenshot