summaryrefslogtreecommitdiff
path: root/Replace-stat-experience-with-EVs.md
blob: 7ecce09f6ba156280b66bbf5d7dee454a3adbc6f (plain)
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!

![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.