diff options
author | Rangi <remy.oukaour+rangi42@gmail.com> | 2018-07-20 21:59:57 -0400 |
---|---|---|
committer | Rangi <remy.oukaour+rangi42@gmail.com> | 2018-07-20 21:59:57 -0400 |
commit | d07673d3800ab939e75994beec4bce961d574b26 (patch) | |
tree | f5fc09a8a47a4d0f0bd5c9c95a65ff6b4b299da8 | |
parent | d54021e44a4e566582779fbd5010549c7ed006a2 (diff) |
Allow trainer data to be stored in multiple banks
-rw-r--r-- | Allow-more-trainer-parties,-with-individual-DVs,-stat-experience,-and-nicknames.md | 605 |
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 |