Skip to content
Xillicis edited this page Jan 22, 2024 · 3 revisions

Adding a new trainer class is fairly straightforward. A lot the changes are just for specifying information about the new trainer you'd like to add. For example, their name, what A.I. they'll have, what the Pokemon teams will be, etc... Although, slight code modification is necessary to allow for the new sprites to be loaded.

This tutorial will specifically replace the unused classes, UNUSED_JUGGLER and CHIEF. If you want to add more than this you can repeat the process but add them at the end of the each list if you'd like.

constants\trainer_constants.asm:

DEF OPP_ID_OFFSET EQU 200

MACRO trainer_const
	const \1
	DEF OPP_\1 EQU OPP_ID_OFFSET + \1
ENDM

; trainer class ids
; indexes for:
; - TrainerNames (see data/trainers/names.asm)
; - TrainerNamePointers (see data/trainers/name_pointers.asm)
; - TrainerDataPointers (see data/trainers/parties.asm)
; - TrainerPicAndMoneyPointers (see data/trainers/pic_pointers_money.asm)
; - TrainerAIPointers (see data/trainers/ai_pointers.asm)
; - TrainerClassMoveChoiceModifications (see data/trainers/move_choices.asm)
	const_def
	trainer_const NOBODY         ; $00
	trainer_const YOUNGSTER      ; $01
	trainer_const BUG_CATCHER    ; $02
	trainer_const LASS           ; $03
	trainer_const SAILOR         ; $04
	trainer_const JR_TRAINER_M   ; $05
	trainer_const JR_TRAINER_F   ; $06
	trainer_const POKEMANIAC     ; $07
	trainer_const SUPER_NERD     ; $08
	trainer_const HIKER          ; $09
	trainer_const BIKER          ; $0A
	trainer_const BURGLAR        ; $0B
	trainer_const ENGINEER       ; $0C
-	trainer_const UNUSED_JUGGLER ; $0D
+       trainer_const NEW_TRAINER1   ; $0D
	trainer_const FISHER         ; $0E
	trainer_const SWIMMER        ; $0F
	trainer_const CUE_BALL       ; $10
	trainer_const GAMBLER        ; $11
	trainer_const BEAUTY         ; $12
	trainer_const PSYCHIC_TR     ; $13
	trainer_const ROCKER         ; $14
	trainer_const JUGGLER        ; $15
	trainer_const TAMER          ; $16
	trainer_const BIRD_KEEPER    ; $17
	trainer_const BLACKBELT      ; $18
	trainer_const RIVAL1         ; $19
	trainer_const PROF_OAK       ; $1A
-	trainer_const CHIEF          ; $1B
+       trainer_const NEW_TRAINER2   ; $1B
	trainer_const SCIENTIST      ; $1C
	trainer_const GIOVANNI       ; $1D
	trainer_const ROCKET         ; $1E
	trainer_const COOLTRAINER_M  ; $1F
	trainer_const COOLTRAINER_F  ; $20
	trainer_const BRUNO          ; $21
	trainer_const BROCK          ; $22
	trainer_const MISTY          ; $23
	trainer_const LT_SURGE       ; $24
	trainer_const ERIKA          ; $25
	trainer_const KOGA           ; $26
	trainer_const BLAINE         ; $27
	trainer_const SABRINA        ; $28
	trainer_const GENTLEMAN      ; $29
	trainer_const RIVAL2         ; $2A
	trainer_const RIVAL3         ; $2B
	trainer_const LORELEI        ; $2C
	trainer_const CHANNELER      ; $2D
	trainer_const AGATHA         ; $2E
	trainer_const LANCE          ; $2F
DEF NUM_TRAINERS EQU const_value - 1

Note that we've moved the position of the trainer that is replacing the UNUSED_JUGGLER to be below PROF_OAK. This is important when we need to modify the code for loading data\trainers\ai_pointers.asm:

TrainerAIPointers:
	table_width 3, TrainerAIPointers
	; one entry per trainer class
	; first byte, number of times (per Pokémon) it can occur
	; next two bytes, pointer to AI subroutine for trainer class
	; subroutines are defined in engine/battle/trainer_ai.asm
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
-	dbw 3, JugglerAI ; unused_juggler
+       dbw 3, GenericAI ; new trainer1
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 3, JugglerAI ; juggler
	dbw 3, GenericAI
	dbw 3, GenericAI
	dbw 2, BlackbeltAI ; blackbelt
	dbw 3, GenericAI ; rival1
	dbw 3, GenericAI
