Split the Special Stat - pret/pokered GitHub Wiki
Introduction
This tutorial details how to replace the SPECIAL stat with special attack and special defense as later generations of Pokémon games do. This tutorial assumes you are modifying a vanilla (unmodified) copy of pokered, but the included explanations should be enough to help regardless of your starting point.
DISCLAIMER: The modifications below are extensive, and though they have been tested, unintended consequences may arise. If you discover any bugs, or any issues with the tutorial itself, feel free to fix them here.
Contents
- Updating Pokémon Data Structures
- Engine Changes
- Modifying Status Screens
- Adding and Updating Move Effects
- Adding a New Vitamin (Optional)
- Renaming X Special and Adding X Sp. Def (Optional)
- Updating NPC Text
Updating Pokémon Data Structures
Constants and Names
This tutorial follows the Gen 2 naming convention of SPCL.ATK and SPCL.DEF, and the ordering convention of keeping SPEED before the special stats. Doing so minimizes the changes we will need to make to stat calculation/display later.
constants/battle_constants.asm
Even if you do not intend to add a new vitamin, adding the new constant here is required. This increases the value of NUM_STATS and allows the battle engine to reference this stat later.
...
; VitaminStats indexes (see data/battle/stat_names.asm)
const_def 1
const STAT_HEALTH
const STAT_ATTACK
const STAT_DEFENSE
const STAT_SPEED
- const STAT_SPECIAL
+ const STAT_SPCLATK
+ const STAT_SPCLDEF
DEF NUM_STATS EQU const_value - 1
; StatModTextStrings indexes (see data/battle/stat_mod_names.asm)
const_def
const MOD_ATTACK
const MOD_DEFENSE
const MOD_SPEED
- const MOD_SPECIAL
+ const MOD_SPCLATK
+ const MOD_SPCLDEF
const MOD_ACCURACY
const MOD_EVASION
const_skip 2
DEF NUM_STAT_MODS EQU const_value
...
constants/pokemon_data_constants.asm
These changes parallel the later changes made to memory addresses.
; base data struct members (see data/pokemon/base_stats/*.asm)
rsreset
DEF BASE_DEX_NO rb
DEF BASE_STATS rb NUM_STATS
rsset BASE_STATS
DEF BASE_HP rb
DEF BASE_ATK rb
DEF BASE_DEF rb
DEF BASE_SPD rb
-DEF BASE_SPC rb
+DEF BASE_SAT rb
+DEF BASE_SDF rb
...
DEF MON_EXP rb 3
DEF MON_HP_EXP rw
DEF MON_ATK_EXP rw
DEF MON_DEF_EXP rw
DEF MON_SPD_EXP rw
-DEF MON_SPC_EXP rw
+DEF MON_SAT_EXP rw
+DEF MON_SDF_EXP rw
DEF MON_DVS rw
DEF MON_PP rb NUM_MOVES
DEF BOXMON_STRUCT_LENGTH EQU _RS
DEF MON_LEVEL rb
DEF MON_STATS rw NUM_STATS
rsset MON_STATS
DEF MON_MAXHP rw
DEF MON_ATK rw
DEF MON_DEF rw
DEF MON_SPD rw
-DEF MON_SPC rw
+DEF MON_SAT rw
+DEF MON_SDF rw
DEF PARTYMON_STRUCT_LENGTH EQU _RS
DEF PARTY_LENGTH EQU 6
-DEF MONS_PER_BOX EQU 20
+DEF MONS_PER_BOX EQU 18 ; Adding special defense means boxed mons will take up more space. Decreased this value from 20 so that the Stack can still have the same size (see ram/wram.asm).
DEF NUM_BOXES EQU 12
DEF HOF_MON EQU $10
...
data/battle/stat_mod_names.asm
; Stats that move effects can raise or lower
; The relevant move effect IDs correspond to the stats
StatModTextStrings:
list_start
li "ATTACK"
li "DEFENSE"
li "SPEED"
- li "SPECIAL"
- assert_list_length SPECIAL_DOWN_SIDE_EFFECT - ATTACK_DOWN_SIDE_EFFECT + 1
+ li "SPCL.ATK"
+ li "SPCL.DEF"
+ assert_list_length SPCLDEF_DOWN_SIDE_EFFECT - ATTACK_DOWN_SIDE_EFFECT + 1
li "ACCURACY"
...
SPCLDEF_DOWN_SIDE_EFFECT will be added in a later step. See Adding and Updating Move Effects.
data/battle/stat_names.asm
; Stats that vitamins can raise or lower
VitaminStats:
list_start
li "HEALTH"
li "ATTACK"
li "DEFENSE"
li "SPEED"
- li "SPECIAL"
+ li "SPCL.ATK"
+ li "SPCL.DEF"
assert_list_length NUM_STATS
Alternatively, if you don't intend to add another vitamin:
; Stats that vitamins can raise or lower
VitaminStats:
list_start
li "HEALTH"
li "ATTACK"
li "DEFENSE"
li "SPEED"
- li "SPECIAL"
- assert_list_length NUM_STATS
+ li "SPCL.ATK"
+ assert_list_length NUM_STATS - 1 ; Zinc not added until Gen 3
RAM Addresses
The difference introduced between box_struct and battle_struct will be accounted for later:
macros/ram.asm
; Used in wram.asm
MACRO flag_array
ds ((\1) + 7) / 8
ENDM
-DEF BOX_STRUCT_LENGTH EQU 25 + NUM_MOVES * 2
+DEF BOX_STRUCT_LENGTH EQU 27 + NUM_MOVES * 2
+; The distance from HPExp to DVs used to be 11 bytes, but is now 13
MACRO box_struct
\1Species:: db
\1HP:: dw
\1BoxLevel:: db
\1Status:: db
\1Type::
\1Type1:: db
\1Type2:: db
\1CatchRate:: db
\1Moves:: ds NUM_MOVES
\1OTID:: dw
\1Exp:: ds 3
\1HPExp:: dw
\1AttackExp:: dw
\1DefenseExp:: dw
\1SpeedExp:: dw
-\1SpecialExp:: dw
+\1SpclAtkExp:: dw
+\1SpclDefExp:: dw
\1DVs:: dw
\1PP:: ds NUM_MOVES
ENDM
MACRO party_struct
box_struct \1
\1Level:: db
\1Stats::
\1MaxHP:: dw
\1Attack:: dw
\1Defense:: dw
\1Speed:: dw
-\1Special:: dw
+\1SpclAtk:: dw
+\1SpclDef:: dw
ENDM
+; The distance from HP to DVs is still 11 bytes
MACRO battle_struct
\1Species:: db
\1HP:: dw
\1PartyPos::
\1BoxLevel:: db
\1Status:: db
\1Type::
\1Type1:: db
\1Type2:: db
\1CatchRate:: db
\1Moves:: ds NUM_MOVES
\1DVs:: dw
\1Level:: db
\1Stats::
\1MaxHP:: dw
\1Attack:: dw
\1Defense:: dw
\1Speed:: dw
-\1Special:: dw
+\1SpclAtk:: dw
+\1SpclDef:: dw
\1PP:: ds NUM_MOVES
ENDM
...
ram/wram.asm
...
-; This union spans 39 bytes.
+; This union spans 45 bytes.
UNION
wInGameTradeGiveMonSpecies:: db
wInGameTradeTextPointerTablePointer:: dw
wInGameTradeTextPointerTableIndex:: db
wInGameTradeGiveMonName:: ds NAME_LENGTH
wInGameTradeReceiveMonName:: ds NAME_LENGTH
wInGameTradeMonNick:: ds NAME_LENGTH
wInGameTradeReceiveMonSpecies:: db
NEXTU
wPlayerMonUnmodifiedLevel:: db
wPlayerMonUnmodifiedMaxHP:: dw
wPlayerMonUnmodifiedAttack:: dw
wPlayerMonUnmodifiedDefense:: dw
wPlayerMonUnmodifiedSpeed:: dw
-wPlayerMonUnmodifiedSpecial:: dw
+wPlayerMonUnmodifiedSpclAtk:: dw
+wPlayerMonUnmodifiedSpclDef:: dw
; stat modifiers for the player's current pokemon
; value can range from 1 - 13 ($1 to $D)
; 7 is normal
wPlayerMonStatMods::
wPlayerMonAttackMod:: db
wPlayerMonDefenseMod:: db
wPlayerMonSpeedMod:: db
-wPlayerMonSpecialMod:: db
+wPlayerMonSpclAtkMod:: db
+wPlayerMonSpclDefMod:: db
wPlayerMonAccuracyMod:: db
wPlayerMonEvasionMod:: db
ds 2
wPlayerMonStatModsEnd::
ds 1
wEnemyMonUnmodifiedLevel:: db
wEnemyMonUnmodifiedMaxHP:: dw
wEnemyMonUnmodifiedAttack:: dw
wEnemyMonUnmodifiedDefense:: dw
wEnemyMonUnmodifiedSpeed:: dw
-wEnemyMonUnmodifiedSpecial:: dw
+wEnemyMonUnmodifiedSpclAtk:: dw
+wEnemyMonUnmodifiedSpclDef:: dw
; stat modifiers for the enemy's current pokemon
; value can range from 1 - 13 ($1 to $D)
; 7 is normal
wEnemyMonStatMods::
wEnemyMonAttackMod:: db
wEnemyMonDefenseMod:: db
wEnemyMonSpeedMod:: db
-wEnemyMonSpecialMod:: db
+wEnemyMonSpclAtkMod:: db
+wEnemyMonSpclDefMod:: db
wEnemyMonAccuracyMod:: db
wEnemyMonEvasionMod:: db
...
ENDU
...
wMonHeader::
; In the ROM base stats data structure, this is the dex number, but it is
; overwritten with the internal index number after the header is copied to WRAM.
wMonHIndex:: db
wMonHBaseStats::
wMonHBaseHP:: db
wMonHBaseAttack:: db
wMonHBaseDefense:: db
wMonHBaseSpeed:: db
-wMonHBaseSpecial:: db
+wMonHBaseSpclAtk:: db
+wMonHBaseSpclDef:: db
...
Pokémon Data
The base stats for every single Pokémon will need to be updated with a value for special defense. This will take a while, so go grab a drink or a snack and get comfortable while you work. Here is an example:
data/pokemon/base_stats/abra.asm
db DEX_ABRA ; pokedex id
- db 25, 20, 15, 90, 105
- ; hp atk def spd spc
+ db 25, 20, 15, 90, 105, 55
+ ; hp atk def spd sat sdf
...
List of stats
Below is a list of the stats for the original 151 Pokémon as they were in the Gen 2 games, arranged alphabetically to make it easier to follow as you modify each file in the data/pokemon/base_stats
folder. The comment for each line also lists instructions for how to update base stats to their modern (as of Gen 9) values. If you intend to use modern stats, now is a good time to make that change.
db: 25, 20, 15, 90, 105, 55 ; ABRA
db: 80, 105, 65, 130, 60, 75 ; AERODACTYL
db: 55, 50, 45, 120, 135, 85 ; ALAKAZAM (+10 sdf for modern stats)
db: 60, 85, 69, 80, 65, 79 ; ARBOK
db: 90, 110, 80, 95, 100, 80 ; ARCANINE
db: 90, 85, 100, 85, 95, 125 ; ARTICUNO
db: 65, 80, 40, 75, 45, 80 ; BEEDRILL (+10 atk for modern stats)
db: 50, 75, 35, 40, 70, 30 ; BELLSPROUT
db: 79, 83, 100, 78, 85, 105 ; BLASTOISE
db: 45, 49, 49, 45, 65, 65 ; BULBASAUR
db: 60, 45, 50, 70, 80, 80 ; BUTTERFREE (+10 sat for modern stats)
db: 45, 30, 35, 45, 20, 20 ; CATERPIE
db: 250, 5, 5, 50, 35, 105 ; CHANSEY
db: 78, 84, 78, 100, 109, 85 ; CHARIZARD
db: 39, 52, 43, 65, 60, 50 ; CHARMANDER
db: 58, 64, 58, 80, 80, 65 ; CHARMELEON
db: 95, 70, 73, 60, 85, 90 ; CLEFABLE (+10 sat for modern stats)
db: 70, 45, 48, 35, 60, 65 ; CLEFAIRY
db: 50, 95, 180, 70, 85, 45 ; CLOYSTER
db: 50, 50, 95, 35, 40, 50 ; CUBONE
db: 90, 70, 80, 70, 70, 95 ; DEWGONG
db: 10, 55, 25, 95, 35, 45 ; DIGLETT
db: 48, 48, 48, 48, 48, 48 ; DITTO
db: 60, 110, 70, 100, 60, 60 ; DODRIO (+10 spd for modern stats)
db: 35, 85, 45, 75, 35, 35 ; DODUO
db: 61, 84, 65, 70, 70, 70 ; DRAGONAIR
db: 91, 134, 95, 80, 100, 100 ; DRAGONITE
db: 41, 64, 45, 50, 50, 50 ; DRATINI
db: 60, 48, 45, 42, 43, 90 ; DROWZEE
db: 35, 80, 50, 120, 50, 70 ; DUGTRIO (+20 atk for modern stats)
db: 55, 55, 50, 55, 45, 65 ; EEVEE
db: 35, 60, 44, 55, 40, 54 ; EKANS
db: 65, 83, 57, 105, 95, 85 ; ELECTABUZZ
db: 60, 50, 70, 140, 80, 80 ; ELECTRODE (+10 spd for modern stats)
db: 60, 40, 80, 40, 60, 45 ; EXEGGCUTE
db: 95, 95, 85, 55, 125, 65 ; EXEGGUTOR (+10 sdf for modern stats)
db: 52, 65, 55, 60, 58, 62 ; FARFETCH'D (+25 atk for modern stats)
db: 65, 90, 65, 100, 61, 61 ; FEAROW
db: 65, 130, 60, 65, 95, 110 ; FLAREON
db: 30, 35, 30, 80, 100, 35 ; GASTLY
db: 60, 65, 60, 110, 130, 75 ; GENGAR
db: 40, 80, 100, 20, 30, 30 ; GEODUDE
db: 60, 65, 70, 40, 85, 75 ; GLOOM
db: 75, 80, 70, 90, 65, 75 ; GOLBAT
db: 45, 67, 60, 63, 35, 50 ; GOLDEEN
db: 80, 82, 78, 85, 95, 80 ; GOLDUCK
db: 80, 110, 130, 45, 55, 65 ; GOLEM (+10 atk for modern stats)
db: 55, 95, 115, 35, 45, 45 ; GRAVELER
db: 80, 80, 50, 25, 40, 50 ; GRIMER
db: 55, 70, 45, 60, 70, 50 ; GROWLITHE
db: 95, 125, 79, 81, 60, 100 ; GYARADOS
db: 45, 50, 45, 95, 115, 55 ; HAUNTER
db: 50, 105, 79, 76, 35, 110 ; HITMONCHAN
db: 50, 120, 53, 87, 35, 110 ; HITMONLEE
db: 30, 40, 70, 60, 70, 25 ; HORSEA
db: 85, 73, 70, 67, 73, 115 ; HYPNO
db: 60, 62, 63, 60, 80, 80 ; IVYSAUR
db: 115, 45, 20, 20, 45, 25 ; JIGGLYPUFF
db: 65, 65, 60, 130, 110, 95 ; JOLTEON
db: 65, 50, 35, 95, 115, 95 ; JYNX
db: 30, 80, 90, 55, 55, 45 ; KABUTO
db: 60, 115, 105, 80, 65, 70 ; KABUTOPS
db: 40, 35, 30, 105, 120, 70 ; KADABRA
db: 45, 25, 50, 35, 25, 25 ; KAKUNA
db: 105, 95, 80, 90, 40, 80 ; KANGASKHAN
db: 55, 130, 115, 75, 50, 50 ; KINGLER
db: 40, 65, 95, 35, 60, 45 ; KOFFING
db: 30, 105, 90, 50, 25, 25 ; KRABBY
db: 130, 85, 80, 60, 85, 95 ; LAPRAS
db: 90, 55, 75, 30, 60, 75 ; LICKITUNG
db: 90, 130, 80, 55, 65, 85 ; MACHAMP
db: 80, 100, 70, 45, 50, 60 ; MACHOKE
db: 70, 80, 50, 35, 35, 35 ; MACHOP
db: 20, 10, 55, 80, 15, 20 ; MAGIKARP
db: 65, 95, 57, 93, 100, 85 ; MAGMAR
db: 25, 35, 70, 45, 95, 55 ; MAGNEMITE
db: 50, 60, 95, 70, 120, 70 ; MAGNETON
db: 40, 80, 35, 70, 35, 45 ; MANKEY
db: 60, 80, 110, 45, 50, 80 ; MAROWAK
db: 40, 45, 35, 90, 40, 40 ; MEOWTH
db: 50, 20, 55, 30, 25, 25 ; METAPOD
db: 100, 100, 100, 100, 100, 100 ; MEW
db: 106, 110, 90, 130, 154, 90 ; MEWTWO
db: 90, 100, 90, 90, 125, 85 ; MOLTRES
db: 40, 45, 65, 90, 100, 120 ; MR. MIME
db: 105, 105, 75, 50, 65, 100 ; MUK
db: 81, 92, 77, 85, 85, 75 ; NIDOKING (+10 atk for modern stats)
db: 90, 82, 87, 76, 75, 85 ; NIDOQUEEN (+10 atk for modern stats)
db: 55, 47, 52, 41, 40, 40 ; NIDORAN_F
db: 46, 57, 40, 50, 40, 40 ; NIDORAN_M
db: 70, 62, 67, 56, 55, 55 ; NIDORINA
db: 61, 72, 57, 65, 55, 55 ; NIDORINO
db: 73, 76, 75, 100, 81, 100 ; NINETALES
db: 45, 50, 55, 30, 75, 65 ; ODDISH
db: 35, 40, 100, 35, 90, 55 ; OMANYTE
db: 70, 60, 125, 55, 115, 70 ; OMASTAR
db: 35, 45, 160, 70, 30, 45 ; ONIX
db: 35, 70, 55, 25, 45, 55 ; PARAS
db: 60, 95, 80, 30, 60, 80 ; PARASECT
db: 65, 70, 60, 115, 65, 65 ; PERSIAN
db: 83, 80, 75, 91, 70, 70 ; PIDGEOT (+10 spd for modern stats)
db: 63, 60, 55, 71, 50, 50 ; PIDGEOTTO
db: 40, 45, 40, 56, 35, 35 ; PIDGEY
db: 35, 55, 30, 90, 50, 40 ; PIKACHU (+10 def and sdf for modern stats)
db: 65, 125, 100, 85, 55, 70 ; PINSIR
db: 40, 50, 40, 90, 40, 40 ; POLIWAG
db: 65, 65, 65, 90, 50, 50 ; POLIWHIRL
db: 90, 85, 95, 70, 70, 90 ; POLIWRATH (+10 atk for modern stats)
db: 50, 85, 55, 90, 65, 65 ; PONYTA
db: 65, 60, 70, 40, 85, 75 ; PORYGON
db: 65, 105, 60, 95, 60, 70 ; PRIMEAPE
db: 50, 52, 48, 55, 65, 50 ; PSYDUCK
db: 60, 90, 55, 100, 90, 80 ; RAICHU (+10 spd for modern stats)
db: 65, 100, 70, 105, 80, 80 ; RAPIDASH
db: 55, 81, 60, 97, 50, 70 ; RATICATE
db: 30, 56, 35, 72, 25, 35 ; RATTATA
db: 105, 130, 120, 40, 45, 45 ; RHYDON
db: 80, 85, 95, 25, 30, 30 ; RHYHORN
db: 50, 75, 85, 40, 20, 30 ; SANDSHREW
db: 75, 100, 110, 65, 45, 55 ; SANDSLASH
db: 70, 110, 80, 105, 55, 80 ; SCYTHER
db: 55, 65, 95, 85, 95, 45 ; SEADRA
db: 80, 92, 65, 68, 65, 80 ; SEAKING
db: 65, 45, 55, 45, 45, 70 ; SEEL
db: 30, 65, 100, 40, 45, 25 ; SHELLDER
db: 95, 75, 110, 30, 100, 80 ; SLOWBRO
db: 90, 65, 65, 15, 40, 40 ; SLOWPOKE
db: 160, 110, 65, 30, 65, 110 ; SNORLAX
db: 40, 60, 30, 70, 31, 31 ; SPEAROW
db: 44, 48, 65, 43, 50, 64 ; SQUIRTLE
db: 60, 75, 85, 115, 100, 85 ; STARMIE
db: 30, 45, 55, 85, 70, 55 ; STARYU
db: 65, 55, 115, 60, 100, 40 ; TANGELA
db: 75, 100, 95, 110, 40, 70 ; TAUROS
db: 40, 40, 35, 70, 50, 100 ; TENTACOOL
db: 80, 70, 65, 100, 80, 120 ; TENTACRUEL
db: 130, 65, 60, 65, 110, 95 ; VAPOREON
db: 70, 65, 60, 90, 90, 75 ; VENOMOTH
db: 60, 55, 50, 45, 40, 55 ; VENONAT
db: 80, 82, 83, 80, 100, 100 ; VENUSAUR
db: 80, 105, 65, 70, 100, 60 ; VICTREEBEL (+10 sdf for modern stats)
db: 75, 80, 85, 50, 100, 90 ; VILEPLUME (+10 sat for modern stats)
db: 40, 30, 50, 100, 55, 55 ; VOLTORB
db: 38, 41, 40, 65, 50, 65 ; VULPIX
db: 59, 63, 80, 58, 65, 80 ; WARTORTLE
db: 40, 35, 30, 50, 20, 20 ; WEEDLE
db: 65, 90, 50, 55, 85, 45 ; WEEPINBELL
db: 65, 90, 120, 60, 85, 70 ; WEEZING
db: 140, 70, 45, 45, 75, 50 ; WIGGLYTUFF (+10 sat for modern stats)
db: 90, 90, 85, 100, 125, 90 ; ZAPDOS
db: 40, 45, 35, 55, 30, 40 ; ZUBAT
Engine Changes
Adding Special Defense Calculation
Stat calculation requires miniminal changes since NUM_STATS has been increased and the values for special defense immediately follow those of special attack in memory. Besides updating comments, the only necessary change is specifying the value to use for special attack DV. This tutorial follows the Gen 2 strategy of reusing the special attack DV. Adding a new DV for special defense (and for HP) is beyond the scope of this tutorial.
home/move_mon.asm
...
-; calculates all 5 stats of current mon and writes them to [de]
+; calculates all 6 stats of current mon and writes them to [de]
CalcStats::
ld c, $0
.statsLoop
inc c
call CalcStat
ldh a, [hMultiplicand+1]
ld [de], a
inc de
ldh a, [hMultiplicand+2]
ld [de], a
inc de
ld a, c
cp NUM_STATS
jr nz, .statsLoop
ret
; calculates stat c of current mon
-; c: stat to calc (HP=1,Atk=2,Def=3,Spd=4,Spc=5)
+; c: stat to calc (HP=1,Atk=2,Def=3,Spd=4,Sat=5,Sdf=6)
...
cp $5
jr z, .getSpecialIV
+ cp $6
+ jr z, .getSpecialIV ; Like Gen 2, special attack and special defense share an "IV" (DV)
.getHpIV
...
Updating Damage Calculation
engine/battle/core.asm
GetDamageVarsForPlayerAttack:
...
.specialAttack
- ld hl, wEnemyMonSpecial
+ ld hl, wEnemyMonSpclDef
ld a, [hli]
ld b, a
- ld c, [hl] ; bc = enemy special
+ ld c, [hl] ; bc = enemy special defense
ld a, [wEnemyBattleStatus3]
bit HAS_LIGHT_SCREEN_UP, a ; check for Light Screen
jr z, .specialAttackCritCheck
-; if the enemy has used Light Screen, double the enemy's special
+; if the enemy has used Light Screen, double the enemy's special defense
sla c
rl b
; reflect and light screen boosts do not cap the stat at MAX_STAT_VALUE, so weird things will happen during stats scaling
-; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen
+; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special Defense has used Light Screen
.specialAttackCritCheck
- ld hl, wBattleMonSpecial
+ ld hl, wBattleMonSpclAtk
ld a, [wCriticalHitOrOHKO]
and a ; check for critical hit
jr z, .scaleStats
-; in the case of a critical hit, reset the player's and enemy's specials to their base values
- ld c, STAT_SPECIAL
+; in the case of a critical hit, reset the player's special attack and enemy's special defense to their base values
+ ld c, STAT_SPCLDEF
call GetEnemyMonStat
ldh a, [hProduct + 2]
ld b, a
ldh a, [hProduct + 3]
ld c, a
push bc
- ld hl, wPartyMon1Special
+ ld hl, wPartyMon1SpclAtk
...
GetDamageVarsForEnemyAttack:
...
.specialAttack
- ld hl, wBattleMonSpecial
+ ld hl, wBattleMonSpclDef
ld a, [hli]
ld b, a
ld c, [hl]
ld a, [wPlayerBattleStatus3]
bit HAS_LIGHT_SCREEN_UP, a ; check for Light Screen
jr z, .specialAttackCritCheck
-; if the player has used Light Screen, double the player's special
+; if the player has used Light Screen, double the player's special defense
sla c
rl b
; reflect and light screen boosts do not cap the stat at MAX_STAT_VALUE, so weird things will happen during stats scaling
-; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special has used Light Screen
+; if a Pokemon with 512 or more Defense has used Reflect, or if a Pokemon with 512 or more Special Defense has used Light Screen
.specialAttackCritCheck
- ld hl, wEnemyMonSpecial
+ ld hl, wEnemyMonSpclAtk
ld a, [wCriticalHitOrOHKO]
and a ; check for critical hit
jr z, .scaleStats
-; in the case of a critical hit, reset the player's and enemy's specials to their base values
- ld hl, wPartyMon1Special
+; in the case of a critical hit, reset the player's special defense and enemy's special attack to their base values
+ ld hl, wPartyMon1SpclDef
ld a, [wPlayerMonNumber]
ld bc, wPartyMon2 - wPartyMon1
call AddNTimes
ld a, [hli]
ld b, a
ld c, [hl]
push bc
- ld c, STAT_SPECIAL
+ ld c, STAT_SPCLATK
...
Adjusting for Memory Changes
A few lines contain hard-coded values (and comments) that assume RAM addresses never change. Failure to update these values accordingly will mangle the stats calculated for enemy Pokémon, and for gift Pokémon sent to boxes instead of the party.
engine/battle/core.asm
; get stat c of enemy mon
; c: stat to get (STAT_* constant)
GetEnemyMonStat:
...
.notLinkBattle
...
- ld hl, wLoadedMonSpeedExp - $b ; this base address makes CalcStat look in [wLoadedMonSpeedExp] for DVs
+ ld hl, wLoadedMonSpeedExp - $d ; this base address makes CalcStat look in [wLoadedMonSpeedExp] for DVs
call CalcStat
...
LoadEnemyMonData:
...
.storeDVs
ld hl, wEnemyMonDVs
ld [hli], a
ld [hl], b
ld de, wEnemyMonLevel
ld a, [wCurEnemyLevel]
ld [de], a
inc de
ld b, $0
- ld hl, wEnemyMonHP
- push hl
+ ld hl, wEnemyMonHP - 2 ; CalcStats will now add 13 bytes to this value instead of 11 to find DVs, so we adjust by subtracting 2 ...
call CalcStats
- pop hl
+ ld hl, wEnemyMonHP ; ... and now we restore the expected value
ld a, [wIsInBattle]
...
-; calculate modified stat for stat c (0 = attack, 1 = defense, 2 = speed, 3 = special)
+; calculate modified stat for stat c (0 = attack, 1 = defense, 2 = speed, 3 = spcl.atk, 4 = spcl.def)
CalculateModifiedStat:
...
engine/pokemon/add_mon.asm
.writeEVsLoop ; set all EVs to 0
...
ld hl, wEnemyMonMaxHP
- ld bc, $a
+ ld bc, NUM_STATS * 2
call CopyData ; copy stats of cur enemy mon
...
.findMonDataDest
ld a, [wMoveMonType]
dec a
ld hl, wPartyMons
- ld bc, wPartyMon2 - wPartyMon1 ; $2c
+ ld bc, wPartyMon2 - wPartyMon1 ; $31
ld a, [wPartyCount]
jr nz, .addMonOffset
; if it's PARTY_TO_BOX
ld hl, wBoxMons
- ld bc, wBoxMon2 - wBoxMon1 ; $21
+ ld bc, wBoxMon2 - wBoxMon1 ; $23
ld a, [wBoxCount]
.addMonOffset
dec a
call AddNTimes
.findMonDataSrc
push hl
ld e, l
ld d, h
ld a, [wMoveMonType]
and a
ld hl, wBoxMons
- ld bc, wBoxMon2 - wBoxMon1 ; $21
+ ld bc, wBoxMon2 - wBoxMon1 ; $23
jr z, .addMonOffset2
cp DAYCARE_TO_PARTY
ld hl, wDayCareMon
jr z, .copyMonData
ld hl, wPartyMons
- ld bc, wPartyMon2 - wPartyMon1 ; $2c
+ ld bc, wPartyMon2 - wPartyMon1 ; $31
...
- ld bc, -18
+ ld bc, -20 ; Moving from end of box_struct, this positions hl 2 bytes before HPExp
add hl, bc
ld b, $1
call CalcStats
.done
and a
ret
Adding Special Defense Badge Boost
This section is optional. Each stat except HP is associated with one of the badges. Acquiring that badge will apply a 12.5% increase (a "badge boost") in its related stat to every Pokémon the player uses, except during Link Battles. The game will function just fine without adding a badge boost for special defense, but it seems odd to leave one stat out.
engine/battle/core.asm
The easiest way to add a special defense badge boost is to append a check for the badge of your choice to the end of the loop in ApplyBadgeStatBoosts
. This tutorial assumes the Volcano Badge will continue to boost both special attack and defense as it did before splitting the SPECIAL stat.
...
ApplyBadgeStatBoosts:
ld a, [wLinkState]
cp LINK_STATE_BATTLING
ret z ; return if link battle
ld a, [wObtainedBadges]
ld b, a
ld hl, wBattleMonAttack
ld c, $4
; the boost is applied for badges whose bit position is even
; the order of boosts matches the order they are laid out in RAM
; Boulder (bit 0) - attack
; Thunder (bit 2) - defense
; Soul (bit 4) - speed
-; Volcano (bit 6) - special
+; Volcano (bit 6) - special attack
.loop
srl b
call c, .applyBoostToStat
inc hl
inc hl
srl b
dec c
jr nz, .loop
- ret
+; Special case added for special defense
+ ld a, [wObtainedBadges]
+ bit BIT_VOLCANOBADGE, a ; checks same badge as special attack, but can be any of them
+ ret z ; return immediately if that badge is not obtained
+; fall-through intended
; multiply stat at hl by 1.125
; cap stat at MAX_STAT_VALUE
.applyBoostToStat
...
Alternatively, the loop could be replaced with a check for each bit corresponding to a badge boost. That is beyond the scope of this tutorial.
Modifying Status Screens
There are two cases in which a player can view a Pokémon's special defense: in battle upon level-up, and in a status screen. Both cases use the same function. For the first case, the text box can be increased in height and the text adjusted to fit. For the second case, the text box can no longer fit on screen. This tutorial removes the border and prints the text only, resulting in just enough room.
engine/pokemon/status_screen.asm
...
PrintStatsBox:
ld a, d
and a ; a is 0 from the status screen
jr nz, .DifferentBox
- hlcoord 0, 8
- ld b, 8
- ld c, 8
- call TextBoxBorder ; Draws the box
- hlcoord 1, 8 ; Start printing stats from here
+; Don't draw a border; status screen needs every line it can get here
+ hlcoord 1, 8 ; Start printing stats from here
ld bc, $19 ; Number offset
jr .PrintStats
.DifferentBox
- hlcoord 9, 2
- ld b, 8
+ hlcoord 9, 0
+ ld b, 10
ld c, 9
call TextBoxBorder
- hlcoord 11, 3
+ hlcoord 11, 1
ld bc, $18
.PrintStats
...
- ld de, wLoadedMonSpecial
+ ld de, wLoadedMonSpclAtk
+ call PrintStat
+ ld de, wLoadedMonSpclDef
jp PrintNumber
...
StatsText:
db "ATTACK"
next "DEFENSE"
next "SPEED"
- next "SPECIAL@"
+ next "SPCL.ATK"
+ next "SPCL.DEF@"
...
Adding and Updating Move Effects
Certain moves interacted with the SPECIAL stat, so they must now be updated. Doing so will require adding new move effects for stat modifications to special defense, adjusting some hard-coded values, and updating lots of comments.
constants/move_effect_constants.asm
New constants for special defense should follow those for special attack in order to make implementing those effects easier. As a result, the numbering in the comments will no longer match reality unless they are updated as well. Although tedious, this is important to prevent confusion in the future. The only thing worse than uncommented code is incorrectly commented code. Not all these changes are detailed in the diff below, to keep things short.
...
- const SPECIAL_UP1_EFFECT ; $0D
+ const SPCLATK_UP1_EFFECT ; $0D
+ const SPCLDEF_UP1_EFFECT ; $0E
...
- const SPECIAL_DOWN1_EFFECT ; $15
+ const SPCLATK_DOWN1_EFFECT ; $16
+ const SPCLDEF_DOWN1_EFFECT ; $17
...
- const EFFECT_1E ; $1E unused
+ const EFFECT_20 ; $20 unused
...
- const SPECIAL_UP2_EFFECT ; $35
+ const SPCLATK_UP2_EFFECT ; $37
+ const SPCLDEF_UP2_EFFECT ; $38
...
- const SPECIAL_DOWN2_EFFECT ; $3D
+ const SPCLATK_DOWN2_EFFECT ; $40
+ const SPCLDEF_DOWN2_EFFECT ; $41
...
- const SPECIAL_DOWN_SIDE_EFFECT ; $47
+ const SPCLATK_DOWN_SIDE_EFFECT ; $4B
+ const SPCLDEF_DOWN_SIDE_EFFECT ; $4C
...
- const DISABLE_EFFECT ; $56
+ const DISABLE_EFFECT ; $5B
DEF NUM_MOVE_EFFECTS EQU const_value - 1
data/battle/always_happen_effects.asm
Updating the name of EFFECT_1E to EFFECT_20 is technically optional, but is recommended. It must also be changed here:
AlwaysHappenSideEffects:
; Attacks that aren't finished after they faint the opponent.
db DRAIN_HP_EFFECT
db EXPLODE_EFFECT
db DREAM_EATER_EFFECT
db PAY_DAY_EFFECT
db TWO_TO_FIVE_ATTACKS_EFFECT
- db EFFECT_1E
+ db EFFECT_20
...
data/battle/special_effects.asm
...and here as well:
SpecialEffects:
; Effects from arrays 2, 4, and 5B, minus Twineedle and Rage.
; Includes all effects that do not need to be called at the end of
; ExecutePlayerMove (or ExecuteEnemyMove), because they have already been handled
db DRAIN_HP_EFFECT
db EXPLODE_EFFECT
db DREAM_EATER_EFFECT
db PAY_DAY_EFFECT
db SWIFT_EFFECT
db TWO_TO_FIVE_ATTACKS_EFFECT
- db EFFECT_1E
+ db EFFECT_20
...
data/battle/residual_effects_2.asm
Back to replacing SPECIAL with SPCLATK and SPCLDEF in move effect names.
ResidualEffects2:
; non-side effects not included in ResidualEffects1
; stat-affecting moves, sleep-inflicting moves, and Bide
; e.g., Meditate, Bide, Hypnosis
db EFFECT_01
db ATTACK_UP1_EFFECT
db DEFENSE_UP1_EFFECT
db SPEED_UP1_EFFECT
- db SPECIAL_UP1_EFFECT
+ db SPCLATK_UP1_EFFECT
+ db SPCLDEF_UP1_EFFECT
db ACCURACY_UP1_EFFECT
db EVASION_UP1_EFFECT
db ATTACK_DOWN1_EFFECT
db DEFENSE_DOWN1_EFFECT
db SPEED_DOWN1_EFFECT
- db SPECIAL_DOWN1_EFFECT
+ db SPCLATK_DOWN1_EFFECT
+ db SPCLDEF_DOWN1_EFFECT
db ACCURACY_DOWN1_EFFECT
db EVASION_DOWN1_EFFECT
db BIDE_EFFECT
db SLEEP_EFFECT
db ATTACK_UP2_EFFECT
db DEFENSE_UP2_EFFECT
db SPEED_UP2_EFFECT
- db SPECIAL_UP2_EFFECT
+ db SPCLATK_UP2_EFFECT
+ db SPCLDEF_UP2_EFFECT
db ACCURACY_UP2_EFFECT
db EVASION_UP2_EFFECT
db ATTACK_DOWN2_EFFECT
db DEFENSE_DOWN2_EFFECT
db SPEED_DOWN2_EFFECT
- db SPECIAL_DOWN2_EFFECT
+ db SPCLATK_DOWN2_EFFECT
+ db SPCLDEF_DOWN2_EFFECT
db ACCURACY_DOWN2_EFFECT
db EVASION_DOWN2_EFFECT
db -1 ; end
data/moves/effects_pointers.asm
Associate the new move effects with the correct function.
...
- dw StatModifierUpEffect ; SPECIAL_UP1_EFFECT
+ dw StatModifierUpEffect ; SPCLATK_UP1_EFFECT
+ dw StatModifierUpEffect ; SPCLDEF_UP1_EFFECT
...
- dw StatModifierDownEffect ; SPECIAL_DOWN1_EFFECT
+ dw StatModifierDownEffect ; SPCLATK_DOWN1_EFFECT
+ dw StatModifierDownEffect ; SPCLDEF_DOWN1_EFFECT
...
- dw TwoToFiveAttacksEffect ; EFFECT_1E
+ dw TwoToFiveAttacksEffect ; EFFECT_20
...
- dw StatModifierUpEffect ; SPECIAL_UP2_EFFECT
+ dw StatModifierUpEffect ; SPCALTK_UP2_EFFECT
+ dw StatModifierUpEffect ; SPCLDEF_UP2_EFFECT
...
- dw StatModifierDownEffect ; SPECIAL_DOWN2_EFFECT
+ dw StatModifierDownEffect ; SPCLATK_DOWN2_EFFECT
+ dw StatModifierDownEffect ; SPCLDEF_DOWN2_EFFECT
...
- dw StatModifierDownEffect ; SPECIAL_DOWN_SIDE_EFFECT
+ dw StatModifierDownEffect ; SPCLATK_DOWN_SIDE_EFFECT
+ dw StatModifierDownEffect ; SPCLDEF_DOWN_SIDE_EFFECT
...
data/moves/moves.asm
Update the moves Growth, Psychic, and Amnesia with the appropriate effects. Growth is a special case: its effect changed multiple times after Gen 1. To replicate its original effect, it should raise both special attack and special defense. To update it to its modern effect (Gen 5 to Gen 9), it should raise both attack and special attack. Increasing two different stats would require adding a new effect, which is outside the scope of this tutorial, so instead the change here raises just special attack by one stage (matching its effect from Gen 2 to Gen 4).
...
- move GROWTH, SPECIAL_UP1_EFFECT, 0, NORMAL, 100, 40
+ move GROWTH, SPCLATK_UP1_EFFECT, 0, NORMAL, 100, 40
...
- move PSYCHIC_M, SPECIAL_DOWN_SIDE_EFFECT, 90, PSYCHIC_TYPE, 100, 10
+ move PSYCHIC_M, SPCLDEF_DOWN_SIDE_EFFECT, 90, PSYCHIC_TYPE, 100, 10
...
- move AMNESIA, SPECIAL_UP2_EFFECT, 0, PSYCHIC_TYPE, 100, 20
+ move AMNESIA, SPCLDEF_UP2_EFFECT, 0, PSYCHIC_TYPE, 100, 20
...
engine/battle/core.asm
Another place to update the name of EFFECT_1E if it was changed.
; Multi-hit attacks may or may not have 0 bp.
cp TWO_TO_FIVE_ATTACKS_EFFECT
jr z, .skipbp
- cp EFFECT_1E
+ cp EFFECT_20
jr z, .skipbp
engine/battle/effects.asm
There are two places where hard-coded values will prevent the SPCLDEF effects from working correctly, since they assume only four base stats can be modified.
StatModifierUpEffect:
...
.incrementStatMod
...
.ok
ld [hl], b
ld a, c
- cp $4
+ cp NUM_STATS
jr nc, UpdateStatDone ; jump if mod affected is evasion/accuracy
...
StatModifierDownEffect:
...
.statModifierDownEffect
...
- sub ATTACK_DOWN_SIDE_EFFECT ; map each stat to 0-3
+ sub ATTACK_DOWN_SIDE_EFFECT ; map each stat to 0-4
...
.ok
ld [hl], b ; save modified mod
ld a, c
- cp $4
+ cp NUM_STATS
jr nc, UpdateLoweredStatDone ; jump for evasion/accuracy
engine/battle/move_effects/transform.asm
Finally, the move effect for Transform also needs an update so that the user's special defense can be saved for later, and the enemy's special defense can be copied.
...
.next
; DVs
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
-; Attack, Defense, Speed, and Special stats
+; Attack, Defense, Speed, Spcl.Atk, and Spcl.Def stats
inc hl
inc hl
inc hl
inc de
inc de
inc de
- ld bc, $8
+ ld bc, $a
call CopyData
...
.gotStatsOrModsToCopy
- ld bc, $8
+ ld bc, $a
jp CopyData
...
engine/battle/trainer_ai.asm
AIUseXSpecial
is unused in vanilla (unmodified) pokered, but SPECIAL_UP1_EFFECT
must be replaced regardless.
...
AIUseXSpecial:
- ld b, SPECIAL_UP1_EFFECT
+ ld b, SPCLATK_UP1_EFFECT
ld a, X_SPECIAL
; fallthrough
AIIncreaseStat:
...
Alternatively, if you wish to update its name and add an X Sp. Def option (more on that later), make the following changes:
...
-AIUseXSpecial:
- ld b, SPECIAL_UP1_EFFECT
- ld a, X_SPECIAL
+AIUseXSpAtk:
+ ld b, SPCLATK_UP1_EFFECT
+ ld a, X_SP_ATK
+ jr AIIncreaseStat
+
+AIUseXSpDef:
+ ld b, SPCLDEF_UP1_EFFECT
+ ld a, X_SP_DEF
; fallthrough
AIIncreaseStat:
...
Adding a New Vitamin
This section is optional. Despite special defense being added in Gen 2, a vitamin to increase that stat (Zinc) was not added until Gen 3.
constants/item_constants.asm
Insert ZINC below CALCIUM. This allows that code that applies vitamins to Stat EXP to work without modification. Again, adjusting the values in the comments is a hassle, but will likely be important for reference later. Less re-numbering is needed if you remove unused ITEM_2C.
...
const HP_UP ; $23
const PROTEIN ; $24
const IRON ; $25
const CARBOS ; $26
const CALCIUM ; $27
- const RARE_CANDY ; $28
- const DOME_FOSSIL ; $29
- const HELIX_FOSSIL ; $2A
- const SECRET_KEY ; $2B
- const ITEM_2C ; $2C ; unused
+ const ZINC ; $28
+ const RARE_CANDY ; $29
+ const DOME_FOSSIL ; $2A
+ const HELIX_FOSSIL ; $2B
+ const SECRET_KEY ; $2C
const BIKE_VOUCHER ; $2D
...
data/items/key_items.asm
...
dbit FALSE ; HP_UP
dbit FALSE ; PROTEIN
dbit FALSE ; IRON
dbit FALSE ; CARBOS
dbit FALSE ; CALCIUM
+ dbit FALSE ; ZINC
dbit FALSE ; RARE_CANDY
dbit TRUE ; DOME_FOSSIL
dbit TRUE ; HELIX_FOSSIL
dbit TRUE ; SECRET_KEY
- dbit TRUE ; ITEM_2C
dbit TRUE ; BIKE_VOUCHER
...
data/items/marts.asm
Add ZINC to the Celadon Vitamin mart, the only place where the other vitamins are sold. Adding ZINC to item balls and hidden items elsewhere is recommended, but beyond the scope of this tutorial.
...
CeladonMart5FClerk2Text::
- script_mart HP_UP, PROTEIN, IRON, CARBOS, CALCIUM
+ script_mart HP_UP, PROTEIN, IRON, CARBOS, CALCIUM, ZINC
...
data/items/names.asm
...
li "HP UP"
li "PROTEIN"
li "IRON"
li "CARBOS"
li "CALCIUM"
+ li "ZINC"
li "RARE CANDY"
li "DOME FOSSIL"
li "HELIX FOSSIL"
li "SECRET KEY"
- li "?????" ; ITEM_2C
...
data/items/prices.asm
...
bcd3 9800 ; HP_UP
bcd3 9800 ; PROTEIN
bcd3 9800 ; IRON
bcd3 9800 ; CARBOS
bcd3 9800 ; CALCIUM
+ bcd3 9800 ; ZINC
bcd3 4800 ; RARE_CANDY
bcd3 0 ; DOME_FOSSIL
bcd3 0 ; HELIX_FOSSIL
bcd3 0 ; SECRET_KEY
- bcd3 0 ; ITEM_2C
bcd3 0 ; BIKE_VOUCHER
...
data/items/use_party.asm
...
db HP_UP
db PROTEIN
db IRON
db CARBOS
db CALCIUM
+ db ZINC
db RARE_CANDY
...
engine/items/item_effects.asm
...
dw ItemUseVitamin ; HP_UP
dw ItemUseVitamin ; PROTEIN
dw ItemUseVitamin ; IRON
dw ItemUseVitamin ; CARBOS
dw ItemUseVitamin ; CALCIUM
+ dw ItemUseVitamin ; ZINC
dw ItemUseVitamin ; RARE_CANDY
dw UnusableItem ; DOME_FOSSIL
dw UnusableItem ; HELIX_FOSSIL
dw UnusableItem ; SECRET_KEY
- dw UnusableItem ; ITEM_2C
dw UnusableItem ; BIKE_VOUCHER
...
Renaming X Special and Adding X Sp. Def
This section is optional. Despite special defense being added in Gen 2, a battle item to boost that stat (X Sp. Def) was not added until Gen 4. The item that boosts special attack kept the name X Special all the way until Gen 6.
constants/item_constants.asm
Insert X_SP_DEF below X_SPECIAL, which can be renamed X_SP_ATK if desired. Adjust the values in the comments for later reference.
const X_ATTACK ; $41
const X_DEFEND ; $42
const X_SPEED ; $43
- const X_SPECIAL ; $44
- const COIN_CASE ; $45
- const OAKS_PARCEL ; $46
- const ITEMFINDER ; $47
- const SILPH_SCOPE ; $48
- const POKE_FLUTE ; $49
- const LIFT_KEY ; $4A
- const EXP_ALL ; $4B
- const OLD_ROD ; $4C
- const GOOD_ROD ; $4D
- const SUPER_ROD ; $4E
- const PP_UP ; $4F
- const ETHER ; $50
- const MAX_ETHER ; $51
- const ELIXER ; $52
- const MAX_ELIXER ; $53
+ const X_SP_ATK ; $44
+ const X_SP_DEF ; $45
+ const COIN_CASE ; $46
+ const OAKS_PARCEL ; $47
+ const ITEMFINDER ; $48
+ const SILPH_SCOPE ; $49
+ const POKE_FLUTE ; $4A
+ const LIFT_KEY ; $4B
+ const EXP_ALL ; $4C
+ const OLD_ROD ; $4D
+ const GOOD_ROD ; $4E
+ const SUPER_ROD ; $4F
+ const PP_UP ; $50
+ const ETHER ; $51
+ const MAX_ETHER ; $52
+ const ELIXER ; $53
+ const MAX_ELIXER ; $54
DEF NUM_ITEMS EQU const_value - 1
; elevator floors use item IDs (see scripts/CeladonMartElevator.asm and scripts/SilphCoElevator.asm)
- const FLOOR_B2F ; $54
- const FLOOR_B1F ; $55
- const FLOOR_1F ; $56
- const FLOOR_2F ; $57
- const FLOOR_3F ; $58
- const FLOOR_4F ; $59
- const FLOOR_5F ; $5A
- const FLOOR_6F ; $5B
- const FLOOR_7F ; $5C
- const FLOOR_8F ; $5D
- const FLOOR_9F ; $5E
- const FLOOR_10F ; $5F
- const FLOOR_11F ; $60
- const FLOOR_B4F ; $61
+ const FLOOR_B2F ; $55
+ const FLOOR_B1F ; $56
+ const FLOOR_1F ; $57
+ const FLOOR_2F ; $58
+ const FLOOR_3F ; $59
+ const FLOOR_4F ; $5A
+ const FLOOR_5F ; $5B
+ const FLOOR_6F ; $5C
+ const FLOOR_7F ; $5D
+ const FLOOR_8F ; $5E
+ const FLOOR_9F ; $5F
+ const FLOOR_10F ; $60
+ const FLOOR_11F ; $61
+ const FLOOR_B4F ; $62
DEF NUM_FLOORS EQU const_value - 1 - NUM_ITEMS
...
constants/move_constants.asm
The only change here is to a comment:
...
- const XSTATITEM_ANIM ; use X Attack/Defense/Speed/Special
+ const XSTATITEM_ANIM ; use X Attack/Defense/Speed/Sp Atk/Sp Def
...
data/events/hidden_objects.asm
If renaming X_SPECIAL, also update this reference in the hidden item data.
...
UndergroundPathNsHiddenObjects:
hidden_object 3, 4, FULL_RESTORE, HiddenItems
- hidden_object 4, 34, X_SPECIAL, HiddenItems
+ hidden_object 4, 34, X_SP_ATK, HiddenItems
db -1 ; end
...
data/items/key_items.asm
...
dbit FALSE ; X_ATTACK
dbit FALSE ; X_DEFEND
dbit FALSE ; X_SPEED
- dbit FALSE ; X_SPECIAL
+ dbit FALSE ; X_SP_ATK
+ dbit FALSE ; X_SP_DEF
dbit TRUE ; COIN_CASE
...
data/items/marts.asm
Add X_SP_DEF to this Celadon mart, the only place where the other battle items are sold. Adding X_SP_DEF to item balls and hidden items elsewhere is recommended, but beyond the scope of this tutorial. Also rename X_SPECIAL.
...
CeladonMart5FClerk1Text::
- script_mart X_ACCURACY, GUARD_SPEC, DIRE_HIT, X_ATTACK, X_DEFEND, X_SPEED, X_SPECIAL
+ script_mart X_ACCURACY, GUARD_SPEC, DIRE_HIT, X_ATTACK, X_DEFEND, X_SPEED, X_SP_ATK, X_SP_DEF
...
data/items/names.asm
Using the Gen 6 names here. Alternatively, these can match the status screen names: X SPCL.ATK and X SPCL.DEF.
...
li "X ATTACK"
li "X DEFEND"
li "X SPEED"
- li "X SPECIAL"
+ li "X SP. ATK"
+ li "X SP. DEF"
li "COIN CASE"
...
data/items/prices.asm
...
bcd3 500 ; X_ATTACK
bcd3 550 ; X_DEFEND
bcd3 350 ; X_SPEED
- bcd3 350 ; X_SPECIAL
+ bcd3 350 ; X_SP_ATK
+ bcd3 350 ; X_SP_DEF
bcd3 0 ; COIN_CASE
...
data/items/use_party.asm
...
db X_ATTACK
db X_DEFEND
db X_SPEED
- db X_SPECIAL
+ db X_SP_ATK
+ db X_SP_DEF
db PP_UP
...
engine/items/item_effects.asm
...
dw ItemUseXStat ; X_ATTACK
dw ItemUseXStat ; X_DEFEND
dw ItemUseXStat ; X_SPEED
- dw ItemUseXStat ; X_SPECIAL
+ dw ItemUseXStat ; X_SP_ATK
+ dw ItemUseXStat ; X_SP_DEF
dw ItemUseCoinCase ; COIN_CASE
...
NOTE: Don't forget to make the necessary changes to engine/battle/trainer_ai.asm
already detailed above.
Updating NPC Text
Several NPCs and a book mention the SPECIAL stat or an associated item. They should be updated to reflect the changes made in previous sections.
text/CeladonMart5F.asm
If you added the ZINC item, make the following change:
_CeladonMart5FGentlemanText::
text "#MON ability"
line "enhancers can be"
cont "bought only here."
para "Use CALCIUM to"
- line "increase SPECIAL"
- cont "abilities."
+ line "increase SPCL.ATK"
+ cont "ability."
+
+ para "Use ZINC to"
+ line "increase SPCL.DEF"
+ cont "ability."
para "Use CARBOS to"
line "increase SPEED."
done
...
Otherwise,
_CeladonMart5FGentlemanText::
text "#MON ability"
line "enhancers can be"
cont "bought only here."
para "Use CALCIUM to"
- line "increase SPECIAL"
- cont "abilities."
+ line "increase SPCL.ATK"
+ cont "ability."
para "Use CARBOS to"
line "increase SPEED."
done
...
text/CeruleanBadgeHouse.asm
If you added the special defense badge boost and associated it with the Volcano Badge, either leave this dialogue unchanged or make the following change:
...
_CeruleanBadgeHouseVolcanoBadgeText::
text "Your #MON's"
- line "SPECIAL abilities"
+ line "SPCL.ATK and"
+ cont "SPCL.DEF"
cont "increase a bit."
prompt
...
text/CinnabarGym.asm
If you added the special defense badge boost and associated it with the Volcano Badge, either leave this dialogue unchanged or make the following change:
...
_CinnabarGymBlaineVolcanoBadgeInfoText::
text "Hah!"
para "The VOLCANOBADGE"
line "heightens the"
- cont "SPECIAL abilities"
+ cont "SPCL.ATK and"
+ cont "SPCL.DEF"
cont "of your #MON!"
...
Otherwise,
...
_CinnabarGymBlaineVolcanoBadgeInfoText::
text "Hah!"
para "The VOLCANOBADGE"
line "heightens the"
- cont "SPECIAL abilities"
+ cont "SPCL.ATK ability"
cont "of your #MON!"
...
text/LavenderMart.asm
Modify this text if renaming X_SPECIAL and/or adding X_SP_DEF:
_LavenderMartBaldingGuyText::
text "I'm searching for"
line "items that raise"
cont "the abilities of"
cont "#MON during a"
cont "single battle."
para "X ATTACK, X"
line "DEFEND, X SPEED"
- cont "and X SPECIAL are"
+ cont "X SP. ATK and X"
+ cont "SP. DEF are"
cont "what I'm after."
...
text/MrPsychicsHouse.asm
...
_MrPsychicsHouseMrPsychicTM29ExplanationText::
text "TM29 is PSYCHIC!"
para "It can lower the"
- line "target's SPECIAL"
- cont "abilities."
+ line "target's SPCL.DEF"
+ cont "ability."
done
...