1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
|
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!

## 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.
|