-
Notifications
You must be signed in to change notification settings - Fork 806
Replace stat experience with EVs
Gen 3 replaced stat experience with EVs, which are different in a number of ways. We'll see those differences in this tutorial.
(EVs have an advantage outside of game mechanics: they take up fewer bytes. You'll end up with four unused bytes in the Pokémon data structure which can be used for all kinds of permanent data.)
- Replace stat experience with EVs in the Pokémon data structure
- Replace stat experience with EVs in base data
- Gain EVs from winning battles
- Calculate stats based on EVs
- Vitamins give EVs, not stat experience
- Replace Odd Egg and Battle Tower stat experience with EVs
- Replace
MON_STAT_EXP
withMON_EVS
everywhere - Replace some more labels
- Remove unused square root code
- Add Zinc to boost Special Defense EVs
- Limit total EVs to 510
- Replace stat experience with EVs in the Debug Room
Stat experience for each stat is a two-byte quantity from 0 to 65,535, with a single Special stat experience shared between Special Attack and Special Defense. EVs for each stat are one byte, from 0 to 255 (actually 252), with independent Special Attack and Special Defense quantities.
Edit macros/ram.asm:
MACRO box_struct
\1Species:: db
\1Item:: db
\1Moves:: ds NUM_MOVES
\1ID:: dw
\1Exp:: ds 3
-\1StatExp::
-\1HPExp:: dw
-\1AtkExp:: dw
-\1DefExp:: dw
-\1SpdExp:: dw
-\1SpcExp:: dw
+\1EVs::
+\1HPEV:: db
+\1AtkEV:: db
+\1DefEV:: db
+\1SpdEV:: db
+\1SpclAtkEV:: db
+\1SpclDefEV:: db
+\1Padding:: ds 4
\1DVs:: dw
\1PP:: ds NUM_MOVES
\1Happiness:: db
\1PokerusStatus:: db
\1CaughtData::
\1CaughtTime::
\1CaughtLevel:: db
\1CaughtGender::
\1CaughtLocation:: db
\1Level:: db
\1End::
ENDM
And edit constants/pokemon_data_constants.asm:
; party_struct members (see macros/ram.asm)
rsreset
DEF MON_SPECIES rb
DEF MON_ITEM rb
DEF MON_MOVES rb NUM_MOVES
DEF MON_ID rw
DEF MON_EXP rb 3
-DEF MON_STAT_EXP rw NUM_EXP_STATS
-rsset MON_STAT_EXP
-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_EVS rb NUM_STATS
+rsset MON_EVS
+DEF MON_HP_EV rb
+DEF MON_ATK_EV rb
+DEF MON_DEF_EV rb
+DEF MON_SPD_EV rb
+DEF MON_SAT_EV rb
+DEF MON_SDF_EV rb
+ rb_skip 4
DEF MON_DVS rw
...
DEF PARTYMON_STRUCT_LENGTH EQU _RS
DEF NICKNAMED_MON_STRUCT_LENGTH EQU PARTYMON_STRUCT_LENGTH + MON_NAME_LENGTH
DEF REDMON_STRUCT_LENGTH EQU 44
...
+; significant EV values
+DEF MAX_EV EQU 252
By replacing the 10 stat experience bytes with 6 EV bytes, we've freed up 4 bytes in box_struct
. That's valuable space, since it gets saved when Pokémon are deposited in the PC. Making use of it is beyond the scope of this tutorial, so we'll leave it as padding for now.
When you knock out a Pokémon, the stat experience you gain is equal to its base stats. That doesn't work for EVs; each species has its own set of EV yields, with a gain of 0 to 3 for each stat. That means we can store each stat's gain in two bits, so six stats will fit in two bytes. Conveniently, there are two unused bytes in base data that we can replace.
Edit ram/wram.asm:
; corresponds to the data/pokemon/base_stats/*.asm contents
wCurBaseData::
wBaseDexNo:: db
wBaseStats::
wBaseHP:: db
wBaseAttack:: db
wBaseDefense:: db
wBaseSpeed:: db
wBaseSpecialAttack:: db
wBaseSpecialDefense:: db
+wBaseEVs::
+wBaseHPAtkDefSpdEVs:: db
+wBaseSpAtkSpDefEVs:: db
wBaseType::
wBaseType1:: db
wBaseType2:: db
wBaseCatchRate:: db
wBaseExp:: db
wBaseItems::
wBaseItem1:: db
wBaseItem2:: db
wBaseGender:: db
-wBaseUnknown1:: db
wBaseEggSteps:: db
-wBaseUnknown2:: db
wBasePicSize:: db
wBasePadding:: ds 4
wBaseGrowthRate:: db
wBaseEggGroups:: db
wBaseTMHM:: flag_array NUM_TM_HM_TUTOR
wCurBaseDataEnd::
assert wCurBaseDataEnd - wCurBaseData == BASE_DATA_SIZE
Edit constants/pokemon_data_constants.asm:
; 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_SAT rb
DEF BASE_SDF rb
+DEF BASE_EVS rw
+rsset BASE_EVS
+DEF BASE_HP_ATK_DEF_SPD_EVS rb
+DEF BASE_SAT_SDF_EVS rb
DEF BASE_TYPES rw
rsset BASE_TYPES
DEF BASE_TYPE_1 rb
DEF BASE_TYPE_2 rb
DEF BASE_CATCH_RATE rb
DEF BASE_EXP rb
DEF BASE_ITEMS rw
rsset BASE_ITEMS
DEF BASE_ITEM_1 rb
DEF BASE_ITEM_2 rb
DEF BASE_GENDER rb
- rb_skip
DEF BASE_EGG_STEPS rb
- rb_skip
DEF BASE_PIC_SIZE rb
DEF BASE_FRONTPIC rw
DEF BASE_BACKPIC rw
DEF BASE_GROWTH_RATE rb
DEF BASE_EGG_GROUPS rb
DEF BASE_TMHM rb (NUM_TM_HM_TUTOR + 7) / 8
DEF BASE_DATA_SIZE EQU _RS
Edit data/pokemon/base_stats.asm:
+MACRO evs
+ db (\1 << 6) | (\2 << 4) | (\3 << 2) | \4
+ db (\5 << 6) | (\6 << 4)
+ENDM
; used in data/pokemon/base_stats/*.asm
MACRO tmhm
...
Finally, edit all 251 data/pokemon/base_stats/*.asm files. With each one, delete the unknown 1
and unknown 2
bytes and add evs
after base stats. For example, here's data/pokemon/base_stats/chikorita.asm:
db CHIKORITA ; 152
db 45, 49, 65, 45, 49, 65
+ evs 0, 0, 0, 0, 0, 1
; hp atk def spd sat sdf
db GRASS, GRASS ; type
db 45 ; catch rate
db 64 ; base exp
db NO_ITEM, NO_ITEM ; items
db GENDER_F12_5 ; gender ratio
- db 100 ; unknown 1
db 20 ; step cycles to hatch
- db 5 ; unknown 2
INCBIN "gfx/pokemon/chikorita/front.dimensions"
dw NULL, NULL ; unused (beta front/back pics)
db GROWTH_MEDIUM_SLOW ; growth rate
dn EGG_MONSTER, EGG_PLANT ; egg groups
; tm/hm learnset
...
You can do this automatically with a Python script. Save this as base-evs.py in the same directory as main.asm:
import glob
filenames = glob.glob('data/pokemon/base_stats/*.asm')
for filename in filenames:
print('Update', filename)
with open(filename, 'r', encoding='utf8') as file:
lines = file.readlines()
with open(filename, 'w', encoding='utf8') as file:
for line in lines:
if line in ['\tdb 100 ; unknown 1\n', '\tdb 5 ; unknown 2\n']:
continue
if line == '\t; hp atk def spd sat sdf\n':
file.write('\tevs 0, 0, 0, 0, 0, 0\n')
file.write(line)
Then run python3 base-evs.py
, just like running make
. It should output:
$ python3 base-evs.py
Update data/pokemon/base_stats/abra.asm
...
Update data/pokemon/base_stats/zubat.asm
(If it gives an error "python3: command not found
", you need to install Python 3. It's available as the python3
package in Cygwin.)
That will format all the base data files correctly, but with zero EV yields. To get the proper EV yields, you can use a Python script made by Nayru62.
Otherwise, you'll have to fill in the correct values yourself.
Edit engine/battle/core.asm:
GiveExperiencePoints:
...
-; give stat exp
- ld hl, MON_STAT_EXP + 1
- add hl, bc
- ld d, h
- ld e, l
- ld hl, wEnemyMonBaseStats - 1
- push bc
- ld c, NUM_EXP_STATS
-.stat_exp_loop
- inc hl
- ld a, [de]
- add [hl]
- ld [de], a
- jr nc, .no_carry_stat_exp
- dec de
- ld a, [de]
- inc a
- jr z, .stat_exp_maxed_out
- ld [de], a
- inc de
-
-.no_carry_stat_exp
- push hl
- push bc
- ld a, MON_POKERUS
- call GetPartyParamLocation
- ld a, [hl]
- and a
- pop bc
- pop hl
- jr z, .stat_exp_awarded
- ld a, [de]
- add [hl]
- ld [de], a
- jr nc, .stat_exp_awarded
- dec de
- ld a, [de]
- inc a
- jr z, .stat_exp_maxed_out
- ld [de], a
- inc de
- jr .stat_exp_awarded
-
-.stat_exp_maxed_out
- ld a, $ff
- ld [de], a
- inc de
- ld [de], a
-
-.stat_exp_awarded
- inc de
- inc de
- dec c
- jr nz, .stat_exp_loop
+; Give EVs
+; e = 0 for no Pokérus, 1 for Pokérus
+ ld e, 0
+ ld hl, MON_POKERUS
+ add hl, bc
+ ld a, [hl]
+ and a
+ jr z, .no_pokerus
+ inc e
+.no_pokerus
+ ld hl, MON_EVS
+ add hl, bc
+ push bc
+ ld a, [wEnemyMonSpecies]
+ ld [wCurSpecies], a
+ call GetBaseData
+; EV yield format: %hhaaddss %ttff0000
+; h = hp, a = atk, d = def, s = spd
+; t = sat, f = sdf, 0 = unused bits
+ ld a, [wBaseHPAtkDefSpdEVs]
+ ld b, a
+ ld c, NUM_STATS ; six EVs
+.ev_loop
+ rlc b
+ rlc b
+ ld a, b
+ and %11
+ bit 0, e
+ jr z, .no_pokerus_boost
+ add a
+.no_pokerus_boost
+ add [hl]
+ jr c, .ev_overflow
+ cp MAX_EV + 1
+ jr c, .got_ev
+.ev_overflow
+ ld a, MAX_EV
+.got_ev
+ ld [hli], a
+ dec c
+ jr z, .evs_done
+; Use the second byte for Sp.Atk and Sp.Def
+ ld a, c
+ cp 2 ; two stats left, Sp.Atk and Sp.Def
+ jr nz, .ev_loop
+ ld a, [wBaseSpAtkSpDefEVs]
+ ld b, a
+ jr .ev_loop
+.evs_done
...
.EvenlyDivideExpAmongParticipants:
...
ld [wTempByteValue], a
- ld hl, wEnemyMonBaseStats
- ld c, wEnemyMonEnd - wEnemyMonBaseStats
-.base_stat_division_loop
+ ld hl, wEnemyMonBaseExp
xor a
ldh [hDividend + 0], a
ld a, [hl]
ldh [hDividend + 1], a
ld a, [wTempByteValue]
ldh [hDivisor], a
ld b, 2
call Divide
ldh a, [hQuotient + 3]
- ld [hli], a
- dec c
- jr nz, .base_stat_division_loop
+ ld [hl], a
ret
Now instead of gaining the enemy's base stats toward your stat experience, you'll gain their base EV yields toward your EV totals. Having Pokérus will double EV gain.
Also, since we're not using stat experience, we no longer need to divide the enemy's base stats among the battle participants.
Edit engine/pokemon/move_mon.asm:
CalcMonStats:
; Calculates all 6 Stats of a mon
-; b: Take into account stat EXP if TRUE
+; b: Take into account EVs if TRUE
; 'c' counts from 1-6 and points with 'wBaseStats' to the base value
-; hl is the path to the Stat EXP
+; hl is the path to the EVs
; de points to where the final stats will be saved
ld c, STAT_HP - 1 ; first stat
.loop
inc c
call CalcMonStatC
ldh a, [hMultiplicand + 1]
ld [de], a
inc de
ldh a, [hMultiplicand + 2]
ld [de], a
inc de
ld a, c
cp STAT_SDEF ; last stat
jr nz, .loop
ret
CalcMonStatC:
; 'c' is 1-6 and points to the BaseStat
; 1: HP
; 2: Attack
; 3: Defense
; 4: Speed
; 5: SpAtk
; 6: SpDef
push hl
push de
push bc
ld a, b
ld d, a
push hl
ld hl, wBaseStats
dec hl ; has to be decreased, because 'c' begins with 1
ld b, 0
add hl, bc
ld a, [hl]
ld e, a
pop hl
push hl
- ld a, c
- cp STAT_SDEF ; last stat
- jr nz, .not_spdef
- dec hl
- dec hl
-
- .not_spdef
- sla c
ld a, d
and a
jr z, .no_stat_exp
add hl, bc
- push de
- ld a, [hld]
- ld e, a
- ld d, [hl]
- farcall GetSquareRoot
- pop de
+ ld a, [hl]
+ ld b, a
.no_stat_exp
- srl c
pop hl
push bc
- ld bc, MON_DVS - MON_HP_EXP + 1
+ ld bc, MON_DVS - MON_HP_EV + 1
add hl, bc
pop bc
...
The CalcMonStatC
implements these formulas for stat values:
- HP = (((base + IV) × 2 + √exp / 4) × level) / 100 + level + 10
- stat = (((base + IV) × 2 + √exp / 4) × level) / 100 + 5
In those formulas, division rounds down and square root rounds up (for example, √12 = 3.4641… rounds to 4). Order of operations is standard PEMDAS.
Anyway, we've just replaced √exp in those formulas with simply EV.
This change has consequences for progressing through the game. Square roots are nonlinear, so early gains to stat experience were contributing relatively larger boosts to stats. But EVs are linear, so gaining 4 EVs will be just as beneficial no matter how many you already had.
For example, 50 EVs are equivalent to 50² = 2,500 stat exp, and 100 EVs are equivalent to 100² = 10,000 stat exp. But getting from 50 EVs to 100 takes the same effort as from 0 to 50, whereas getting from 2,500 to 10,000 stat exp means gaining another 7,500 stat exp: three times as much effort as the first 2,500.
Eventually this won't matter, since the maximum 252 EVs or 65,535 stat exp both result in the same stats (252 / 4 = √65,535 / 4 = 63). But you may notice your Pokémon stats growing more slowly at first, and more quickly later on than you're used to.
Edit engine/items/item_effects.asm:
VitaminEffect:
ld b, PARTYMENUACTION_HEALING_ITEM
call UseItem_SelectMon
jp c, RareCandy_StatBooster_ExitMenu
call RareCandy_StatBooster_GetParameters
- call GetStatExpRelativePointer
+ call GetEVRelativePointer
- ld a, MON_STAT_EXP
+ ld a, MON_EVS
call GetPartyParamLocation
add hl, bc
ld a, [hl]
cp 100
jr nc, NoEffectMessage
add 10
ld [hl], a
call UpdateStatsAfterItem
- call GetStatExpRelativePointer
+ call GetEVRelativePointer
ld hl, StatStrings
add hl, bc
+ add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
ld de, wStringBuffer2
ld bc, ITEM_NAME_LENGTH
call CopyBytes
...
StatStrings:
dw .health
dw .attack
dw .defense
dw .speed
- dw .special
+ dw .sp_atk
.health db "HEALTH@"
.attack db "ATTACK@"
.defense db "DEFENSE@"
.speed db "SPEED@"
-.special db "SPECIAL@"
+.sp_atk db "SPCL.ATK@"
-GetStatExpRelativePointer:
+GetEVRelativePointer:
ld a, [wCurItem]
- ld hl, StatExpItemPointerOffsets
+ ld hl, EVItemPointerOffsets
...
-StatExpItemPointerOffsets:
- db HP_UP, MON_HP_EXP - MON_STAT_EXP
- db PROTEIN, MON_ATK_EXP - MON_STAT_EXP
- db IRON, MON_DEF_EXP - MON_STAT_EXP
- db CARBOS, MON_SPD_EXP - MON_STAT_EXP
- db CALCIUM, MON_SPC_EXP - MON_STAT_EXP
+EVItemPointerOffsets:
+ db HP_UP, MON_HP_EV - MON_EVS
+ db PROTEIN, MON_ATK_EV - MON_EVS
+ db IRON, MON_DEF_EV - MON_EVS
+ db CARBOS, MON_SPD_EV - MON_EVS
+ db CALCIUM, MON_SAT_EV - MON_EVS
Vitamins used to give 2,560 stat experience, up to a maximum of 25,600. Now they give 10 EVs, up to a maximum of 100. Conveniently, the vitamin code already used the values 10 and 100, because those are the high bytes of 2,560 and 25,600.
Due to that convenience, this mostly involved changing label and constant names. The only real adjustment needed was the offset to StatStrings
: stat experience and string pointers were both two-byte values, but now EVs are one byte, so we needed a second add hl, bc
to get the stat string corresponding to an EV.
We also replaced "SPECIAL" with "SPCL.ATK" since Calcium only affects the Special Attack EV. The same should be done for the description of Calcium.
Edit data/items/descriptions.asm:
CalciumDesc:
- db "Ups SPECIAL stats"
+ db "Raises SPCL.ATK"
next "of one #MON.@"
First, edit data/events/odd_eggs.asm. Make this same replacement 14 times, once for each hard-coded Odd Egg Pokémon structure:
- ; Stat exp
- bigdw 0
- bigdw 0
- bigdw 0
- bigdw 0
- bigdw 0
+ db 0, 0, 0, 0, 0, 0 ; EVs
+ db 0, 0, 0, 0 ; padding
Next, edit data/battle_tower/parties.asm. This is trickier for two reasons. One, there are 210 Pokémon structures instead of 14. Two, they have nonzero stat experience, and their hard-coded stats need to match their new EV values. For example:
db JOLTEON
db MIRACLEBERRY
db THUNDERBOLT, HYPER_BEAM, SHADOW_BALL, ROAR
dw 0 ; OT ID
dt 1000 ; Exp
- ; Stat exp
- bigdw 50000
- bigdw 40000
- bigdw 40000
- bigdw 35000
- bigdw 40000
+ db 224, 200, 200, 188, 200, 200 ; EVs
+ db 0, 0, 0, 0 ; padding
dn 13, 13, 11, 13 ; DVs
db 15, 5, 15, 20 ; PP
db 100 ; Happiness
db 0, 0, 0 ; Pokerus, Caught data
db 10 ; Level
db 0, 0 ; Status
bigdw 41 ; HP
bigdw 41 ; Max HP
bigdw 25 ; Atk
bigdw 24 ; Def
bigdw 37 ; Spd
bigdw 34 ; SAtk
bigdw 31 ; SDef
db "SANDA-SU@@@"
Numerically speaking, you just have to take the square root of each stat experience value and round up to an integer EV; but you have to do this for 210 × 5 values, and insert padding bytes.
You can do this automatically with a Python script. Save this as bt-evs.py in the same directory as main.asm:
from math import sqrt, ceil
def derive_ev(stat_exp_line):
stat_exp = int(stat_exp_line[len('\tbigdw '):])
return str(int(ceil(sqrt(stat_exp))))
filename = 'data/battle_tower/parties.asm'
with open(filename, 'r', encoding='utf8') as file:
lines = file.readlines()
with open(filename, 'w', encoding='utf8') as file:
i = 0
while i < len(lines):
line = lines[i]
if line != '\t; Stat exp\n':
file.write(line)
i += 1
continue
exp_lines = lines[i+1:i+6]
evs = [derive_ev(exp_line) for exp_line in exp_lines]
evs.append(evs[-1]) # Special -> Sp.Atk and Sp.Def
file.write('\tdb {} ; EVs\n'.format(', '.join(evs)))
file.write('\tdb 0, 0, 0, 0 ; padding\n')
i += 6
print('Done!')
Then run python3 bt-evs.py
. It should output:
$ python3 battle-tower-evs.py
Done!
Replace every occurrence of MON_STAT_EXP
with MON_EVS
in these files:
-
engine/battle/core.asm again (two, in
LoadEnemyMon
andGiveExperiencePoints
) -
engine/pokemon/move_mon.asm again (five; three in
GeneratePartyMonStats
, one inSendGetMonIntoFromBox
, one inComputeNPCTrademonStats
-
engine/items/item_effects.asm again (one, in
UpdateStatsAfterItem
) -
engine/events/battle_tower/battle_tower.asm (one, in
ValidateBTParty
) -
engine/link/link.asm (three; one in
Link_PrepPartyData_Gen1
, two inLink_ConvertPartyStruct1to2
) -
engine/pokemon/breeding.asm (one, in
HatchEggs
) -
engine/pokemon/correct_party_errors.asm (one, in
CorrectPartyErrors
) -
engine/pokemon/tempmon.asm (one, in
_TempMonStatsCalculation
) -
mobile/mobile_46.asm (one, in
TradeCornerHoldMon_PrepareForUpload
)
Most of the MON_STAT_EXP
occurrences are part of an argument passed to CalcMonStats
.
Edit engine/events/daycare.asm:
DayCare_InitBreeding:
...
xor a
- ld b, wEggMonDVs - wEggMonStatExp
- ld hl, wEggMonStatExp
+ ld b, wEggMonDVs - wEggMonEVs
+ ld hl, wEggMonEVs
.loop2
ld [hli], a
dec b
jr nz, .loop2
We're technically done now; EVs will work behind the scenes just like stat experience did. But there's room for more improvement.
The only place GetSquareRoot
was used was in CalcMonStatC
. Without that, we can safely remove it.
Delete engine/math/get_square_root.asm.
Then edit main.asm:
-INCLUDE "engine/math/get_square_root.asm"
Now that Calcium only boosts Special Attack EVs, we need Zinc for Special Defense. Follow this tutorial to add a new item.
First, add the essential data. Replace ITEM_89
with ZINC
; give it a name, description, and attributes (9800, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_PARTY, ITEMMENU_NOUSE
); and give it the VitaminEffect
. (ITEM_89
is not in TimeCapsule_CatchRateItems
.)
Then edit engine/items/item_effects.asm again:
StatStrings:
dw .health
dw .attack
dw .defense
dw .speed
dw .sp_atk
+ dw .sp_def
.health db "HEALTH@"
.attack db "ATTACK@"
.defense db "DEFENSE@"
.speed db "SPEED@"
.sp_atk db "SPCL.ATK@"
+.sp_def db "SPCL.DEF@"
...
EVItemPointerOffsets:
db HP_UP, MON_HP_EV - MON_EVS
db PROTEIN, MON_ATK_EV - MON_EVS
db IRON, MON_DEF_EV - MON_EVS
db CARBOS, MON_SPD_EV - MON_EVS
db CALCIUM, MON_SAT_EV - MON_EVS
+ db ZINC, MON_SDF_EV - MON_EVS
That's all!
At this point all stats use EVs instead of Stat Exp, but the total EV limit form Gen 3 onward hasn't been coded yet; in this section we're going to implement it. First, edit constants/pokemon_data_constants.asm again:
; significant EV values
DEF MAX_EV EQU 252
+DEF MAX_TOTAL_EV EQU 510
Next edit GiveExperiencePoints
in engine/battle/core.asm again:
jr z, .no_pokerus_boost
add a
.no_pokerus_boost
+; Make sure total EVs never surpass 510
+ push bc
+ push hl
+ ld d, a
+ ld a, c
+.find_correct_ev_address
+ ; If address of first EV is changed, find the correct one.
+ cp NUM_STATS
+ jr z, .found_address
+ dec hl
+ inc a
+ jr .find_correct_ev_address
+.found_address
+ ld e, NUM_STATS
+ ld bc, 0
+.count_evs
+ ld a, [hli]
+ add c
+ ld c, a
+ jr nc, .cont
+ inc b
+.cont
+ dec e
+ jr nz, .count_evs
+ ld a, d
+ add c
+ ld c, a
+ adc b
+ sub c
+ ld b, a
+ ld e, d
+.decrease_evs_gained
+ call IsEvsGreaterThan510
+ jr nc, .check_ev_overflow
+ dec e
+ dec bc
+ jr .decrease_evs_gained
+.check_ev_overflow
+ pop hl
+ pop bc
+ ld a, e
add [hl]
jr c, .ev_overflow
cp MAX_EV + 1
...
ldh a, [hQuotient + 3]
ld [hl], a
ret
+IsEvsGreaterThan510:
+; Total EVs in bc. Set Carry flag if bc > 510.
+ ld a, HIGH(MAX_TOTAL_EV)
+ cp b
+ ret nz
+ ld a, LOW(MAX_TOTAL_EV)
+ cp c
+ ret
What this does is first count your Pokémon's total EVs, then add the EVs you would normally gain. If the total EVs after the battle is greater than 510, decrease the EVs you gained until the total EVs after the battle is 510.
But this change doesn't affect vitamins, thus, we need to do another edit.
Edit VitaminEffect
in engine/items/item_effects.asm again:
VitaminEffect:
...
ld a, MON_EVS
call GetPartyParamLocation
+ ld d, 10
+ push bc
+ push hl
+ ld e, NUM_STATS
+ ld bc, 0
+.count_evs
+ ld a, [hli]
+ add c
+ ld c, a
+ jr nc, .cont
+ inc b
+.cont
+ dec e
+ jr nz, .count_evs
+ ld a, d
+ add c
+ ld c, a
+ adc b
+ sub c
+ ld b, a
+ ld e, d
+.decrease_evs_gained
+ farcall IsEvsGreaterThan510
+ jr nc, .check_ev_overflow
+ dec e
+ dec bc
+ jr .decrease_evs_gained
+.check_ev_overflow
+ pop hl
+ pop bc
+ ld a, e
+ and a
+ jr z, NoEffectMessage
add hl, bc
ld a, [hl]
cp 100
jr nc, NoEffectMessage
- add 10
+ add e
ld [hl], a
call UpdateStatsAfterItem
...
This way, not only the limit of 510 EVs is implemented, but it will also display a message if the total EVs has reached the max limit.
This is only relevant if you're building a debug ROM. If you're not, you can skip this step.
The "POKéMON GET!" option in the Debug Room creates a Pokémon by manually editing each field of its party_struct
. We need to change the stat experience fields to EVs, otherwise the debug ROM can't be assembled.
Edit engine/debug/debug_room.asm:
DebugRoomMenu_PokemonGet_Page2Values:
- db 8
- paged_value wDebugRoomMonHPExp+0, $00, $ff, $00, DebugRoom_BoxStructStrings.HPExp0, NULL, FALSE
- paged_value wDebugRoomMonHPExp+1, $00, $ff, $00, DebugRoom_BoxStructStrings.HPExp1, NULL, FALSE
- paged_value wDebugRoomMonAtkExp+0, $00, $ff, $00, DebugRoom_BoxStructStrings.AttkExp0, NULL, FALSE
- paged_value wDebugRoomMonAtkExp+1, $00, $ff, $00, DebugRoom_BoxStructStrings.AttkExp1, NULL, FALSE
- paged_value wDebugRoomMonDefExp+0, $00, $ff, $00, DebugRoom_BoxStructStrings.DfnsExp0, NULL, FALSE
- paged_value wDebugRoomMonDefExp+1, $00, $ff, $00, DebugRoom_BoxStructStrings.DfnsExp1, NULL, FALSE
- paged_value wDebugRoomMonSpdExp+0, $00, $ff, $00, DebugRoom_BoxStructStrings.SpeedExp0, NULL, FALSE
- paged_value wDebugRoomMonSpdExp+1, $00, $ff, $00, DebugRoom_BoxStructStrings.SpeedExp1, NULL, FALSE
+ db 6
+ paged_value wDebugRoomMonHPEV, $00, $ff, $00, DebugRoom_BoxStructStrings.HPEV, NULL, FALSE
+ paged_value wDebugRoomMonAtkEV, $00, $ff, $00, DebugRoom_BoxStructStrings.AttackEV, NULL, FALSE
+ paged_value wDebugRoomMonDefEV, $00, $ff, $00, DebugRoom_BoxStructStrings.DefenseEV, NULL, FALSE
+ paged_value wDebugRoomMonSpdEV, $00, $ff, $00, DebugRoom_BoxStructStrings.SpeedEV, NULL, FALSE
+ paged_value wDebugRoomMonSpclAtkEV, $00, $ff, $00, DebugRoom_BoxStructStrings.SpclAtkEV, NULL, FALSE
+ paged_value wDebugRoomMonSpclDefEV, $00, $ff, $00, DebugRoom_BoxStructStrings.SpclDefEV, NULL, FALSE
DebugRoomMenu_PokemonGet_Page3Values:
- db 8
- paged_value wDebugRoomMonSpcExp+0, $00, $ff, $00, DebugRoom_BoxStructStrings.SpclExp0, NULL, FALSE
- paged_value wDebugRoomMonSpcExp+1, $00, $ff, $00, DebugRoom_BoxStructStrings.SpclExp1, NULL, FALSE
+ db 6
paged_value wDebugRoomMonDVs+0, $00, $ff, $00, DebugRoom_BoxStructStrings.PowerRnd0, NULL, TRUE
paged_value wDebugRoomMonDVs+1, $00, $ff, $00, DebugRoom_BoxStructStrings.PowerRnd1, NULL, TRUE
paged_value wDebugRoomMonPP+0, $00, $ff, $00, DebugRoom_BoxStructStrings.PP1, NULL, FALSE
paged_value wDebugRoomMonPP+1, $00, $ff, $00, DebugRoom_BoxStructStrings.PP2, NULL, FALSE
paged_value wDebugRoomMonPP+2, $00, $ff, $00, DebugRoom_BoxStructStrings.PP3, NULL, FALSE
paged_value wDebugRoomMonPP+3, $00, $ff, $00, DebugRoom_BoxStructStrings.PP4, NULL, FALSE
DebugRoom_BoxStructStrings:
...
-.HPExp0: db "HP EXP[0]@"
-.HPExp1: db "HP EXP[1]@"
-.AttkExp0: db "ATTK EXP[0]@"
-.AttkExp1: db "ATTK EXP[1]@"
-.DfnsExp0: db "DFNS EXP[0]@"
-.DfnsExp1: db "DFNS EXP[1]@"
-.SpeedExp0: db "SPEED EXP[0]@"
-.SpeedExp1: db "SPEED EXP[1]@"
-.SpclExp0: db "SPCL EXP[0]@"
-.SpclExp1: db "SPCL EXP[1]@"
+.HPEV: db "HP EV@"
+.AttackEV: db "ATTACK EV@"
+.DefenseEV: db "DEFENSE EV@"
+.SpeedEV: db "SPEED EV@"
+.SpclAtkEV: db "SPCL ATK EV@"
+.SpclDefEV: db "SPCL DEF EV@"
...
TODO: add Macho Brace.