summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md778
-rw-r--r--Tutorials.md4
-rw-r--r--screenshots/enemy-dvs.pngbin0 -> 2101 bytes
-rw-r--r--screenshots/enemy-nicknames.pngbin0 -> 2249 bytes
4 files changed, 779 insertions, 3 deletions
diff --git a/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md b/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md
new file mode 100644
index 0000000..70b2edf
--- /dev/null
+++ b/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md
@@ -0,0 +1,778 @@
+The NPC trainer teams in pokecrystal are fairly limited: their Pokémon can hold items and have custom movesets, but they cannot have nicknames, custom DVs (so enemy Pokémon can't be shiny or have well-typed Hidden Power), or custom stat experience. The trainer party data is also stored in a single ROM bank, which limits how many teams you can have; and the code for reading teams is repetitive and hard to edit.
+
+This tutorial will fix all of those problems.
+
+
+## TOC
+
+
+## 1. Refactor trainer types to use bit flags
+
+Each enemy trainer has one to six Pokémon, with individual data depending on the trainer type:
+
+- `TRAINERTYPE_NORMAL`: level, species
+- `TRAINERTYPE_MOVES`: level, species, four moves
+- `TRAINERTYPE_ITEM`: level, species, held item
+- `TRAINERTYPE_ITEM_MOVES`: level, species, held item, four moves
+
+Clearly there's a lot of shared data across those four types. But if you look at `ReadTrainerParty` in [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm), you'll see that each trainer type has a totally separate routine for reading their data, so there are four identical chunks of code for reading the level and species, two for the moves, and two for the items.
+
+An alternative is to treat the trainer type byte as a set of *bit flags*. Without any bits set, their Pokémon will just have a level and species; but then one bit will toggle reading of held items, one for movesets, and six more bits will be available for other new kinds of data.
+
+Edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm):
+
+```diff
+-; TrainerTypes indexes (see engine/battle/read_trainer_party.asm)
+- const_def
+- const TRAINERTYPE_NORMAL
+- const TRAINERTYPE_MOVES
+- const TRAINERTYPE_ITEM
+- const TRAINERTYPE_ITEM_MOVES
++; TrainerTypes bits (see engine/battle/read_trainer_party.asm)
++ const_def
++ const TRAINERTYPE_MOVES_F ; 0
++ const TRAINERTYPE_ITEM_F ; 1
++
++; Trainer party types (see data/trainers/parties.asm)
++TRAINERTYPE_NORMAL EQU 0
++TRAINERTYPE_MOVES EQU 1 << TRAINERTYPE_MOVES_F
++TRAINERTYPE_ITEM EQU 1 << TRAINERTYPE_ITEM_F
++TRAINERTYPE_ITEM_MOVES EQU TRAINERTYPE_MOVES | TRAINERTYPE_ITEM
+```
+
+Ironically, the numeric values of the `TRAINERTYPE_*` constants haven't even changed. They still go from 0 to 3.
+
+Edit [wram.asm](../blob/master/wram.asm):
+
+```diff
+- ds 3
++wOtherTrainerType:: db
++
++ ds 2
+
+ wLinkBattleRNs:: ds 10 ; d1fa
+
+ ...
+
+ wOtherTrainerClass:: ; d22f
+ ; class (Youngster, Bug Catcher, etc.) of opposing trainer
+ ; 0 if opponent is a wild Pokémon, not a trainer
+ db
+```
+
+The `wOtherTrainerType` byte will store the trainer type while their data is being read.
+
+Finally, edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm):
+
+```diff
+ ReadTrainerParty:
+ ...
+
+ ld a, [hli]
+- ld c, a
+- ld b, 0
++ ld [wOtherTrainerType], a
+ ld d, h
+ ld e, l
+- ld hl, TrainerTypes
+- add hl, bc
+- add hl, bc
+- ld a, [hli]
+- ld h, [hl]
+- ld l, a
+- ld bc, .done
+- push bc
+- jp hl
+ call ReadTrainerPartyPieces
+-
+ .done
+ jp ComputeTrainerReward
+
+ .cal2
+ ld a, BANK(sMysteryGiftTrainer)
+ call GetSRAMBank
++ ld a, TRAINERTYPE_MOVES
++ ld [wOtherTrainerType], a
+ ld de, sMysteryGiftTrainer
+- call TrainerType2
++ call ReadTrainerPartyPieces
+ call CloseSRAM
+ jr .done
+
+-TrainerTypes:
+-; entries correspond to TRAINERTYPE_* constants
+- dw TrainerType1 ; level, species
+- dw TrainerType2 ; level, species, moves
+- dw TrainerType3 ; level, species, item
+- dw TrainerType4 ; level, species, item, moves
+-
+-TrainerType1:
+-; normal (level, species)
+- ld h, d
+- ld l, e
+-.loop
+- ...
+- jr .loop
+-
+-TrainerType2:
+-; moves
+- ld h, d
+- ld l, e
+-.loop
+- ...
+- jr .loop
+-
+-TrainerType3:
+-; item
+- ld h, d
+- ld l, e
+-.loop
+- ...
+- jr .loop
+-
+-TrainerType4:
+-; item + moves
+- ld h, d
+- ld l, e
+-.loop
+- ...
+- jr .loop
++ReadTrainerPartyPieces:
++ ld h, d
++ ld l, e
++
++.loop
++; end?
++ ld a, [hli]
++ cp -1
++ ret z
++
++; level
++ ld [wCurPartyLevel], a
++
++; species
++ ld a, [hli]
++ ld [wCurPartySpecies], a
++
++; add to party
++ ld a, OTPARTYMON
++ ld [wMonType], a
++ push hl
++ predef TryAddMonToParty
++ pop hl
++
++; item?
++ ld a, [wOtherTrainerType]
++ bit TRAINERTYPE_ITEM_F, a
++ jr z, .no_item
++
++ push hl
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1Item
++ call GetPartyLocation
++ ld d, h
++ ld e, l
++ pop hl
++
++ ld a, [hli]
++ ld [de], a
++.no_item
++
++; moves?
++ ld a, [wOtherTrainerType]
++ bit TRAINERTYPE_MOVES_F, a
++ jr z, .no_moves
++
++ push hl
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1Moves
++ call GetPartyLocation
++ ld d, h
++ ld e, l
++ pop hl
++
++ ld b, NUM_MOVES
++.copy_moves
++ ld a, [hli]
++ ld [de], a
++ inc de
++ dec b
++ jr nz, .copy_moves
++
++ push hl
++
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1
++ call GetPartyLocation
++ ld d, h
++ ld e, l
++ ld hl, MON_PP
++ add hl, de
++
++ push hl
++ ld hl, MON_MOVES
++ add hl, de
++ pop de
++
++ ld b, NUM_MOVES
++.copy_pp
++ ld a, [hli]
++ and a
++ jr z, .copied_pp
++
++ push hl
++ push bc
++ dec a
++ ld hl, Moves + MOVE_PP
++ ld bc, MOVE_LENGTH
++ call AddNTimes
++ ld a, BANK(Moves)
++ call GetFarByte
++ pop bc
++ pop hl
++
++ ld [de], a
++ inc de
++ dec b
++ jr nz, .copy_pp
++.copied_pp
++
++ pop hl
++.no_moves
++
++ jp .loop
+```
+
+We've replaced the four routines `TrainerType1`, `TrainerType2`, `TrainerType3`, and `TrainerType4` with a single `ReadTrainerPartyPieces` routine. If you compare them all side by side, you'll notice how the chunks of `ReadTrainerPartyPieces` are all taken from the old routines, but now they don't need repeating.
+
+If we stopped here, the code would be cleaner and smaller, but would not do anything new. So let's continue.
+
+
+## 2. Add a trainer type flag for nicknames
+
+This will allow enemy trainer parties to define nicknames for their Pokémon.
+
+Edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm) again:
+
+```diff
+ ; TrainerTypes bits (see engine/battle/read_trainer_party.asm)
+ const_def
+ const TRAINERTYPE_MOVES_F ; 0
+ const TRAINERTYPE_ITEM_F ; 1
++ const TRAINERTYPE_NICKNAME_F ; 2
+
+ ; Trainer party types (see data/trainers/parties.asm)
+ TRAINERTYPE_NORMAL EQU 0
+ TRAINERTYPE_MOVES EQU 1 << TRAINERTYPE_MOVES_F
+ TRAINERTYPE_ITEM EQU 1 << TRAINERTYPE_ITEM_F
+ TRAINERTYPE_ITEM_MOVES EQU TRAINERTYPE_MOVES | TRAINERTYPE_ITEM
++TRAINERTYPE_NICKNAME EQU 1 << TRAINERTYPE_NICKNAME_F
+```
+
+I'm not bothering to define new `TRAINERTYPE_*` constants for every combination of {moves, item, nickname}. You can just combine flag values, like `TRAINERTYPE_NICKNAME | TRAINERTYPE_ITEM` for a trainer with Pokémon that have nicknames and held items.
+
+Edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again:
+
+```diff
+ ; add to party
+ ld a, OTPARTYMON
+ ld [wMonType], a
+ push hl
+ predef TryAddMonToParty
+ pop hl
++
++; nickname?
++ ld a, [wOtherTrainerType]
++ bit TRAINERTYPE_NICKNAME_F, a
++ jr z, .no_nickname
++
++ push de
++
++ ld de, wStringBuffer2
++.copy_nickname
++ ld a, [hli]
++ ld [de], a
++ inc de
++ cp "@"
++ jr nz, .copy_nickname
++
++ push hl
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMonNicknames
++ ld bc, MON_NAME_LENGTH
++ call AddNTimes
++ ld d, h
++ ld e, l
++ ld hl, wStringBuffer2
++ ld bc, MON_NAME_LENGTH
++ call CopyBytes
++ pop hl
++
++ pop de
++.no_nickname
+
+ ; item?
+ ...
+```
+
+Then edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm):
+
+```diff
+LoadEnemyMon:
+ ...
+
+ ld a, [wTempEnemyMonSpecies]
+ ld [wd265], a
+
+- call GetPokemonName
+-
+ ; Did we catch it?
+ ld a, [wBattleMode]
+ and a
+ ret z
+
+ ; Update enemy nick
++ ld a, [wBattleMode]
++ dec a ; WILD_BATTLE?
++ jr z, .no_nickname
++ ld a, [wOtherTrainerType]
++ bit TRAINERTYPE_NICKNAME_F, a
++ jr z, .no_nickname
++ ld a, [wCurPartyMon]
++ ld hl, wOTPartyMonNicknames
++ ld bc, MON_NAME_LENGTH
++ call AddNTimes
++ jr .got_nickname
++.no_nickname
++ call GetPokemonName
+ ld hl, wStringBuffer1
++.got_nickname
+ ld de, wEnemyMonNick
+ ld bc, MON_NAME_LENGTH
+ call CopyBytes
+```
+
+Now you can give nicknames to enemy Pokémon. Be sure to keep the data in order: level, species, nickname, held item, moves.
+
+For example, these are parties for your rival that give him nicknames, held items, and a new Pokémon:
+
+```
+Rival1Group:
+ ; RIVAL1 (1)
+ db "?@", TRAINERTYPE_NICKNAME | TRAINERTYPE_ITEM
+ db 3, RATTATA, "RATTATA@", NO_ITEM
+ db 5, CHIKORITA, "ROOT@", BERRY
+ db -1 ; end
+
+ ; RIVAL1 (2)
+ db "?@", TRAINERTYPE_NICKNAME | TRAINERTYPE_ITEM
+ db 3, RATTATA, "RATTATA@", NO_ITEM
+ db 5, CYNDAQUIL, "BLAZE@", BERRY
+ db -1 ; end
+
+ ; RIVAL1 (3)
+ db "?@", TRAINERTYPE_NICKNAME | TRAINERTYPE_ITEM
+ db 3, RATTATA, "RATTATA@", NO_ITEM
+ db 5, TOTODILE, "JAWS@", BERRY
+ db -1 ; end
+
+ ...
+```
+
+Which successfully loads in battle:
+
+![Screenshot](screenshots/enemy-nicknames.png)
+
+Note that since −1 ($FF) is the end-of-party marker, you can't use the digit "9" in nicknames because it's equal to $FF (as seen in [charmap.asm](../blob/master/charmap.asm)). If you really need a nickname with "9" in it, you can add a duplicate character that looks just like "9", for example by editing the "『" in [gfx/font/font_battle_extra.png](../blob/master/gfx/font/font_battle_extra.png) (since "『" is unused character $72).
+
+
+## 3. Add a trainer type flag for DVs
+
+This will allow enemy trainer parties to define individual DVs for their Pokémon, which not only affects their stats, but also gender, shininess, and Hidden Power type.
+
+Edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm) again:
+
+```diff
+ ; TrainerTypes bits (see engine/battle/read_trainer_party.asm)
+ const_def
+ const TRAINERTYPE_MOVES_F ; 0
+ const TRAINERTYPE_ITEM_F ; 1
+ const TRAINERTYPE_NICKNAME_F ; 2
++ const TRAINERTYPE_DVS_F ; 3
+
+ ; Trainer party types (see data/trainers/parties.asm)
+ TRAINERTYPE_NORMAL EQU 0
+ TRAINERTYPE_MOVES EQU 1 << TRAINERTYPE_MOVES_F
+ TRAINERTYPE_ITEM EQU 1 << TRAINERTYPE_ITEM_F
+ TRAINERTYPE_ITEM_MOVES EQU TRAINERTYPE_MOVES | TRAINERTYPE_ITEM
+ TRAINERTYPE_NICKNAME EQU 1 << TRAINERTYPE_NICKNAME_F
++TRAINERTYPE_DVS EQU 1 << TRAINERTYPE_DVS_F
++
++PERFECT_DV EQU $11 ; treated as $FF in enemy party data
+```
+
+Again, I'm not bothering to define new `TRAINERTYPE_*` constants for every combination of {moves, item, nickname, DVs}. You can just combine individual flag values.
+
+Edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again:
+
+```diff
+ ; add to party
+ ld a, OTPARTYMON
+ ld [wMonType], a
+ push hl
+ predef TryAddMonToParty
+ pop hl
+
+ ; nickname?
+ ld a, [wOtherTrainerType]
+ bit TRAINERTYPE_NICKNAME_F, a
+ jr z, .no_nickname
+ ...
+ .no_nickname
++
++; dvs?
++ ld a, [wOtherTrainerType]
++ bit TRAINERTYPE_DVS_F, a
++ jr z, .no_dvs
++
++ push hl
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1DVs
++ call GetPartyLocation
++ ld d, h
++ ld e, l
++ pop hl
++
++; When reading DVs, treat PERFECT_DV as $ff
++ ld a, [hli]
++ cp PERFECT_DV
++ jr nz, .atk_def_dv_ok
++ ld a, $ff
++.atk_def_dv_ok
++ ld [de], a
++ inc de
++ ld a, [hli]
++ cp PERFECT_DV
++ jr nz, .spd_spc_dv_ok
++ ld a, $ff
++.spd_spc_dv_ok
++ ld [de], a
++.no_dvs
+
+ ; item?
+ ...
+
+ .no_moves
++
++; Custom DVs affect stats, so recalculate them after TryAddMonToParty
++ ld a, [wOtherTrainerType]
++ and TRAINERTYPE_DVS
++ jr z, .no_stat_recalc
++
++ push hl
++
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1MaxHP
++ call GetPartyLocation
++ ld d, h
++ ld e, l
++
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1StatExp - 1
++ call GetPartyLocation
++
++; recalculate stats
++ ld b, TRUE
++ push de
++ predef CalcMonStats
++ pop hl
++
++; copy max HP to current HP
++ inc hl
++ ld c, [hl]
++ dec hl
++ ld b, [hl]
++ dec hl
++ ld [hl], c
++ dec hl
++ ld [hl], b
++
++ pop hl
++.no_stat_recalc
+
+ jp .loop
+```
+
+Then edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm) again:
+
+```diff
+ .InitDVs:
+-; Trainer DVs
+-
+-; All trainers have preset DVs, determined by class
+-; See GetTrainerDVs for more on that
+- farcall GetTrainerDVs
+-; These are the DVs we'll use if we're actually in a trainer battle
+ ld a, [wBattleMode]
+ dec a
+- jr nz, .UpdateDVs
++ jr z, .WildDVs
++
++; Trainer DVs
++ ld a, [wCurPartyMon]
++ ld hl, wOTPartyMon1DVs
++ call GetPartyLocation
++ ld b, [hl]
++ inc hl
++ ld c, [hl]
++ jr .UpdateDVs
+
++.WildDVs:
+ ; Wild DVs
+ ...
+```
+
+Now you can give custom DVs to enemy Pokémon. Be sure to keep the data in order: level, species, nickname, DVs, held item, moves.
+
+DVs are specified as `$AD, $SP`, where *A* = attack, *D* = defense, *S* = speed, and *P* = special, with each one going from $0 to $F (15).
+
+For example, these are parties for your rival that give him custom DVs and held items:
+
+```
+Rival1Group:
+ ; RIVAL1 (1)
+ db "?@", TRAINERTYPE_DVS | TRAINERTYPE_ITEM
+ db 3, RATTATA, PERFECT_DV, PERFECT_DV, NO_ITEM ; top percentage
+ db 5, CHIKORITA, ATKDEFDV_SHINY, SPDSPCDV_SHINY, BERRY
+ db -1 ; end
+
+ ; RIVAL1 (2)
+ db "?@", TRAINERTYPE_DVS | TRAINERTYPE_ITEM
+ db 3, RATTATA, PERFECT_DV, PERFECT_DV, NO_ITEM ; top percentage
+ db 5, CYNDAQUIL, ATKDEFDV_SHINY, SPDSPCDV_SHINY, BERRY
+ db -1 ; end
+
+ ; RIVAL1 (3)
+ db "?@", TRAINERTYPE_DVS | TRAINERTYPE_ITEM
+ db 3, RATTATA, PERFECT_DV, PERFECT_DV, NO_ITEM ; top percentage
+ db 5, TOTODILE, ATKDEFDV_SHINY, SPDSPCDV_SHINY, BERRY
+ db -1 ; end
+
+ ...
+```
+
+Which successfully loads in battle:
+
+![Screenshot](screenshots/enemy-dvs.png)
+
+Note that since −1 ($FF) is the end-of-party marker, you can't use $FF for any DVs. That's why `PERFECT_DV` gets turned into $FF, as explained in the comments. It's defined as $11 since you're unlikely to want those specific DVs, but you can use any value for it. If you want to use $00, you should also replace the two `cp PERFECT_DV` lines with `and a` since that's a more efficient way to check for zero.
+
+
+## 4. Add a trainer type flag for stat experience
+
+This will allow enemy trainer parties to define individual stat experience for their Pokémon, which lets you increase the difficulty better than just raising levels.
+
+Edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm) again:
+
+```diff
+ ; TrainerTypes bits (see engine/battle/read_trainer_party.asm)
+ const_def
+ const TRAINERTYPE_MOVES_F ; 0
+ const TRAINERTYPE_ITEM_F ; 1
+ const TRAINERTYPE_NICKNAME_F ; 2
+ const TRAINERTYPE_DVS_F ; 3
++ const TRAINERTYPE_STAT_EXP_F ; 4
+
+ ; Trainer party types (see data/trainers/parties.asm)
+ TRAINERTYPE_NORMAL EQU 0
+ TRAINERTYPE_MOVES EQU 1 << TRAINERTYPE_MOVES_F
+ TRAINERTYPE_ITEM EQU 1 << TRAINERTYPE_ITEM_F
+ TRAINERTYPE_ITEM_MOVES EQU TRAINERTYPE_MOVES | TRAINERTYPE_ITEM
+ TRAINERTYPE_NICKNAME EQU 1 << TRAINERTYPE_NICKNAME_F
+ TRAINERTYPE_DVS EQU 1 << TRAINERTYPE_DVS_F
++TRAINERTYPE_STAT_EXP EQU 1 << TRAINERTYPE_STAT_EXP_F
+
+ PERFECT_DV EQU $11 ; treated as $FF in enemy party data
++PERFECT_STAT_EXP EQU $1337 ; treated as $FFFF in enemy party data
+```
+
+Edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again:
+
+```diff
+ ; add to party
+ ld a, OTPARTYMON
+ ld [wMonType], a
+ push hl
+ predef TryAddMonToParty
+ pop hl
+
+ ; nickname?
+ ld a, [wOtherTrainerType]
+ bit TRAINERTYPE_NICKNAME_F, a
+ jr z, .no_nickname
+ ...
+ .no_nickname
+
+ ; dvs?
+ ld a, [wOtherTrainerType]
+ bit TRAINERTYPE_DVS_F, a
+ jr z, .no_dvs
+ ...
+ .no_dvs
++
++; stat exp?
++ ld a, [wOtherTrainerType]
++ bit TRAINERTYPE_STAT_EXP_F, a
++ jr z, .no_stat_exp
++
++ push hl
++ ld a, [wOTPartyCount]
++ dec a
++ ld hl, wOTPartyMon1StatExp
++ call GetPartyLocation
++ ld d, h
++ ld e, l
++ pop hl
++
++ ld c, NUM_EXP_STATS
++.stat_exp_loop
++; When reading stat experience, treat PERFECT_STAT_EXP as $FFFF
++ ld a, [hl]
++ cp LOW(PERFECT_STAT_EXP)
++ jr nz, .not_perfect_stat_exp
++ inc hl
++ ld a, [hl]
++ cp HIGH(PERFECT_STAT_EXP)
++ dec hl
++ jr nz, .not_perfect_stat_exp
++ ld a, $ff
++rept 2
++ ld [de], a
++ inc de
++ inc hl
++endr
++ jr .continue_stat_exp
++
++.not_perfect_stat_exp
++rept 2
++ ld a, [hli]
++ ld [de], a
++ inc de
++endr
++.continue_stat_exp
++ dec c
++ jr nz, .stat_exp_loop
++.no_stat_exp
+
+ ; item?
+ ...
+
+ .no_moves
+
+-; Custom DVs affect stats, so recalculate them after TryAddMonToParty
++; Custom DVs or stat experience affect stats,
++; so recalculate them after TryAddMonToParty
+ ld a, [wOtherTrainerType]
+- and TRAINERTYPE_DVS
++ and TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP
+ jr z, .no_stat_recalc
+ ...
+ .no_stat_recalc
+
+ jp .loop
+```
+
+(If you're using an older version of pokecrystal where `NUM_EXP_STATS` is not defined, then replace `ld c, NUM_EXP_STATS` with `ld c, 5`.)
+
+Then edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm) again:
+
+```diff
+ LoadEnemyMon:
+ ...
+
+ ; Fill stats
+ ld de, wEnemyMonMaxHP
+ ld b, FALSE
+ ld hl, wEnemyMonDVs - (MON_DVS - MON_STAT_EXP + 1) ; wLinkBattleRNs + 7 ; ?
++ ld a, [wBattleMode]
++ cp TRAINER_BATTLE
++ jr nz, .no_stat_exp
++ ld a, [wCurPartyMon]
++ ld hl, wOTPartyMon1StatExp - 1
++ call GetPartyLocation
++ ld b, TRUE
++.no_stat_exp
+ predef CalcMonStats
+```
+
+Now you can give custom stat experience to enemy Pokémon. Be sure to keep the data in order: level, species, nickname, DVs, stat experience, held item, moves.
+
+Stat experience is specified as five *words*, not bytes, because each of the five stats (HP, Attack, Defense, Speed, and Special) has two-byte experience going from $0000 to $FFFF (65,535).
+
+For example, these are parties for your rival that give him custom DVs, stat experience, held items, and moves:
+
+```
+Rival1Group:
+ ; RIVAL1 (1)
+ db "?@", TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP | TRAINERTYPE_ITEM | TRAINERTYPE_MOVES
+ db 3, RATTATA
+ db PERFECT_DV, PERFECT_DV ; atk|def, spd|spc
+ dw PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP ; hp, atk, def, spd, spc
+ db NO_ITEM
+ db TACKLE, TAIL_WHIP, BITE, NO_MOVE ; Bite is an egg move
+ db 5, CHIKORITA
+ db ATKDEFDV_SHINY, SPDSPCDV_SHINY ; atk|def, spd|spc
+ dw $0000, $0000, $0000, $0000, $0000 ; hp, atk, def, spd, spc
+ db BERRY
+ db TACKLE, GROWL, NO_MOVE, NO_MOVE
+ db -1 ; end
+
+ ; RIVAL1 (2)
+ db "?@", TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP | TRAINERTYPE_ITEM | TRAINERTYPE_MOVES
+ db 3, RATTATA
+ db PERFECT_DV, PERFECT_DV ; atk|def, spd|spc
+ dw PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP ; hp, atk, def, spd, spc
+ db NO_ITEM
+ db TACKLE, TAIL_WHIP, BITE, NO_MOVE ; Bite is an egg move
+ db 5, CYNDAQUIL
+ db ATKDEFDV_SHINY, SPDSPCDV_SHINY ; atk|def, spd|spc
+ dw $0000, $0000, $0000, $0000, $0000 ; hp, atk, def, spd, spc
+ db BERRY
+ db TACKLE, LEER, NO_MOVE, NO_MOVE
+ db -1 ; end
+
+ ; RIVAL1 (3)
+ db "?@", TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP | TRAINERTYPE_ITEM | TRAINERTYPE_MOVES
+ db 3, RATTATA
+ db PERFECT_DV, PERFECT_DV ; atk|def, spd|spc
+ dw PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP, PERFECT_STAT_EXP ; hp, atk, def, spd, spc
+ db NO_ITEM
+ db TACKLE, TAIL_WHIP, BITE, NO_MOVE ; Bite is an egg move
+ db 5, TOTODILE
+ db ATKDEFDV_SHINY, SPDSPCDV_SHINY ; atk|def, spd|spc
+ dw $0000, $0000, $0000, $0000, $0000 ; hp, atk, def, spd, spc
+ db BERRY
+ db SCRATCH, LEER, NO_MOVE, NO_MOVE
+ db -1 ; end
+
+ ...
+```
+
+Which successfully loads in battle (no screenshot because stat experience isn't visible).
+
+
+## 5. Allow trainer data to be stored in multiple banks
+
+TODO
+
+
+## 6. Add a trainer type flag for variable parties
+
+[TODO](https://hax.iimarckus.org/topic/7137/)
diff --git a/Tutorials.md b/Tutorials.md
index 17ea718..a7c31bd 100644
--- a/Tutorials.md
+++ b/Tutorials.md
@@ -40,6 +40,7 @@ Tutorials may use diff syntax to show edits:
- [Allow map tiles to appear above sprites (so NPCs can walk behind tiles) with `PRIORITY` colors](Allow-map-tiles-to-appear-above-sprites-\(so-NPCs-can-walk-behind-tiles\)-with-PRIORITY-colors)
- [Allow tiles to have different attributes in different blocks (including X and Y flip)](Allow-tiles-to-have-different-attributes-in-different-blocks-\(including-X-and-Y-flip\))
- [Increase Pokémon sprite animation size](Increase-Pokémon-sprite-animation-size)
+- [Allow more trainer parties, with individual DVs, stat experience, and nicknames](Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames)
- [Remove the 25% failure chance for AI status moves](Remove-the-25%25-failure-chance-for-AI-status-moves)
- [Colored trainer card badges](Colored-trainer-card-badges)
- [Show the tops of leaders' heads on the trainer card](Show-the-tops-of-leaders-heads-on-the-trainer-card)
@@ -73,13 +74,10 @@ Tutorials may use diff syntax to show edits:
- Evolution methods (location, held item, move, [etc](https://gitgud.io/pfero/axyllagame/commit/81914d43eb89195734caee724c0a40d4686a0bab))
- More daily and weekly events
- Third trainer card page for Kanto badges
-- Allow multiple banks of trainer party data
-- Custom DVs for individual trainer teams (allows trainers to have shiny Pokémon)
- Remove all Pokémon sprite animations
- Implement dynamic overhead+underfoot bridges
- Pan the camera for cutscenes by making the player invisible
- Gain experience from catching Pokémon
- Evolution moves from Gen 7
-- A trainer type that has a different party for all 16 badge counts (useful for open world games) ([via](https://hax.iimarckus.org/topic/7137/))
- Nuzlocke mode (an in-game enforced [Nuzlocke Challenge](https://bulbapedia.bulbagarden.net/wiki/Nuzlocke_Challenge))
- Useful unused content (`COLL_CURRENT_*`, `HELD_PREVENT_*`, `ENVIRONMENT_5`, `TRADE_GENDER_MALE`, `GROWTH_SLIGHTLY_*`, `SPRITE_UNUSED_GUY`, `PAL_NPC_PINK`, etc)
diff --git a/screenshots/enemy-dvs.png b/screenshots/enemy-dvs.png
new file mode 100644
index 0000000..bd44aab
--- /dev/null
+++ b/screenshots/enemy-dvs.png
Binary files differ
diff --git a/screenshots/enemy-nicknames.png b/screenshots/enemy-nicknames.png
new file mode 100644
index 0000000..4201479
--- /dev/null
+++ b/screenshots/enemy-nicknames.png
Binary files differ