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.) ## Contents 1. [Replace stat experience with EVs in the Pokémon data structure](#1-replace-stat-experience-with-evs-in-the-pokémon-data-structure) 2. [Replace stat experience with EVs in base data](#2-replace-stat-experience-with-evs-in-base-data) 3. [Gain EVs from winning battles](#3-gain-evs-from-winning-battles) 4. [Calculate stats based on EVs](#4-calculate-stats-based-on-evs) 5. [Vitamins give EVs, not stat experience](#5-vitamins-give-evs-not-stat-experience) 6. [Replace Odd Egg and Battle Tower stat experience with EVs](#6-replace-odd-egg-and-battle-tower-stat-experience-with-evs) 7. [Replace `MON_STAT_EXP` with `MON_EVS` everywhere](#7-replace-mon_stat_exp-with-mon_evs-everywhere) 8. [Replace some more labels](#8-replace-some-more-labels) 9. [Remove unused square root code](#9-remove-unused-square-root-code) 10. [Add Zinc to boost Special Defense EVs](#10-add-zinc-to-boost-special-defense-evs) 11. [Limit total EVs to 510](#11-limit-total-evs-to-510) 12. [Replace stat experience with EVs in the Debug Room](#12-replace-stat-experience-with-evs-in-the-debug-room) ## 1. Replace stat experience with EVs in the Pokémon data structure 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/wram.asm](../blob/master/macros/wram.asm): ```diff box_struct: MACRO \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](../blob/master/constants/pokemon_data_constants.asm): ```diff ; party_struct members (see macros/wram.asm) rsreset MON_SPECIES rb MON_ITEM rb MON_MOVES rb NUM_MOVES MON_ID rw MON_EXP rb 3 -MON_STAT_EXP rw NUM_EXP_STATS -rsset MON_STAT_EXP -MON_HP_EXP rw -MON_ATK_EXP rw -MON_DEF_EXP rw -MON_SPD_EXP rw -MON_SPC_EXP rw +MON_EVS rb NUM_STATS +rsset MON_EVS +MON_HP_EV rb +MON_ATK_EV rb +MON_DEF_EV rb +MON_SPD_EV rb +MON_SAT_EV rb +MON_SDF_EV rb + rb_skip 4 MON_DVS rw ... PARTYMON_STRUCT_LENGTH EQU _RS NICKNAMED_MON_STRUCT_LENGTH EQU PARTYMON_STRUCT_LENGTH + MON_NAME_LENGTH REDMON_STRUCT_LENGTH EQU 44 ... +; significant EV values +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. ## 2. Replace stat experience with EVs in base data 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 [wram.asm](../blob/master/wram.asm): ```diff ; 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:: ``` Edit [constants/pokemon_data_constants.asm](../blob/master/constants/pokemon_data_constants.asm): ```diff ; base data struct members (see data/pokemon/base_stats/*.asm) rsreset BASE_DEX_NO rb BASE_STATS rb NUM_STATS rsset BASE_STATS BASE_HP rb BASE_ATK rb BASE_DEF rb BASE_SPD rb BASE_SAT rb BASE_SDF rb +BASE_EVS rw +rsset BASE_EVS +BASE_HP_ATK_DEF_SPD_EVS rb +BASE_SAT_SDF_EVS rb BASE_TYPES rw rsset BASE_TYPES BASE_TYPE_1 rb BASE_TYPE_2 rb BASE_CATCH_RATE rb BASE_EXP rb BASE_ITEMS rw rsset BASE_ITEMS BASE_ITEM_1 rb BASE_ITEM_2 rb BASE_GENDER rb - rb_skip BASE_EGG_STEPS rb - rb_skip BASE_PIC_SIZE rb BASE_FRONTPIC rw BASE_BACKPIC rw BASE_GROWTH_RATE rb BASE_EGG_GROUPS rb BASE_TMHM rb (NUM_TM_HM_TUTOR + 7) / 8 BASE_DATA_SIZE EQU _RS ``` Edit [data/pokemon/base_stats.asm](../blob/master/data/pokemon/base_stats.asm): ```diff +evs: MACRO + db (\1 << 6) | (\2 << 4) | (\3 << 2) | \4 + db (\5 << 6) | (\6 << 4) +ENDM tmhm: MACRO ... ``` Finally, edit all 251 [data/pokemon/base_stats/\*.asm](../tree/master/data/pokemon/base_stats/) 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](../blob/master/data/pokemon/base_stats/chikorita.asm): ```diff 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: ```python 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. You'll have to fill in the correct values yourself. ## 3. Gain EVs from winning battles Edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm): ```diff 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_PKRUS - 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 Pokerus, 1 for Pokerus + ld e, 0 + ld hl, MON_PKRUS + 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. ## 4. Calculate stats based on EVs Edit [engine/pokemon/move_mon.asm](../blob/master/engine/pokemon/move_mon.asm): ```diff 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](https://en.wikipedia.org/wiki/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. ## 5. Vitamins give EVs, not stat experience Edit [engine/items/item_effects.asm](../blob/master/engine/items/item_effects.asm): ```diff 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](../blob/master/data/items/descriptions.asm): ```diff CalciumDesc: - db "Ups SPECIAL stats" + db "Raises SPCL.ATK" next "of one #MON.@" ``` ## 6. Replace Odd Egg and Battle Tower stat experience with EVs First, edit [data/events/odd_eggs.asm](../blob/master/data/events/odd_eggs.asm). Make this same replacement 14 times, once for each hard-coded Odd Egg Pokémon structure: ```diff - ; 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](../blob/master/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: ```diff 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: ```python 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! ``` ## 7. Replace `MON_STAT_EXP` with `MON_EVS` everywhere Replace every occurrence of `MON_STAT_EXP` with `MON_EVS` in these files: - [engine/battle/core.asm](../blob/master/engine/battle/core.asm) again (two, in `LoadEnemyMon` and `GiveExperiencePoints`) - [engine/pokemon/move_mon.asm](../blob/master/engine/pokemon/move_mon.asm) again (five; three in `GeneratePartyMonStats`, one in `SendGetMonIntoFromBox`, one in `ComputeNPCTrademonStats` - [engine/items/item_effects.asm](../blob/master/engine/items/item_effects.asm) again (one, in `UpdateStatsAfterItem`) - [engine/events/battle_tower/battle_tower.asm](../blob/master/engine/events/battle_tower/battle_tower.asm) (one, in `ValidateBTParty`) - [engine/link/link.asm](../blob/master/engine/link/link.asm) (three; one in `Link_PrepPartyData_Gen1`, two in `Function2868a`) - [engine/pokemon/breeding.asm](../blob/master/engine/pokemon/breeding.asm) (one, in `HatchEggs`) - [engine/pokemon/correct_party_errors.asm](../blob/master/engine/pokemon/correct_party_errors.asm) (one, in `Unreferenced_CorrectPartyErrors`) - [engine/pokemon/tempmon.asm](../blob/master/engine/pokemon/tempmon.asm) (one, in `_TempMonStatsCalculation`) - [mobile/mobile_46.asm](../blob/master/mobile/mobile_46.asm) (two; one in `Function11b483`, one in `Function11b6b4`) Most of the `MON_STAT_EXP` occurrences are part of an argument passed to `CalcMonStats`. ## 8. Replace some more labels Edit [engine/events/daycare.asm](../blob/master/engine/events/daycare.asm): ```diff 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. ## 9. Remove unused square root code The only place `GetSquareRoot` was used was in `CalcMonStatC`. Without that, we can safely remove it. Delete [engine/math/get_square_root.asm](../blob/master/engine/math/get_square_root.asm). Then edit [main.asm](../blob/master/main.asm): ```diff -INCLUDE "engine/math/get_square_root.asm" ``` ## 10. Add Zinc to boost Special Defense EVs Now that Calcium only boosts Special Attack EVs, we need Zinc for Special Defense. Follow [this tutorial](Add-a-new-item) 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](../blob/master/engine/items/item_effects.asm) again: ```diff 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! ![Screenshot](screenshots/zinc.png) ## 11. Limit total EVs to 510 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](../blob/master/constants/pokemon_data_constants.asm) again: ```diff ; significant EV values MAX_EV EQU 252 +MAX_TOTAL_EV EQU 510 ``` Next edit `GiveExperiencePoints` in [engine/battle/core.asm](../blob/master/engine/battle/core.asm) again: ```diff 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](../blob/master/engine/items/item_effects.asm) again: ```diff 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. ## 12. Replace stat experience with EVs in the Debug Room 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](../blob/master/engine/debug/debug_room.asm): ```diff 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 ``` ```diff 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.