From 2d33e7b528a9096064ef06b90e52fbf0d55603a3 Mon Sep 17 00:00:00 2001 From: Idain Date: Wed, 30 Jun 2021 22:56:25 -0400 Subject: Added new step to give enemy parties individual happiness values. --- ...dividual-DVs,-stat-experience,-and-nicknames.md | 1450 ------------------ ...-experience,-nicknames,-variable-teams,-etc..md | 1572 ++++++++++++++++++++ 2 files changed, 1572 insertions(+), 1450 deletions(-) delete mode 100644 Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md create mode 100644 Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-nicknames,-variable-teams,-etc..md 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 deleted file mode 100644 index 59d93f1..0000000 --- a/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md +++ /dev/null @@ -1,1450 +0,0 @@ -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. - -(The code for this feature was adapted from [Pokémon Polished Crystal](https://github.com/Rangi42/polishedcrystal/).) - - -## Contents - -1. [Refactor trainer types to use bit flags](#1-refactor-trainer-types-to-use-bit-flags) -2. [Add a trainer type flag for nicknames](#2-add-a-trainer-type-flag-for-nicknames) -3. [Add a trainer type flag for DVs](#3-add-a-trainer-type-flag-for-dvs) -4. [Add a trainer type flag for stat experience](#4-add-a-trainer-type-flag-for-stat-experience) -5. [Allow trainer data to be stored in multiple banks](#5-allow-trainer-data-to-be-stored-in-multiple-banks) -6. [Add a trainer type flag for variable parties](#6-add-a-trainer-type-flag-for-variable-parties) - - -## 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 - ... - NEXTU - ; catch tutorial dude pack - wDudeNumItems:: db - wDudeItems:: ds 2 * 4 + 1 - - wDudeNumKeyItems:: db - wDudeKeyItems:: ds 18 + 1 - - wDudeNumBalls:: db - wDudeBalls:: ds 2 * 4 + 1 - ENDU - -- ds 4 -+wOtherTrainerType:: db -+ ds 3 - ... -``` - -The `wOtherTrainerType` byte will store the trainer type while their data is being read. - -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 OpenSRAM -+ 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. - -Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm): - -```diff - RandomPhoneMon: - ; Get a random monster owned by the trainer who's calling. - ... - - .skip_name - ld a, BANK(Trainers) - call GetFarByte - inc hl - cp "@" - jr nz, .skip_name - - ld a, BANK(Trainers) - call GetFarByte - inc hl -- ld bc, 2 ; level, species -- cp TRAINERTYPE_NORMAL -- jr z, .got_mon_length -- ld bc, 2 + NUM_MOVES ; level, species, moves -- cp TRAINERTYPE_MOVES -- jr z, .got_mon_length -- ld bc, 2 + 1 ; level, species, item -- cp TRAINERTYPE_ITEM -- jr z, .got_mon_length -- ; TRAINERTYPE_ITEM_MOVES -- ld bc, 2 + 1 + NUM_MOVES ; level, species, item, moves --.got_mon_length -+; b = trainer type -+ ld b, a -+; c = mon length -+; All trainers use 2 bytes for level and species -+ ld c, 2 -+; TRAINERTYPE_ITEM uses 1 more byte -+ bit TRAINERTYPE_ITEM_F, b -+ jr z, .no_item -+ inc c -+.no_item -+; TRAINERTYPE_MOVES uses NUM_MOVES (4) more bytes -+ bit TRAINERTYPE_MOVES_F, b -+ jr z, .no_moves -+ ld a, NUM_MOVES -+ add c -+ ld c, a -+.no_moves -+; bc = mon length -+ xor a -+ ld b, a -``` - -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 -+ -+ ld a, [hli] -+ cp "@" -+ jr z, .no_nickname -+ -+ push de -+ -+ ld de, wStringBuffer2 -+ ld [de], a -+ inc de -+.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 [wNamedObjectIndex], 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, wEnemyMonNickname - ld bc, MON_NAME_LENGTH - call CopyBytes -``` - -Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: - -```diff - RandomPhoneMon: - ; Get a random monster owned by the trainer who's calling. - ... - - ld a, BANK(Trainers) - call GetFarByte - inc hl - ; b = trainer type - ld b, a -+; TRAINERTYPE_NICKNAME has uneven length, so always use the first mon -+ bit TRAINERTYPE_NICKNAME_F, b -+ jr nz, .got_mon - ; c = mon length - ; All trainers use 2 bytes for level and species - ld c, 2 - ... -``` - -Now you can give nicknames to enemy Pokémon. If the nickname is just `"@"`, it will use the default species name. Be sure to keep the data in order: level, species, nickname, held item, moves. - -For example, here's a party for your rival that give him nicknames, held items, and a new Pokémon: - -``` - db "?@", TRAINERTYPE_NICKNAME | TRAINERTYPE_ITEM - db 3, 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 - ... -``` - -Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: - -```diff - RandomPhoneMon: - ; Get a random monster owned by the trainer who's calling. - ... - ; c = mon length - ; All trainers use 2 bytes for level and species - ld c, 2 -+; TRAINERTYPE_DVS uses 2 more bytes -+ bit TRAINERTYPE_DVS_F, b -+ jr z, .no_dvs -+ inc c -+ inc c -+.no_dvs - ; TRAINERTYPE_ITEM uses 1 more byte - bit TRAINERTYPE_ITEM_F, b - jr z, .no_item - ... -``` - -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, here's a party with custom DVs: - -``` - db "?@", TRAINERTYPE_DVS | TRAINERTYPE_ITEM - db 3, RATTATA, $87, $77, NO_ITEM - 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 do `PERFECT_DV EQU $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) -+ 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 -``` - -Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: - -```diff - RandomPhoneMon: - ; Get a random monster owned by the trainer who's calling. - ... - ; c = mon length - ; All trainers use 2 bytes for level and species - ld c, 2 - ; TRAINERTYPE_DVS uses 2 more bytes - bit TRAINERTYPE_DVS_F, b - jr z, .no_dvs - inc c - inc c - .no_dvs -+; TRAINERTYPE_STAT_EXP uses NUM_EXP_STATS * 2 (10) more bytes -+ bit TRAINERTYPE_STAT_EXP_F, b -+ jr z, .no_stat_exp -+ ld a, NUM_EXP_STATS * 2 -+ add c -+ ld c, a -+.no_stat_exp - ; TRAINERTYPE_ITEM uses 1 more byte - bit TRAINERTYPE_ITEM_F, b - jr z, .no_item - ... -``` - -(Again, if you're using an older version of pokecrystal where `NUM_EXP_STATS` is not defined, then replace `ld a, NUM_EXP_STATS * 2` with `ld a, 10`.) - -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, here's a party with custom DVs, stat experience, held items, and moves: - -``` - db "?@", TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP | TRAINERTYPE_ITEM_MOVES - db 3, RATTATA - db PERFECT_DV, $de ; atk|def, spd|spc - dw $0040, $0060, $0020, $0040, $0000 ; 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, PERFECT_STAT_EXP, $0000, PERFECT_STAT_EXP, $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). - -Again, since −1 ($FF) is the end-of-party marker, you can't use stat experience values with $FF in them. That's why `PERFECT_STAT_EXP` gets turned into $FFFF, as explained in the comments. It's defined as $1337 since you're unlikely to want that specific value, but you can use any value for it. If you want to do `PERFECT_STAT_EXP EQU $0000`, you should also replace the `cp LOW(PERFECT_STAT_EXP)` and `cp HIGH(PERFECT_STAT_EXP)` lines with `and a` since that's a more efficient way to check for zero. - - -## 5. Allow trainer data to be stored in multiple banks - -If you're adding more trainers, *and* more data for those trainers, you'll probably run out of room in the ROM bank. The solution is to split trainer party data across multiple banks. - -Edit [wram.asm](../blob/master/wram.asm) again: - -```diff -wOtherTrainerType:: db -+wTrainerGroupBank:: db -- ds 3 -+ ds 2 -``` - -The `wTrainerGroupBank` byte will store the trainer group's bank while a trainer's data is being read. - -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 -+ dba FalknerGroup -+ ... -+ dba MysticalmanGroup -``` - -We're just replacing `dw` with `dba` everywhere. Each table entry now has a third byte to declare which bank it's in, instead of expecting all the entries to be in `BANK(Trainers)`. - -Edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again: - -```diff -+GetNextTrainerDataByte: -+ ld a, [wTrainerGroupBank] -+ call GetFarByte -+ inc hl -+ ret -+ - ReadTrainerParty: - ... - - dec a - ld c, a - ld b, 0 - ld hl, TrainerGroups - add hl, bc - add hl, bc -+ add hl, bc -+ ld a, [hli] -+ ld [wTrainerGroupBank], a - ld a, [hli] - ld h, [hl] - ld l, a - - ld a, [wOtherTrainerID] - ld b, a - .skip_trainer - dec b - jr z, .got_trainer - .loop -- ld a, [hli] -+ call GetNextTrainerDataByte - cp -1 - jr nz, .loop - jr .skip_trainer - .got_trainer - - .skip_name -- ld a, [hli] -+ call GetNextTrainerDataByte - cp "@" - jr nz, .skip_name - -- ld a, [hli] -+ call GetNextTrainerDataByte - ld [wOtherTrainerType], a - ld d, h - ld e, l - call ReadTrainerPartyPieces - .done - jp ComputeTrainerReward - - .cal2 - ld a, BANK(sMysteryGiftTrainer) - call OpenSRAM - ld a, TRAINERTYPE_MOVES - ld [wOtherTrainerType], a - ld de, sMysteryGiftTrainer - call ReadTrainerPartyPieces - call CloseSRAM - jr .done - - ReadTrainerPartyPieces: - ld h, d - ld l, e - - .loop - ; end? -- ld a, [hli] -+ call GetNextTrainerDataByte - cp -1 - ret z - - ; level - ld [wCurPartyLevel], a - - ; species -- ld a, [hli] -+ call GetNextTrainerDataByte - ld [wCurPartySpecies], a - - ; 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 - -- ld a, [hli] -+ call GetNextTrainerDataByte - cp "@" - jr z, .no_nickname - - push de - - ld de, wStringBuffer2 - ld [de], a - inc de - .copy_nickname -- ld a, [hli] -+ call GetNextTrainerDataByte - 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 - - ; 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] -+ call GetNextTrainerDataByte - cp PERFECT_DV - jr nz, .atk_def_dv_nonzero - ld a, $ff - .atk_def_dv_nonzero - ld [de], a - inc de -- ld a, [hli] -+ call GetNextTrainerDataByte - cp PERFECT_DV - jr nz, .spd_spc_dv_nonzero - ld a, $ff - .spd_spc_dv_nonzero - ld [de], a - .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] -+ call GetNextTrainerDataByte -+ dec hl - cp LOW(PERFECT_STAT_EXP) - jr nz, .not_perfect_stat_exp - inc hl -- ld a, [hl] -+ call GetNextTrainerDataByte -+ dec 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] -+ call GetNextTrainerDataByte - ld [de], a - inc de - endr - .continue_stat_exp - dec c - jr nz, .stat_exp_loop - .no_stat_exp - - ; 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] -+ call GetNextTrainerDataByte - 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] -+ call GetNextTrainerDataByte - ld [de], a - inc de - dec b - jr nz, .copy_moves - - ... - - jp .loop - - ... - - Battle_GetTrainerName:: - ld a, [wInBattleTowerBattle] - bit 0, a - ld hl, wOTPlayerName -+ ld a, BANK(Battle_GetTrainerName) -+ ld [wTrainerGroupBank], a - jp nz, CopyTrainerName - - ld a, [wOtherTrainerID] - ld b, a - ld a, [wOtherTrainerClass] - ld c, a - - GetTrainerName:: - ... - - .not_cal2 - dec c - push bc - ld b, 0 - ld hl, TrainerGroups - add hl, bc - add hl, bc -+ add hl, bc -+ ld a, [hli] -+ ld [wTrainerGroupBank], a - ld a, [hli] - ld h, [hl] - ld l, a - pop bc - - .loop - dec b - jr z, CopyTrainerName - - .skip -- ld a, [hli] -+ call GetNextTrainerDataByte - cp -1 - jr nz, .skip - jr .loop - - CopyTrainerName: - ld de, wStringBuffer1 - push de - ld bc, NAME_LENGTH -- call CopyBytes -+ ld a, [wTrainerGroupBank] -+ call FarCopyBytes - pop de - ret - - ... - --INCLUDE "data/trainers/parties.asm" -+INCLUDE "data/trainers/party_pointers.asm" -``` - -That's a long series of edits, but they're all basically the same: instead of getting data directly from `[hl]` in the current ROM bank (i.e. the bank that this code is in), we call `GetNextTrainerDataByte` to switch banks while reading party data. We also have to account for the new bank byte in each `TrainerGroups` table entry. - -Edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: - -```diff - RandomPhoneMon: - ; Get a random monster owned by the trainer who's calling. - farcall GetCallerLocation - ld hl, TrainerGroups - ld a, d - dec a - ld c, a - ld b, 0 - add hl, bc - add hl, bc -+ add hl, bc -+ ld a, BANK(TrainerGroups) -+ call GetFarByte -+ ld [wTrainerGroupBank], a -+ inc hl - ld a, BANK(TrainerGroups) - call GetFarWord - - .skip_trainer - dec e - jr z, .skipped - .skip -- ld a, BANK(Trainers) -+ ld a, [wTrainerGroupBank] - call GetFarByte - inc hl - cp -1 - jr nz, .skip - jr .skip_trainer - .skipped - - .skip_name -- ld a, BANK(Trainers) -+ ld a, [wTrainerGroupBank] - call GetFarByte - inc hl - cp "@" - jr nz, .skip_name - -- ld a, BANK(Trainers) -+ ld a, [wTrainerGroupBank] - call GetFarByte - inc hl - ... - - ld e, 0 - push hl - .count_mon - inc e - add hl, bc -- ld a, BANK(Trainers) -+ ld a, [wTrainerGroupBank] - call GetFarByte - cp -1 - jr nz, .count_mon - pop hl - - .rand - call Random - maskbits PARTY_LENGTH - cp e - jr nc, .rand - - inc a - .get_mon - dec a - jr z, .got_mon - add hl, bc - jr .get_mon - .got_mon - - inc hl ; species -- ld a, BANK(Trainers) -+ ld a, [wTrainerGroupBank] - call GetFarByte - ld [wNamedObjectIndex], a - call GetPokemonName - ld hl, wStringBuffer1 - ld de, wStringBuffer4 - ld bc, MON_NAME_LENGTH - jp CopyBytes -``` - -Again, we're repeating the same change in many places: replacing `BANK(Trainers)` with `[wTrainerGroupBank]`, and accounting for the new bank byte in each `TrainerGroups` table entry. - -Edit [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm): - -```diff --INCLUDE "data/trainers/party_pointers.asm" -- --Trainers: - ; Trainer data structure: --; - db "NAME@", TRAINERTYPE_* constant -+; - db "NAME@", TRAINERTYPE_* constants |ed together - ; - 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 -+; * in all cases: db level, species -+; * with TRAINERTYPE_NICKNAME: db "NICKNAME@" -+; * with TRAINERTYPE_DVS: db atk|def dv, spd|spc dv -+; * with TRAINERTYPE_STAT_EXP: dw hp, atk, def, spd, spc -+; * with TRAINERTYPE_ITEM: db item -+; * with TRAINERTYPE_MOVES: db move 1, move 2, move 3, move 4 -+; (TRAINERTYPE_ITEM_MOVES is just TRAINERTYPE_ITEM | TRAINERTYPE_MOVES) - ; - db -1 ; end -+ -+SECTION "Enemy Trainer Parties 1", ROMX - - FalknerGroup: - ... -``` - -`BANK(Trainers)` is now meaningless and unused, so we can simply remove that label. - -All the parties are in the same section, "Enemy Trainer Parties 1", but of course you can now create more. Just don't split a *group* across multiple sections. - -(I took this opportunity to also update the documentation, given our previous changes to the party data structure.) - -Finally, edit [main.asm](../blob/master/main.asm): - -```diff - SECTION "Enemy Trainers", ROMX - - INCLUDE "engine/battle/ai/items.asm" - INCLUDE "engine/battle/ai/scoring.asm" - INCLUDE "engine/battle/read_trainer_attributes.asm" - INCLUDE "engine/battle/read_trainer_party.asm" -+ -+ -+INCLUDE "data/trainers/parties.asm" -``` - -Since [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) doesn't `INCLUDE` [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) any more, we have to do so here. And since [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) has its own `SECTION` headings, we don't need one before it in [main.asm](../blob/master/main.asm). - -Anyway, we're done now. Just like [step 1](#1-refactor-trainer-types-to-use-bit-flags), the game doesn't do anything new, but the code has become more extensible. - - -## 6. Add a trainer type flag for variable parties - -In this step we'll add a special flag to define variable parties for the same trainer, which will vary depending on the player's number of badges. Let's edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm): - -```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 -+ const TRAINERTYPE_VARIABLE_F ; 5 - - ; 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 -+TRAINERTYPE_VARIABLE EQU 1 << TRAINERTYPE_VARIABLE_F - - PERFECT_DV EQU $11 ; treated as $FF in enemy party data - PERFECT_STAT_EXP EQU $1337 ; treated as $FFFF in enemy party data -``` - -And edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again: - -```diff -ReadTrainerPartyPieces: - ld h, d - ld l, e - -+; Variable? -+ bit TRAINERNTYPE_VARIABLE_F, a -+ jr z, .not_variable -+ ; get badge count in c -+ push hl -+ ld hl, Badges -+ ld b, 2 -+ call CountSetBits -+ pop hl -+ ; Skip that many $fe delimiters -+.outerloop -+ ld a, c -+ and a -+ jr z, .continue -+.innerloop -+ call GetNextTrainerDataByte -+ cp $fe -+ jr nz, .innerloop -+ dec c -+ jr .outerloop -+ -+.continue -+ ; Get trainer type of variable stage -+ call GetNextTrainerDataByte -+ ld [OtherTrainerType], a -+ ; fallthrough -+.not_variable - .loop - ; end? - call GetNextTrainerDataByte - cp -1 - ret z -+ cp $fe -+ ret z - - ; level - ld [wCurPartyLevel], a - ... -``` - -As an example, let's show how it'd work with Falkner: - -``` - FalknerGroup: - ; FALKNER (1) - db "Falkner@", TRAINERTYPE_VARIABLE - - ; No badges - db TRAINERTYPE_MOVES - db 7, PIDGEY, TACKLE, MUD_SLAP, NO_MOVE, NO_MOVE - db 9, PIDGEOTTO, TACKLE, MUD_SLAP, GUST, NO_MOVE - db $fe ; delimiter - - ... - - ; Six badges - db TRAINERTYPE_ITEM_MOVES - db 33, PIDGEOTTO, NO_ITEM, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK - db 35, PIDGEOT, SHARP_BEAK, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK - db $fe ; delimiter - - ... - - ; 16 badges - db TRAINERTYPE_ITEM_MOVES - db 58, PIDGEOT, NO_ITEM, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK - db 60, PIDGEOT, SHARP_BEAK, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK - db $ff ; end -``` - -Now the enemy trainer's team will vary depending on how many badges you have. -A few things to note about this: - -- One trainer ID applies to all 17 parties, depending on your badge count. So you don't have to repeat the trainer's name 17 times. -- Each party can have its own actual type (normal, item, moves, or item+moves). -- Each stage ends in $fe (except the last one ends in $ff) so you cannot use it for individual DV values (unless you change the delimiter's value, of course). diff --git a/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-nicknames,-variable-teams,-etc..md b/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-nicknames,-variable-teams,-etc..md new file mode 100644 index 0000000..1fe38d3 --- /dev/null +++ b/Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-nicknames,-variable-teams,-etc..md @@ -0,0 +1,1572 @@ +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. + +(The code for this feature was adapted from [Pokémon Polished Crystal](https://github.com/Rangi42/polishedcrystal/).) + + +## Contents + +1. [Refactor trainer types to use bit flags](#1-refactor-trainer-types-to-use-bit-flags) +2. [Add a trainer type flag for nicknames](#2-add-a-trainer-type-flag-for-nicknames) +3. [Add a trainer type flag for DVs](#3-add-a-trainer-type-flag-for-dvs) +4. [Add a trainer type flag for stat experience](#4-add-a-trainer-type-flag-for-stat-experience) +5. [Allow trainer data to be stored in multiple banks](#5-allow-trainer-data-to-be-stored-in-multiple-banks) +6. [Add a trainer type flag for variable parties](#6-add-a-trainer-type-flag-for-variable-parties) +7. [Add a trainer type flag for happiness](#7-add-a-trainer-type-flag-for-happiness) + + +## 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 + ... + NEXTU + ; catch tutorial dude pack + wDudeNumItems:: db + wDudeItems:: ds 2 * 4 + 1 + + wDudeNumKeyItems:: db + wDudeKeyItems:: ds 18 + 1 + + wDudeNumBalls:: db + wDudeBalls:: ds 2 * 4 + 1 + ENDU + +- ds 4 ++wOtherTrainerType:: db ++ ds 3 + ... +``` + +The `wOtherTrainerType` byte will store the trainer type while their data is being read. + +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 OpenSRAM ++ 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. + +Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm): + +```diff + RandomPhoneMon: + ; Get a random monster owned by the trainer who's calling. + ... + + .skip_name + ld a, BANK(Trainers) + call GetFarByte + inc hl + cp "@" + jr nz, .skip_name + + ld a, BANK(Trainers) + call GetFarByte + inc hl +- ld bc, 2 ; level, species +- cp TRAINERTYPE_NORMAL +- jr z, .got_mon_length +- ld bc, 2 + NUM_MOVES ; level, species, moves +- cp TRAINERTYPE_MOVES +- jr z, .got_mon_length +- ld bc, 2 + 1 ; level, species, item +- cp TRAINERTYPE_ITEM +- jr z, .got_mon_length +- ; TRAINERTYPE_ITEM_MOVES +- ld bc, 2 + 1 + NUM_MOVES ; level, species, item, moves +-.got_mon_length ++; b = trainer type ++ ld b, a ++; c = mon length ++; All trainers use 2 bytes for level and species ++ ld c, 2 ++; TRAINERTYPE_ITEM uses 1 more byte ++ bit TRAINERTYPE_ITEM_F, b ++ jr z, .no_item ++ inc c ++.no_item ++; TRAINERTYPE_MOVES uses NUM_MOVES (4) more bytes ++ bit TRAINERTYPE_MOVES_F, b ++ jr z, .no_moves ++ ld a, NUM_MOVES ++ add c ++ ld c, a ++.no_moves ++; bc = mon length ++ xor a ++ ld b, a +``` + +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 ++ ++ ld a, [hli] ++ cp "@" ++ jr z, .no_nickname ++ ++ push de ++ ++ ld de, wStringBuffer2 ++ ld [de], a ++ inc de ++.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 [wNamedObjectIndex], 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, wEnemyMonNickname + ld bc, MON_NAME_LENGTH + call CopyBytes +``` + +Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: + +```diff + RandomPhoneMon: + ; Get a random monster owned by the trainer who's calling. + ... + + ld a, BANK(Trainers) + call GetFarByte + inc hl + ; b = trainer type + ld b, a ++; TRAINERTYPE_NICKNAME has uneven length, so always use the first mon ++ bit TRAINERTYPE_NICKNAME_F, b ++ jr nz, .got_mon + ; c = mon length + ; All trainers use 2 bytes for level and species + ld c, 2 + ... +``` + +Now you can give nicknames to enemy Pokémon. If the nickname is just `"@"`, it will use the default species name. Be sure to keep the data in order: level, species, nickname, held item, moves. + +For example, here's a party for your rival that give him nicknames, held items, and a new Pokémon: + +``` + db "?@", TRAINERTYPE_NICKNAME | TRAINERTYPE_ITEM + db 3, 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 + ... +``` + +Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: + +```diff + RandomPhoneMon: + ; Get a random monster owned by the trainer who's calling. + ... + ; c = mon length + ; All trainers use 2 bytes for level and species + ld c, 2 ++; TRAINERTYPE_DVS uses 2 more bytes ++ bit TRAINERTYPE_DVS_F, b ++ jr z, .no_dvs ++ inc c ++ inc c ++.no_dvs + ; TRAINERTYPE_ITEM uses 1 more byte + bit TRAINERTYPE_ITEM_F, b + jr z, .no_item + ... +``` + +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, here's a party with custom DVs: + +``` + db "?@", TRAINERTYPE_DVS | TRAINERTYPE_ITEM + db 3, RATTATA, $87, $77, NO_ITEM + 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 do `PERFECT_DV EQU $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) ++ 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 +``` + +Finally, edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: + +```diff + RandomPhoneMon: + ; Get a random monster owned by the trainer who's calling. + ... + ; c = mon length + ; All trainers use 2 bytes for level and species + ld c, 2 + ; TRAINERTYPE_DVS uses 2 more bytes + bit TRAINERTYPE_DVS_F, b + jr z, .no_dvs + inc c + inc c + .no_dvs ++; TRAINERTYPE_STAT_EXP uses NUM_EXP_STATS * 2 (10) more bytes ++ bit TRAINERTYPE_STAT_EXP_F, b ++ jr z, .no_stat_exp ++ ld a, NUM_EXP_STATS * 2 ++ add c ++ ld c, a ++.no_stat_exp + ; TRAINERTYPE_ITEM uses 1 more byte + bit TRAINERTYPE_ITEM_F, b + jr z, .no_item + ... +``` + +(Again, if you're using an older version of pokecrystal where `NUM_EXP_STATS` is not defined, then replace `ld a, NUM_EXP_STATS * 2` with `ld a, 10`.) + +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, here's a party with custom DVs, stat experience, held items, and moves: + +``` + db "?@", TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP | TRAINERTYPE_ITEM_MOVES + db 3, RATTATA + db PERFECT_DV, $de ; atk|def, spd|spc + dw $0040, $0060, $0020, $0040, $0000 ; 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, PERFECT_STAT_EXP, $0000, PERFECT_STAT_EXP, $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). + +Again, since −1 ($FF) is the end-of-party marker, you can't use stat experience values with $FF in them. That's why `PERFECT_STAT_EXP` gets turned into $FFFF, as explained in the comments. It's defined as $1337 since you're unlikely to want that specific value, but you can use any value for it. If you want to do `PERFECT_STAT_EXP EQU $0000`, you should also replace the `cp LOW(PERFECT_STAT_EXP)` and `cp HIGH(PERFECT_STAT_EXP)` lines with `and a` since that's a more efficient way to check for zero. + + +## 5. Allow trainer data to be stored in multiple banks + +If you're adding more trainers, *and* more data for those trainers, you'll probably run out of room in the ROM bank. The solution is to split trainer party data across multiple banks. + +Edit [wram.asm](../blob/master/wram.asm) again: + +```diff +wOtherTrainerType:: db ++wTrainerGroupBank:: db +- ds 3 ++ ds 2 +``` + +The `wTrainerGroupBank` byte will store the trainer group's bank while a trainer's data is being read. + +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 ++ dba FalknerGroup ++ ... ++ dba MysticalmanGroup +``` + +We're just replacing `dw` with `dba` everywhere. Each table entry now has a third byte to declare which bank it's in, instead of expecting all the entries to be in `BANK(Trainers)`. + +Edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again: + +```diff ++GetNextTrainerDataByte: ++ ld a, [wTrainerGroupBank] ++ call GetFarByte ++ inc hl ++ ret ++ + ReadTrainerParty: + ... + + dec a + ld c, a + ld b, 0 + ld hl, TrainerGroups + add hl, bc + add hl, bc ++ add hl, bc ++ ld a, [hli] ++ ld [wTrainerGroupBank], a + ld a, [hli] + ld h, [hl] + ld l, a + + ld a, [wOtherTrainerID] + ld b, a + .skip_trainer + dec b + jr z, .got_trainer + .loop +- ld a, [hli] ++ call GetNextTrainerDataByte + cp -1 + jr nz, .loop + jr .skip_trainer + .got_trainer + + .skip_name +- ld a, [hli] ++ call GetNextTrainerDataByte + cp "@" + jr nz, .skip_name + +- ld a, [hli] ++ call GetNextTrainerDataByte + ld [wOtherTrainerType], a + ld d, h + ld e, l + call ReadTrainerPartyPieces + .done + jp ComputeTrainerReward + + .cal2 + ld a, BANK(sMysteryGiftTrainer) + call OpenSRAM + ld a, TRAINERTYPE_MOVES + ld [wOtherTrainerType], a + ld de, sMysteryGiftTrainer + call ReadTrainerPartyPieces + call CloseSRAM + jr .done + + ReadTrainerPartyPieces: + ld h, d + ld l, e + + .loop + ; end? +- ld a, [hli] ++ call GetNextTrainerDataByte + cp -1 + ret z + + ; level + ld [wCurPartyLevel], a + + ; species +- ld a, [hli] ++ call GetNextTrainerDataByte + ld [wCurPartySpecies], a + + ; 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 + +- ld a, [hli] ++ call GetNextTrainerDataByte + cp "@" + jr z, .no_nickname + + push de + + ld de, wStringBuffer2 + ld [de], a + inc de + .copy_nickname +- ld a, [hli] ++ call GetNextTrainerDataByte + 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 + + ; 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] ++ call GetNextTrainerDataByte + cp PERFECT_DV + jr nz, .atk_def_dv_nonzero + ld a, $ff + .atk_def_dv_nonzero + ld [de], a + inc de +- ld a, [hli] ++ call GetNextTrainerDataByte + cp PERFECT_DV + jr nz, .spd_spc_dv_nonzero + ld a, $ff + .spd_spc_dv_nonzero + ld [de], a + .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] ++ call GetNextTrainerDataByte ++ dec hl + cp LOW(PERFECT_STAT_EXP) + jr nz, .not_perfect_stat_exp + inc hl +- ld a, [hl] ++ call GetNextTrainerDataByte ++ dec 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] ++ call GetNextTrainerDataByte + ld [de], a + inc de + endr + .continue_stat_exp + dec c + jr nz, .stat_exp_loop + .no_stat_exp + + ; 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] ++ call GetNextTrainerDataByte + 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] ++ call GetNextTrainerDataByte + ld [de], a + inc de + dec b + jr nz, .copy_moves + + ... + + jp .loop + + ... + + Battle_GetTrainerName:: + ld a, [wInBattleTowerBattle] + bit 0, a + ld hl, wOTPlayerName ++ ld a, BANK(Battle_GetTrainerName) ++ ld [wTrainerGroupBank], a + jp nz, CopyTrainerName + + ld a, [wOtherTrainerID] + ld b, a + ld a, [wOtherTrainerClass] + ld c, a + + GetTrainerName:: + ... + + .not_cal2 + dec c + push bc + ld b, 0 + ld hl, TrainerGroups + add hl, bc + add hl, bc ++ add hl, bc ++ ld a, [hli] ++ ld [wTrainerGroupBank], a + ld a, [hli] + ld h, [hl] + ld l, a + pop bc + + .loop + dec b + jr z, CopyTrainerName + + .skip +- ld a, [hli] ++ call GetNextTrainerDataByte + cp -1 + jr nz, .skip + jr .loop + + CopyTrainerName: + ld de, wStringBuffer1 + push de + ld bc, NAME_LENGTH +- call CopyBytes ++ ld a, [wTrainerGroupBank] ++ call FarCopyBytes + pop de + ret + + ... + +-INCLUDE "data/trainers/parties.asm" ++INCLUDE "data/trainers/party_pointers.asm" +``` + +That's a long series of edits, but they're all basically the same: instead of getting data directly from `[hl]` in the current ROM bank (i.e. the bank that this code is in), we call `GetNextTrainerDataByte` to switch banks while reading party data. We also have to account for the new bank byte in each `TrainerGroups` table entry. + +Edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) again: + +```diff + RandomPhoneMon: + ; Get a random monster owned by the trainer who's calling. + farcall GetCallerLocation + ld hl, TrainerGroups + ld a, d + dec a + ld c, a + ld b, 0 + add hl, bc + add hl, bc ++ add hl, bc ++ ld a, BANK(TrainerGroups) ++ call GetFarByte ++ ld [wTrainerGroupBank], a ++ inc hl + ld a, BANK(TrainerGroups) + call GetFarWord + + .skip_trainer + dec e + jr z, .skipped + .skip +- ld a, BANK(Trainers) ++ ld a, [wTrainerGroupBank] + call GetFarByte + inc hl + cp -1 + jr nz, .skip + jr .skip_trainer + .skipped + + .skip_name +- ld a, BANK(Trainers) ++ ld a, [wTrainerGroupBank] + call GetFarByte + inc hl + cp "@" + jr nz, .skip_name + +- ld a, BANK(Trainers) ++ ld a, [wTrainerGroupBank] + call GetFarByte + inc hl + ... + + ld e, 0 + push hl + .count_mon + inc e + add hl, bc +- ld a, BANK(Trainers) ++ ld a, [wTrainerGroupBank] + call GetFarByte + cp -1 + jr nz, .count_mon + pop hl + + .rand + call Random + maskbits PARTY_LENGTH + cp e + jr nc, .rand + + inc a + .get_mon + dec a + jr z, .got_mon + add hl, bc + jr .get_mon + .got_mon + + inc hl ; species +- ld a, BANK(Trainers) ++ ld a, [wTrainerGroupBank] + call GetFarByte + ld [wNamedObjectIndex], a + call GetPokemonName + ld hl, wStringBuffer1 + ld de, wStringBuffer4 + ld bc, MON_NAME_LENGTH + jp CopyBytes +``` + +Again, we're repeating the same change in many places: replacing `BANK(Trainers)` with `[wTrainerGroupBank]`, and accounting for the new bank byte in each `TrainerGroups` table entry. + +Edit [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm): + +```diff +-INCLUDE "data/trainers/party_pointers.asm" +- +-Trainers: + ; Trainer data structure: +-; - db "NAME@", TRAINERTYPE_* constant ++; - db "NAME@", TRAINERTYPE_* constants |ed together + ; - 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 ++; * in all cases: db level, species ++; * with TRAINERTYPE_NICKNAME: db "NICKNAME@" ++; * with TRAINERTYPE_DVS: db atk|def dv, spd|spc dv ++; * with TRAINERTYPE_STAT_EXP: dw hp, atk, def, spd, spc ++; * with TRAINERTYPE_ITEM: db item ++; * with TRAINERTYPE_MOVES: db move 1, move 2, move 3, move 4 ++; (TRAINERTYPE_ITEM_MOVES is just TRAINERTYPE_ITEM | TRAINERTYPE_MOVES) + ; - db -1 ; end ++ ++SECTION "Enemy Trainer Parties 1", ROMX + + FalknerGroup: + ... +``` + +`BANK(Trainers)` is now meaningless and unused, so we can simply remove that label. + +All the parties are in the same section, "Enemy Trainer Parties 1", but of course you can now create more. Just don't split a *group* across multiple sections. + +(I took this opportunity to also update the documentation, given our previous changes to the party data structure.) + +Finally, edit [main.asm](../blob/master/main.asm): + +```diff + SECTION "Enemy Trainers", ROMX + + INCLUDE "engine/battle/ai/items.asm" + INCLUDE "engine/battle/ai/scoring.asm" + INCLUDE "engine/battle/read_trainer_attributes.asm" + INCLUDE "engine/battle/read_trainer_party.asm" ++ ++ ++INCLUDE "data/trainers/parties.asm" +``` + +Since [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) doesn't `INCLUDE` [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) any more, we have to do so here. And since [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) has its own `SECTION` headings, we don't need one before it in [main.asm](../blob/master/main.asm). + +Anyway, we're done now. Just like [step 1](#1-refactor-trainer-types-to-use-bit-flags), the game doesn't do anything new, but the code has become more extensible. + + +## 6. Add a trainer type flag for variable parties + +In this step we'll add a special flag to define variable parties for the same trainer, which will vary depending on the player's number of badges. Let's edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm): + +```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 ++ const TRAINERTYPE_VARIABLE_F ; 5 + + ; 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 ++TRAINERTYPE_VARIABLE EQU 1 << TRAINERTYPE_VARIABLE_F + + PERFECT_DV EQU $11 ; treated as $FF in enemy party data + PERFECT_STAT_EXP EQU $1337 ; treated as $FFFF in enemy party data +``` + +And edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm) again: + +```diff +ReadTrainerPartyPieces: + ld h, d + ld l, e + ++; Variable? ++ bit TRAINERNTYPE_VARIABLE_F, a ++ jr z, .not_variable ++ ; get badge count in c ++ push hl ++ ld hl, Badges ++ ld b, 2 ++ call CountSetBits ++ pop hl ++ ; Skip that many $fe delimiters ++.outerloop ++ ld a, c ++ and a ++ jr z, .continue ++.innerloop ++ call GetNextTrainerDataByte ++ cp $fe ++ jr nz, .innerloop ++ dec c ++ jr .outerloop ++ ++.continue ++ ; Get trainer type of variable stage ++ call GetNextTrainerDataByte ++ ld [OtherTrainerType], a ++ ; fallthrough ++.not_variable + .loop + ; end? + call GetNextTrainerDataByte + cp -1 + ret z ++ cp $fe ++ ret z + + ; level + ld [wCurPartyLevel], a + ... +``` + +As an example, let's show how it'd work with Falkner: + +``` + FalknerGroup: + ; FALKNER (1) + db "Falkner@", TRAINERTYPE_VARIABLE + + ; No badges + db TRAINERTYPE_MOVES + db 7, PIDGEY, TACKLE, MUD_SLAP, NO_MOVE, NO_MOVE + db 9, PIDGEOTTO, TACKLE, MUD_SLAP, GUST, NO_MOVE + db $fe ; delimiter + + ... + + ; Six badges + db TRAINERTYPE_ITEM_MOVES + db 33, PIDGEOTTO, NO_ITEM, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK + db 35, PIDGEOT, SHARP_BEAK, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK + db $fe ; delimiter + + ... + + ; 16 badges + db TRAINERTYPE_ITEM_MOVES + db 58, PIDGEOT, NO_ITEM, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK + db 60, PIDGEOT, SHARP_BEAK, MUD_SLAP, FLY, QUICK_ATTACK, WING_ATTACK + db $ff ; end +``` + +Now the enemy trainer's team will vary depending on how many badges you have. +A few things to note about this: + +- One trainer ID applies to all 17 parties, depending on your badge count. So you don't have to repeat the trainer's name 17 times. +- Each party can have its own actual type (normal, item, moves, etc.). +- Each stage ends in $fe (except the last one ends in $ff) so you cannot use it for individual DV values (unless you change the delimiter's value, of course). + +## 7. Add a trainer type flag for happiness + +Normally all enemy Pokémon have base happiness (70), including trainers, so in this step we'll be able to give trainer parties different happiness values for each Pokémon, so that one Pokémon can have max power Return and another max power Frustration. Let's edit [constants/trainer_data_constants.asm](../blob/master/constants/trainer_data_constants.asm) to add a new trainer type flag: + +```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 + const TRAINERTYPE_VARIABLE_F ; 5 + const TRAINERTYPE_HAPPINESS_F ; 6 + + ; 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 + TRAINERTYPE_VARIABLE EQU 1 << TRAINERTYPE_VARIABLE_F ++TRAINERTYPE_HAPPINESS EQU 1 << TRAINERTYPE_HAPPINESS_F + + PERFECT_DV EQU $11 ; treated as $FF in enemy party data + PERFECT_STAT_EXP EQU $1337 ; treated as $FFFF in enemy party data ++MAX_HAPPINESS EQU $42 ; treated as $FF in enemy party data +``` + +Now let's edit [engine/battle/read_trainer_party.asm](../blob/master/engine/battle/read_trainer_party.asm): + +```diff + ... + .no_stat_exp ++; happpiness? ++ ld a, [wOtherTrainerType] ++ bit TRAINERTYPE_HAPPINESS_F, a ++ jr z, .no_happiness ++ ++ push hl ++ ld a, [wOTPartyCount] ++ dec a ++ ld hl, wOTPartyMon1Happiness ++ call GetPartyLocation ++ ld d, h ++ ld e, l ++ pop hl ++ ++ call GetNextTrainerDataByte ++ cp MAX_HAPPINESS ++ jr nz, .happiness_ok ++ ld a, $ff ++.happiness_ok ++ ld [de], a ++ ++.no_happiness + ; item? + ... + +``` + +Also edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm): + +```diff + LoadEnemyMon: + ... + .Happiness: +; Set happiness ++ ld a, [wBattleMode] ++ dec a ++ ld a, BASE_HAPPINESS ++ jr z, .load_happiness ++ ++ ld a, [wCurPartyMon] ++ ld hl, wOTPartyMon1Happiness ++ call GetPartyLocation ++ ld a, [hl] ++.load_happiness + ld [wEnemyMonHappiness], a + ... +``` + +And then edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm): + +```diff +RandomPhoneMon: + ... + .no_stat_exp ++; TRAINERTYPE_HAPPINESS uses 1 more byte ++ bit TRAINERTYPE_HAPPINESS_F, b ++ jr z, .no_happiness ++ inc c ++.no_happiness + ; TRAINERTYPE_ITEM uses 1 more byte + ... +``` + +We're technically done here, but it's also good to edit [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) to remember in which order to place the attributes for the `TRAINERTYPE_* flags`: + +```diff + ; Trainer data structure: + ; - db "NAME@", TRAINERTYPE_* constants |ed together + ; - 1 to 6 Pokémon: + ; * in all cases: db level, species + ; * with TRAINERTYPE_NICKNAME: db "NICKNAME@" + ; * with TRAINERTYPE_DVS: db atk|def dv, spd|spc dv + ; * with TRAINERTYPE_STAT_EXP: dw hp, atk, def, spd, spc ++; * with TRAINERTYPE_HAPPINESS db happiness + ; * with TRAINERTYPE_ITEM: db item + ; * with TRAINERTYPE_MOVES: db move 1, move 2, move 3, move 4 + ; (TRAINERTYPE_ITEM_MOVES is just TRAINERTYPE_ITEM | TRAINERTYPE_MOVES) + ; - db -1 ; end + ... +``` + +Some important notes: +- We created `MAX_HAPPINESS` since we can't directly use $ff (end-of-party marker). It's highly doubtful you're gonna use $42, but you can change it to $00 and use `and a` instead of `cp MAX_HAPPINESS` to slightly optimize your code. +- If you're gonna give Return to a Pokémon, setting its happiness to 0 will give the move 0 power, dealing no damage. The same goes for Frustration with 255 happiness, so if you want to change this behavior you should edit the move effects for these moves. -- cgit v1.2.3