-	dbw 1, GenericAI ; chief
+       dbw 3, GenericAI ; new trainer2
	dbw 3, GenericAI
	dbw 1, GiovanniAI ; giovanni
	dbw 3, GenericAI
	dbw 2, CooltrainerMAI ; cooltrainerm
	dbw 1, CooltrainerFAI ; cooltrainerf
	dbw 2, BrunoAI ; bruno
	dbw 5, BrockAI ; brock
	dbw 1, MistyAI ; misty
	dbw 1, LtSurgeAI ; surge
	dbw 1, ErikaAI ; erika
	dbw 2, KogaAI ; koga
	dbw 2, BlaineAI ; blaine
	dbw 1, SabrinaAI ; sabrina
	dbw 3, GenericAI
	dbw 1, Rival2AI ; rival2
	dbw 1, Rival3AI ; rival3
	dbw 2, LoreleiAI ; lorelei
	dbw 3, GenericAI
	dbw 2, AgathaAI ; agatha
	dbw 1, LanceAI ; lance
	assert_table_length NUM_TRAINERS

Encounter Types: for default Male/Gym trainers encounter music, just not put in any list.

data\trainers\encounter_types.asm:

FemaleTrainerList::
	db OPP_LASS
	db OPP_JR_TRAINER_F
	db OPP_BEAUTY
	db OPP_COOLTRAINER_F
	db -1 ; end

EvilTrainerList::
-	db OPP_UNUSED_JUGGLER
	db OPP_GAMBLER
	db OPP_ROCKER
	db OPP_JUGGLER
-	db OPP_CHIEF
	db OPP_SCIENTIST
	db OPP_GIOVANNI
	db OPP_ROCKET
	db -1 ; end

data\trainers\move_choices.asm:

MACRO move_choices
	IF _NARG
		db \# ; all args
	ENDC
	db 0 ; end
	DEF list_index += 1
ENDM

; move choice modification methods that are applied for each trainer class
TrainerClassMoveChoiceModifications:
	list_start TrainerClassMoveChoiceModifications
	move_choices         ; YOUNGSTER
	move_choices 1       ; BUG CATCHER
	move_choices 1       ; LASS
	move_choices 1, 3    ; SAILOR
	move_choices 1       ; JR_TRAINER_M
	move_choices 1       ; JR_TRAINER_F
	move_choices 1, 2, 3 ; POKEMANIAC
	move_choices 1, 2    ; SUPER_NERD
	move_choices 1       ; HIKER
	move_choices 1       ; BIKER
	move_choices 1, 3    ; BURGLAR
	move_choices 1       ; ENGINEER
-	move_choices 1, 2    ; UNUSED_JUGGLER
+       move_choices 1, 2, 3 ; NEW_TRAINER1
	move_choices 1, 3    ; FISHER
	move_choices 1, 3    ; SWIMMER
	move_choices         ; CUE_BALL
	move_choices 1       ; GAMBLER
	move_choices 1, 3    ; BEAUTY
	move_choices 1, 2    ; PSYCHIC_TR
	move_choices 1, 3    ; ROCKER
	move_choices 1       ; JUGGLER
	move_choices 1       ; TAMER
	move_choices 1       ; BIRD_KEEPER
	move_choices 1       ; BLACKBELT
	move_choices 1       ; RIVAL1
	move_choices 1, 3    ; PROF_OAK
-	move_choices 1, 2    ; CHIEF
+       move_choices 1       ; NEW_TRAINER2
	move_choices 1, 2    ; SCIENTIST
	move_choices 1, 3    ; GIOVANNI
	move_choices 1       ; ROCKET
	move_choices 1, 3    ; COOLTRAINER_M
	move_choices 1, 3    ; COOLTRAINER_F
	move_choices 1       ; BRUNO
	move_choices 1       ; BROCK
	move_choices 1, 3    ; MISTY
	move_choices 1, 3    ; LT_SURGE
	move_choices 1, 3    ; ERIKA
	move_choices 1, 3    ; KOGA
	move_choices 1, 3    ; BLAINE
	move_choices 1, 3    ; SABRINA
	move_choices 1, 2    ; GENTLEMAN
	move_choices 1, 3    ; RIVAL2
	move_choices 1, 3    ; RIVAL3
	move_choices 1, 2, 3 ; LORELEI
	move_choices 1       ; CHANNELER
	move_choices 1       ; AGATHA
	move_choices 1, 3    ; LANCE
	assert_list_length NUM_TRAINERS

