summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRangi <remy.oukaour+rangi42@gmail.com>2018-07-20 21:59:57 -0400
committerRangi <remy.oukaour+rangi42@gmail.com>2018-07-20 21:59:57 -0400
commitd07673d3800ab939e75994beec4bce961d574b26 (patch)
treef5fc09a8a47a4d0f0bd5c9c95a65ff6b4b299da8
parentd54021e44a4e566582779fbd5010549c7ed006a2 (diff)
Allow trainer data to be stored in multiple banks
-rw-r--r--Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md605
1 files changed, 604 insertions, 1 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
index 676f105..cc4530b 100644
--- 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
@@ -902,7 +902,610 @@ Again, since −1 ($FF) is the end-of-party marker, you can't use stat experienc
## 5. Allow trainer data to be stored in multiple banks
-TODO
+Edit [wram.asm](../blob/master/wram.asm) again:
+
+```diff
+wOtherTrainerType:: db
++wTrainerGroupBank:: db
+
+- ds 2
++ ds 1
+```
+
+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
+```
+
+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 GetSRAMBank
+ 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
+
+ push de
+
+ ld de, wStringBuffer2
+ .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
+
+ 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
+
+ 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
+
+ ; Custom DVs affect stats, so recalculate them after TryAddMonToParty
+ ld a, [wOtherTrainerType]
+ and TRAINERTYPE_DVS | TRAINERTYPE_STAT_EXP
+ 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
+
+ ...
+
+ 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::
+ ld a, c
+ cp CAL
+ jr nz, .not_cal2
+
+ ld a, BANK(sMysteryGiftTrainerHouseFlag)
+ call GetSRAMBank
+ ld a, [sMysteryGiftTrainerHouseFlag]
+ and a
+ call CloseSRAM
+ jr z, .not_cal2
+
+ ld a, BANK(sMysteryGiftPartnerName)
+ call GetSRAMBank
+ ld hl, sMysteryGiftPartnerName
+ call CopyTrainerName
+ jp CloseSRAM
+
+ .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 GetFarHalfword
+
+ .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
+ ; 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
+ ; 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 10 more bytes
+ bit TRAINERTYPE_STAT_EXP_F, b
+ jr z, .no_stat_exp
+ ld a, 10
+ add c
+ ld c, a
+ .no_stat_exp
+ ; TRAINERTYPE_ITEM uses 1 more byte
+ bit TRAINERTYPE_ITEM_F, b
+ jr z, .no_item
+ inc c
+ .no_item
+ ; TRAINERTYPE_MOVES uses 4 more bytes
+ bit TRAINERTYPE_MOVES_F, b
+ jr z, .no_moves
+ ld a, 4
+ add c
+ ld c, a
+ .no_moves
+ ; bc = mon length
+ xor a
+ ld b, a
+
+ 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 [wNamedObjectIndexBuffer], 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_ITEM: db level, species, item
+-; * for TRAINERTYPE_MOVES: db level, species, 4 moves
+-; * 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