data\trainers\name_pointers.asm:

TrainerNamePointers:
; These are only used for trainers' defeat speeches.
; They were originally shortened variants of the trainer class names
; in the Japanese versions, but are now redundant with TrainerNames.
	table_width 2, TrainerNamePointers
	dw .YoungsterName
	dw .BugCatcherName
	dw .LassName
	dw wTrainerName
	dw .JrTrainerMName
	dw .JrTrainerFName
	dw .PokemaniacName
	dw .SuperNerdName
	dw wTrainerName
	dw wTrainerName
	dw .BurglarName
	dw .EngineerName
-	dw .UnusedJugglerName
+	dw wTrainerName
	dw wTrainerName
	dw .SwimmerName
	dw wTrainerName
	dw wTrainerName
	dw .BeautyName
	dw wTrainerName
	dw .RockerName
	dw .JugglerName
	dw wTrainerName
	dw wTrainerName
	dw .BlackbeltName
	dw wTrainerName
	dw .ProfOakName
-	dw .ChiefName
+	dw wTrainerName
	dw .ScientistName
	dw wTrainerName
	dw .RocketName
	dw .CooltrainerMName
	dw .CooltrainerFName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	dw wTrainerName
	assert_table_length NUM_TRAINERS

.YoungsterName:     db "YOUNGSTER@"
.BugCatcherName:    db "BUG CATCHER@"
.LassName:          db "LASS@"
.JrTrainerMName:    db "JR.TRAINER♂@"
.JrTrainerFName:    db "JR.TRAINER♀@"
.PokemaniacName:    db "POKéMANIAC@"
.SuperNerdName:     db "SUPER NERD@"
.BurglarName:       db "BURGLAR@"
.EngineerName:      db "ENGINEER@"
-.UnusedJugglerName: db "JUGGLER@"
.SwimmerName:       db "SWIMMER@"
.BeautyName:        db "BEAUTY@"
.RockerName:        db "ROCKER@"
.JugglerName:       db "JUGGLER@"
.BlackbeltName:     db "BLACKBELT@"
.ProfOakName:       db "PROF.OAK@"
-.ChiefName:         db "CHIEF@"
.ScientistName:     db "SCIENTIST@"
.RocketName:        db "ROCKET@"
.CooltrainerMName:  db "COOLTRAINER♂@"
.CooltrainerFName:  db "COOLTRAINER♀@"

data\trainers\names.asm:

TrainerNames::
	list_start TrainerNames
	li "YOUNGSTER"
	li "BUG CATCHER"
	li "LASS"
	li "SAILOR"
	li "JR.TRAINER♂"
	li "JR.TRAINER♀"
	li "POKéMANIAC"
	li "SUPER NERD"
	li "HIKER"
	li "BIKER"
	li "BURGLAR"
	li "ENGINEER"
-	li "JUGGLER"
+       li "NEW TRAINER1"
	li "FISHERMAN"
	li "SWIMMER"
	li "CUE BALL"
	li "GAMBLER"
	li "BEAUTY"
	li "PSYCHIC"
	li "ROCKER"
	li "JUGGLER"
	li "TAMER"
	li "BIRD KEEPER"
	li "BLACKBELT"
	li "RIVAL1"
	li "PROF.OAK"
-	li "CHIEF"
+       li "NEW TRAINER2"
	li "SCIENTIST"
	li "GIOVANNI"
	li "ROCKET"
	li "COOLTRAINER♂"
	li "COOLTRAINER♀"
	li "BRUNO"
	li "BROCK"
	li "MISTY"
	li "LT.SURGE"
	li "ERIKA"
	li "KOGA"
	li "BLAINE"
	li "SABRINA"
	li "GENTLEMAN"
	li "RIVAL2"
	li "RIVAL3"
	li "LORELEI"
	li "CHANNELER"
	li "AGATHA"
	li "LANCE"
	assert_list_length NUM_TRAINERS

data\trainers\parties.asm:

TrainerDataPointers:
	table_width 2, TrainerDataPointers
	dw YoungsterData
	dw BugCatcherData
	dw LassData
	dw SailorData
	dw JrTrainerMData
	dw JrTrainerFData
	dw PokemaniacData
	dw SuperNerdData
	dw HikerData
	dw BikerData
	dw BurglarData
	dw EngineerData
-	dw UnusedJugglerData
+       dw NewTrainer1Data
	dw FisherData
	dw SwimmerData
	dw CueBallData
	dw GamblerData
	dw BeautyData
	dw PsychicData
	dw RockerData
	dw JugglerData
	dw TamerData
	dw BirdKeeperData
	dw BlackbeltData
	dw Green1Data
	dw ProfOakData
-	dw ChiefData
+       dw NewTrainer2Data
	dw ScientistData
	dw GiovanniData
...
...
EngineerData:
; Unused
	db 21, VOLTORB, MAGNEMITE, 0
; Route 11
	db 21, MAGNEMITE, 0
	db 18, MAGNEMITE, MAGNEMITE, MAGNETON, 0

-UnusedJugglerData:
-; none
+NewTrainer1Data:
+       db 5, CATERPIE, 0

FisherData:
...
...
ProfOakData:
; Unused
	db $FF, 66, TAUROS, 67, EXEGGUTOR, 68, ARCANINE, 69, BLASTOISE, 70, GYARADOS, 0
	db $FF, 66, TAUROS, 67, EXEGGUTOR, 68, ARCANINE, 69, VENUSAUR, 70, GYARADOS, 0
	db $FF, 66, TAUROS, 67, EXEGGUTOR, 68, ARCANINE, 69, CHARIZARD, 70, GYARADOS, 0

-ChiefData:
-; none
+NewTrainer2Data:
+       db $FF, 50, CHARIZARD, 50, BLASTOISE, 0

ScientistData:

data\trainers\pic_pointers_money.asm:

MACRO pic_money
	dw \1
	bcd3 \2
ENDM

TrainerPicAndMoneyPointers::
	table_width 5, TrainerPicAndMoneyPointers
	; pic pointer, base reward money
	; money received after battle = base money × level of last enemy mon
	pic_money YoungsterPic,    1500
	pic_money BugCatcherPic,   1000
	pic_money LassPic,         1500
	pic_money SailorPic,       3000
	pic_money JrTrainerMPic,   2000
	pic_money JrTrainerFPic,   2000
	pic_money PokemaniacPic,   5000
	pic_money SuperNerdPic,    2500
	pic_money HikerPic,        3500
	pic_money BikerPic,        2000
	pic_money BurglarPic,      9000
	pic_money EngineerPic,     5000
-	pic_money JugglerPic,      3500
+       pic_money NewTrainer1Pic,   500
	pic_money FisherPic,       3500
	pic_money SwimmerPic,       500
	pic_money CueBallPic,      2500
	pic_money GamblerPic,      7000
	pic_money BeautyPic,       7000
	pic_money PsychicPic,      1000
	pic_money RockerPic,       2500
	pic_money JugglerPic,      3500
	pic_money TamerPic,        4000
	pic_money BirdKeeperPic,   2500
	pic_money BlackbeltPic,    2500
	pic_money Rival1Pic,       3500
	pic_money ProfOakPic,      9900
-	pic_money ChiefPic,        3000
+       pic_money NewTrainer2Pic,  9900
	pic_money ScientistPic,    5000
	pic_money GiovanniPic,     9900
	pic_money RocketPic,       3000
	pic_money CooltrainerMPic, 3500
	pic_money CooltrainerFPic, 3500
	pic_money BrunoPic,        9900
	pic_money BrockPic,        9900
	pic_money MistyPic,        9900
	pic_money LtSurgePic,      9900
	pic_money ErikaPic,        9900
	pic_money KogaPic,         9900
	pic_money BlainePic,       9900
	pic_money SabrinaPic,      9900
	pic_money GentlemanPic,    7000
	pic_money Rival2Pic,       6500
	pic_money Rival3Pic,       9900
	pic_money LoreleiPic,      9900
	pic_money ChannelerPic,    3000
	pic_money AgathaPic,       9900
	pic_money LancePic,        9900
	assert_table_length NUM_TRAINERS

Now, if you want to create new trainer class sprites, you need to create a new SECTION to them.

engine\battle\core.asm:

...
	ld d, a ; de contains pointer to trainer pic
	ld a, [wLinkState]
	and a
-	ld a, BANK("Pics 6") ; this is where all the trainer pics are (not counting Red's)
-	jr z, .loadSprite
+	jr nz, .useRed
+	ld a, [wTrainerClass]
+	cp PROF_OAK ; first trainer class in "Trainer Pics 2"
+	ld a, BANK("Trainer Pics 2")
+	jr nc, .loadSprite
+	ld a, BANK("Trainer Pics 1")
+	jr .loadSprite
+.useRed
	ld a, BANK(RedPicFront)
.loadSprite
	call UncompressSpriteFromDE
...
...
-SECTION "Pics 6", ROMX
+SECTION "Trainer Pics 1", ROMX

YoungsterPic::     INCBIN "gfx/trainers/youngster.pic"
BugCatcherPic::    INCBIN "gfx/trainers/bugcatcher.pic"
LassPic::          INCBIN "gfx/trainers/lass.pic"
SailorPic::        INCBIN "gfx/trainers/sailor.pic"
JrTrainerMPic::    INCBIN "gfx/trainers/jr.trainerm.pic"
JrTrainerFPic::    INCBIN "gfx/trainers/jr.trainerf.pic"
PokemaniacPic::    INCBIN "gfx/trainers/pokemaniac.pic"
SuperNerdPic::     INCBIN "gfx/trainers/supernerd.pic"
HikerPic::         INCBIN "gfx/trainers/hiker.pic"
BikerPic::         INCBIN "gfx/trainers/biker.pic"
BurglarPic::       INCBIN "gfx/trainers/burglar.pic"
EngineerPic::      INCBIN "gfx/trainers/engineer.pic"
FisherPic::        INCBIN "gfx/trainers/fisher.pic"
SwimmerPic::       INCBIN "gfx/trainers/swimmer.pic"
CueBallPic::       INCBIN "gfx/trainers/cueball.pic"
GamblerPic::       INCBIN "gfx/trainers/gambler.pic"
BeautyPic::        INCBIN "gfx/trainers/beauty.pic"
PsychicPic::       INCBIN "gfx/trainers/psychic.pic"
RockerPic::        INCBIN "gfx/trainers/rocker.pic"
JugglerPic::       INCBIN "gfx/trainers/juggler.pic"
TamerPic::         INCBIN "gfx/trainers/tamer.pic"
BirdKeeperPic::    INCBIN "gfx/trainers/birdkeeper.pic"
BlackbeltPic::     INCBIN "gfx/trainers/blackbelt.pic"
Rival1Pic::        INCBIN "gfx/trainers/rival1.pic"
+NameHere1Pic::    INCBIN "gfx/trainers/newtrainer1.pic"

+SECTION "Trainer Pics 2", ROMX

ProfOakPic::       INCBIN "gfx/trainers/prof.oak.pic"
-ChiefPic::
ScientistPic::     INCBIN "gfx/trainers/scientist.pic"
GiovanniPic::      INCBIN "gfx/trainers/giovanni.pic"
RocketPic::        INCBIN "gfx/trainers/rocket.pic"
CooltrainerMPic::  INCBIN "gfx/trainers/cooltrainerm.pic"
CooltrainerFPic::  INCBIN "gfx/trainers/cooltrainerf.pic"
BrunoPic::         INCBIN "gfx/trainers/bruno.pic"
BrockPic::         INCBIN "gfx/trainers/brock.pic"
MistyPic::         INCBIN "gfx/trainers/misty.pic"
LtSurgePic::       INCBIN "gfx/trainers/lt.surge.pic"
ErikaPic::         INCBIN "gfx/trainers/erika.pic"
KogaPic::          INCBIN "gfx/trainers/koga.pic"
BlainePic::        INCBIN "gfx/trainers/blaine.pic"
SabrinaPic::       INCBIN "gfx/trainers/sabrina.pic"
GentlemanPic::     INCBIN "gfx/trainers/gentleman.pic"
Rival2Pic::        INCBIN "gfx/trainers/rival2.pic"
Rival3Pic::        INCBIN "gfx/trainers/rival3.pic"
LoreleiPic::       INCBIN "gfx/trainers/lorelei.pic"
ChannelerPic::     INCBIN "gfx/trainers/channeler.pic"
AgathaPic::        INCBIN "gfx/trainers/agatha.pic"
LancePic::         INCBIN "gfx/trainers/lance.pic"
+NameHere2Pic::    INCBIN "gfx/trainers/newtrainer2.pic"
Clone this wiki locally