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
|
Pokémon Gold, Silver and Crystal use a slot-based encounter table, rolling against the current area's table to generate a wildmon for the player to fight.
You can only fit so many Pokémon in an area's wild encounter table, and specify the level of each slot. If you wanted to have variance in levels, you would have to use another slot for the same Pokémon at a different level, thus limiting the amount of unique wildmon in that location. (Example: a Lv. 3 Rattata and a Lv. 4 Rattata require two slots, one for each level).
This tutorial will teach you how to get around that and be able to use fewer slots (freeing up more space for more unique species!), via three different methods:
1. Custom encounter table with level variation
2. Custom probabilities and level ranges for each encounter table.
3. Hijacking the surf variance code
Each has their benefits: the first method allows more fine-tuned control over the level variance, albeit a more invested change; the second one allows for even more control at the cost of space; finally, the last one is much simpler to implement across the game, requiring less setup and lacking more control over the levels.
I'd recommend starting with the easier method first, to understand how this works and what exactly it does, before moving on to a more complex implementation.
You decide what works best for you and your vision for your fangame!
## Contents
1. [Method #1: Understanding Bug Catching Contest Encounter Code](#1-method-1-understanding-bug-catching-contest-encounter-code)
1. [Add max levels in probabilities](#11-add-max-levels-in-probabilities)
2. [Change wild encounter algorithm](#12-change-wild-encounter-algorithm)
3. [Other notes for Method 1](#13-other-notes-for-method-1)
2. [Method #2: Custom probabilities and level ranges for each encounter table](#2-method-2-custom-probabilities-and-level-ranges-for-each-encounter-table)
1. [Adjust data related to wildata constants](#21-adjust-data-related-to-wildata-constants)
2. [Edit the encounter tables](#22-edit-the-encounter-tables)
3. [Fix space issues](#23-fix-space-issues)
3. [Method #3: Hijack the surf variance code](#3-method-3-hijack-the-surf-variance-code)
## 1. Method #1: Understanding Bug Catching Contest Encounter Code
First, we look at a snippet used in [engine/overworld/events.asm](../blob/master/engine/overworld/events.asm) because we'll use a portion of the code to replace a portion of wild encounter code.
```asm
ChooseWildEncounter_BugContest::
; Pick a random mon out of ContestMons.
.loop
call Random
cp 100 << 1
jr nc, .loop
srl a
ld hl, ContestMons
ld de, 4
.CheckMon:
sub [hl]
jr c, .GotMon
add hl, de
jr .CheckMon
.GotMon:
inc hl
; Species
ld a, [hli]
ld [wTempWildMonSpecies], a
; Min level
ld a, [hli]
ld d, a
; Max level
ld a, [hl]
sub d
jr nz, .RandomLevel
; If min and max are the same.
ld a, d
jr .GotLevel
.RandomLevel:
; Get a random level between the min and max.
ld c, a
inc c
call Random
ldh a, [hRandomAdd]
call SimpleDivide
add d
.GotLevel:
ld [wCurPartyLevel], a
xor a
ret
```
This code is used to randomize wild encounters in the Bug Catching Contest. We then look at [data/wild/bug_contest_mons.asm](../blob/master/data/wild/bug_contest_mons.asm).
```asm
ContestMons:
; %, species, min, max
db 20, CATERPIE, 7, 18
db 20, WEEDLE, 7, 18
db 10, METAPOD, 9, 18
db 10, KAKUNA, 9, 18
db 5, BUTTERFREE, 12, 15
db 5, BEEDRILL, 12, 15
db 10, VENONAT, 10, 16
db 10, PARAS, 10, 17
db 5, SCYTHER, 13, 14
db 5, PINSIR, 13, 14
db -1, VENOMOTH, 30, 40
```
We can see that there's a minimum and maximum level for every wild Pokémon. The last slot, occupied by Venomoth, signals the end of the list. We can use a part of the algorithm of Bug Catching Contest encounters in [engine/overworld/events.asm](../blob/master/engine/overworld/events.asm) to randomize regular wild encounters.
### 1.1. Add max levels in probabilities
Edit [data/wild/probabilities.asm](https://github.com/pret/pokecrystal/blob/master/data/wild/probabilities.asm).
```diff
...
mon_prob 100, 2 ; 10% chance
assert_table_length NUM_WATERMON
+MaxLevelGrass:
+ db 2
+ db 3
+ db 0
+ db 2
+ db 1
+ db 2
+ db 3
+
+MaxLevelWater:
+ db 2
+ db 3
+ db 4
```
Both `MaxLevelGrass` and `MaxLevelWater` contain the level buffs to obtain the highest-leveled encounter on that area. For example, if the first slot in the encounter table is Lv. 2, then the max level for the wild Pokémon will be Lv. 4.
### 1.2. Change wild encounter algorithm
Finally, we change the wild encounter algorithm in [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm):
```diff
...
ld h, d
ld l, e
+ call CheckOnWater
+ ld de, MaxLevelWater
+ jr z, .prob_bracket_loop
+ ld de, MaxLevelGrass
; This next loop chooses which mon to load up.
.prob_bracket_loop
ld a, [hli]
cp b
jr nc, .got_it
inc hl
+ inc de
jr .prob_bracket_loop
.got_it
ld c, [hl]
ld b, 0
pop hl
add hl, bc ; this selects our mon
+; Min Level
ld a, [hli]
ld b, a
-; If the Pokemon is encountered by surfing, we need to give the levels some variety.
- call CheckOnWater
- jr nz, .ok
-; Check if we buff the wild mon, and by how much.
- call Random
- cp 35 percent
- jr c, .ok
- inc b
- cp 65 percent
- jr c, .ok
- inc b
- cp 85 percent
- jr c, .ok
- inc b
- cp 95 percent
- jr c, .ok
- inc b
+; Max Level
+ ld a, [de]
+; Min Level
+ ld d, b
+ ld b, a
+ and a
+ jr nz, .RandomLevel
+; If min and max are the same.
+ ld b, d
+ jr .ok
+
+.RandomLevel:
+; Get a random level between the min and max.
+ ld c, a
+ inc c
+ call Random
+ ldh a, [hRandomAdd]
+ call SimpleDivide
+ add d
+ ld b, a
+
; Store the level
.ok
ld a, b
```
And there you have it! Wild encounters have been randomized.
### 1.3. Other notes for method #1
Take note that the slots in `MaxLevelGrass` and `MaxLevelWater` in [data/wild/probabilities.asm](../blob/master/data/wild/probabilities.asm) correspond to the probability slots in the same file. Also, each slot is tied to the wild Pokémon slots in each map where there are wild encounters. Thus if you use the tutorial to [add a wild Pokémon slot](Add-a-new-wild-Pokémon-slot), you also need to add new slots in `MaxLevelGrass` and/or `MaxLevelWater`.
Sometimes, you might want to create custom encounter tables. For example:

If you add [wild Pokémon slots](Add-a-new-wild-Pokémon-slot) to accommodate all encounters in the morning, you'd have to use 19 slots because each level is placed in one slot. So for example, you need to use 3 slots for Abra, one for each level: Lv. 13, Lv. 14, and Lv. 15. After following this tutorial, you no longer need so many of the same Pokémon occupying multiple slots in the table, so extra slots can now mean more unique species in an area!
## 2. Method #2: custom probabilities and level ranges for each encounter table
Method #1 has some inconveniences, though: each slot still has a fixed probability and the same level variation. For example, the first slot of each encounter table will always have a 30% probability and will vary at most by 2 levels from the minumum. What if we wanted to have custom probabilities and individual level ranges for each slot for each encounter table? It's totally possible!
With the following method, you'll have even more control over these two aspects, albeit it'll take more space than method #1.
First, go and edit [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm):
```diff
ChooseWildEncounter:
...
call CheckOnWater
- ld de, WaterMonProbTable
jr z, .watermon
inc hl
inc hl
ld a, [wTimeOfDay]
- ld bc, NUM_GRASSMON * 2
+ ld bc, NUM_GRASSMON * 4
call AddNTimes
- ld de, GrassMonProbTable
.watermon
-; hl contains the pointer to the wild mon data, let's save that to the stack
- push hl
.randomloop
call Random
cp 100
jr nc, .randomloop
- inc a ; 1 <= a <= 100
- ld b, a
- ld h, d
- ld l, e
+ ld de, 4
; This next loop chooses which mon to load up.
.prob_bracket_loop
- ld a, [hli]
- cp b
- jr nc, .got_it
- inc hl
+ sub [hl]
+ jr c, .got_it
+ add hl, de
jr .prob_bracket_loop
.got_it
- ld c, [hl]
- ld b, 0
- pop hl
- add hl, bc ; this selects our mon
- ld a, [hli]
- ld b, a
-; If the Pokemon is encountered by surfing, we need to give the levels some variety.
- call CheckOnWater
- jr nz, .ok
-; Check if we buff the wild mon, and by how much.
- call Random
- cp 35 percent
- jr c, .ok
- inc b
- cp 65 percent
- jr c, .ok
- inc b
- cp 85 percent
- jr c, .ok
- inc b
- cp 95 percent
- jr c, .ok
- inc b
-; Store the level
-.ok
- ld a, b
- ld [wCurPartyLevel], a
- ld b, [hl]
- ; ld a, b
+ inc hl
+ ld a, [hli]
+ ld b, a
call ValidateTempWildMonSpecies
jr c, .nowildbattle
- ld a, b ; This is in the wrong place.
cp UNOWN
- jr nz, .done
+ jr nz, .load_species
ld a, [wUnlockedUnowns]
and a
jr z, .nowildbattle
+ ld a, b
+.load_species
+ ld [wTempWildMonSpecies], a
+
+; Min level
+ ld a, [hli]
+ ld d, a
+
+; Max level
+ ld a, [hl]
+ sub d
+ jr nz, .RandomLevel
+
+; If min and max are the same.
+ ld a, d
+ jr .GotLevel
+
+.RandomLevel:
+; Get a random level between the min and max.
+ ld c, a
+ inc c
+ call Random
+ ldh a, [hRandomAdd]
+ call SimpleDivide
+ add d
+
+.GotLevel:
+ ld [wCurPartyLevel], a
+
+.startwildbattle
+ xor a
+ ret
-.done
- jr .loadwildmon
.nowildbattle
ld a, 1
and a
ret
-.loadwildmon
- ld a, b
- ld [wTempWildMonSpecies], a
-
-.startwildbattle
- xor a
- ret
-INCLUDE "data/wild/probabilities.asm"
```
Let's give a quick explanation. The code works similarly to `ChooseWildEncounter_BugContest`, where each Pokémon slot contains the probability, the species, the minimum level and the maximum level. Also, since we're not going to use [data/wild/probabilities.asm](../blob/master/data/wild/probabilities.asm) anymore, we can safely delete it.
You might have noticed that we did `ld bc, NUM_GRASSMON * 4` at some point and it's because each slot will now contain four bytes (% chance, species, min. level and max. level) instead of two (level, species). We need to make similar changes in other places, too.
### 2.1. Adjust data related to Wildata constants
Let's edit data related to `NUM_GRASSMON` and `NUM_WATERMON`. In the same file:
```diff
FindNest:
...
.ScanMapLoop:
push af
ld a, [wNamedObjectIndex]
cp [hl]
jr z, .found
inc hl
inc hl
+ inc hl
+ inc hl
pop af
dec a
jr nz, .ScanMapLoop
and a
ret
```
```diff
RandomUnseenWildMon:
...
.GetGrassmon:
push hl
- ld bc, 5 + 4 * 2 ; Location of the level of the 5th wild Pokemon in that map
+ ld bc, 5 + 4 * 4 ; Location of the level of the 5th wild Pokemon in that map
add hl, bc
call GetTimeOfDayNotEve
- ld bc, NUM_GRASSMON * 2
+ ld bc, NUM_GRASSMON * 4
call AddNTimes
.randloop1
...
ld c, [hl] ; Contains the species index of this rare Pokemon
pop hl
- ld de, 5 + 0 * 2
+ ld de, 5 + 0 * 4
add hl, de
...
```
```diff
RandomPhoneWildMon:
...
.ok
- ld bc, 5 + 0 * 2
+ ld bc, 5 + 0 * 4
add hl, bc
call GetTimeOfDayNotEve
inc a
- ld bc, NUM_GRASSMON * 2
+ ld bc, NUM_GRASSMON * 4
.loop
...
```
Also in [engine/pokegear/radio.asm](../blob/master/engine/pokegear/radio.asm):
```diff
OaksPKMNTalk4:
...
.loop2
call Random
maskbits NUM_DAYTIMES
cp EVE_F
jr z, .loop2
- ld bc, 2 * NUM_GRASSMON
+ ld bc, 4 * NUM_GRASSMON
call AddNTimes
```
Finally, edit [constants/pokemon_data_constants.asm](../blob/master/constants/pokemon_data_constants.asm):
```diff
-GRASS_WILDDATA_LENGTH EQU 2 + 3 + NUM_GRASSMON * 2 * 3
-WATER_WILDDATA_LENGTH EQU 2 + 1 + NUM_WATERMON * 2
+GRASS_WILDDATA_LENGTH EQU 2 + 3 + NUM_GRASSMON * 4 * 3
+WATER_WILDDATA_LENGTH EQU 2 + 1 + NUM_WATERMON * 4
```
About this last edit, since the encounter tables now have more bytes the data length of each one is bigger, so we needed to adjust the related constants.
### 2.2. Edit the encounter tables
This is the most tedious part. You'll now have to edit _each encounter table_ to match the new format. For example, this is how it'd look like for Sprout Tower 2F:
```diff
JohtoGrassWildMons:
def_grass_wildmons SPROUT_TOWER_2F
db 2 percent, 2 percent, 2 percent ; encounter rates: morn/day/nite
- ; morn
- db 3, RATTATA
- db 4, RATTATA
- db 5, RATTATA
- db 3, RATTATA
- db 6, RATTATA
- db 5, RATTATA
- db 5, RATTATA
- ; day
- db 3, RATTATA
- db 4, RATTATA
- db 5, RATTATA
- db 3, RATTATA
- db 6, RATTATA
- db 5, RATTATA
- db 5, RATTATA
- ; nite
- db 3, GASTLY
- db 4, GASTLY
- db 5, GASTLY
- db 3, RATTATA
- db 6, GASTLY
- db 5, RATTATA
- db 5, RATTATA
+ ; morn
+ ; %, species, min, max
+ db 30, RATTATA, 3, 6
+ db 30, RATTATA, 3, 6
+ db 20, RATTATA, 3, 6
+ db 10, RATTATA, 3, 6
+ db 5, RATTATA, 3, 6
+ db 4, RATTATA, 3, 6
+ db 1, RATTATA, 3, 6
+
+ ; day
+ ; %, species, min, max
+ db 30, RATTATA, 3, 6
+ db 30, RATTATA, 3, 6
+ db 20, RATTATA, 3, 6
+ db 10, RATTATA, 3, 6
+ db 5, RATTATA, 3, 6
+ db 4, RATTATA, 3, 6
+ db 1, RATTATA, 3, 6
+
+ ; nite
+ ; %, species, min, max
+ db 30, GASTLY, 3, 6
+ db 30, GASTLY, 3, 6
+ db 20, GASTLY, 3, 6
+ db 10, RATTATA, 3, 5
+ db 5, GASTLY, 3, 6
+ db 4, RATTATA, 3, 5
+ db 1, RATTATA, 3, 5
end_grass_wildmons
```
**Note:** You can change the percentages freely, as long as they add 100.
You have five places to edit: [data/wild/johto_grass.asm](../blob/master/data/wild/johto_grass.asm), [data/wild/kanto_grass.asm](./blob/master/data/wild/kanto_grass.asm), [data/wild/johto_water.asm](../blob/master/data/wild/johto_water.asm), [data/wild/kanto_water.asm](../blob/master/data/wild/kanto_water.asm) and [data/wild/swarm_grass.asm](../blob/master/data/wild/swarm_grass.asm). _Have fun._
### 2.3. Fix space issues
Congrats! You edited each encounter table and it probably took you some hours, but you have a new problem: one of your banks is now full and you can't assemble your ROM. An easy way to fix this is to put [engine/overworld/wildmons.asm](../blob/master/engine/overworld/wildmons.asm) in a new section. Go to [main.asm](../blob/master/main.asm):
```diff
...
SECTION "bankA", ROMX
INCLUDE "engine/link/link.asm"
-INCLUDE "engine/overworld/wildmons.asm"
INCLUDE "engine/battle/link_result.asm"
...
+SECTION "Overworld Wildmon Data", ROMX
+
+INCLUDE "engine/overworld/wildmons.asm"
```
Now RGBDS (our development tool) will take care of it and put the new section where there's free space.
## 3. Method #3: Hijack the surf variance code
Method #1 made use of the surf variance code in a more complex way, allowing for more finer control over the tables, and method #2 went even further, making the code similar to how the Bug Contest handles wild encounters. The following method is a lot simpler to implement, and has a similar effect to method #1, but lacks that deeper control of the encounter levels.
Head on over to `ChooseWildEncounter` in [engine/overworld/wildmons.asm](https://github.com/pret/pokecrystal/blob/master/engine/overworld.wildmons.asm):
```
; If the Pokemon is encountered by surfing, we need to give the levels some variety.
call CheckOnWater
jr nz, .ok
; Check if we buff the wild mon, and by how much.
call Random
cp 35 percent
jr c, .ok
inc b
cp 65 percent
jr c, .ok
inc b
cp 85 percent
jr c, .ok
inc b
cp 95 percent
jr c, .ok
inc b
```
Let's break it down.
As you can see, there _is_ level variance code right in the original game! It calls `CheckOnWater` to see if player is currently surfing and if so, falls through to call `Random`, which will randomly choose one of these percentages, and then add that percentage to `b`, adding the rolled variance level to the level of the currently encountered surfing Pokémon inside `b`.
The slots for surfing Pokémon are the minimum level for that slot, as is the logic for any encounter table slot. Through the use of `Random`, however, surfing Pokémon have a chance to be encountered at levels higher than their set level in the encounter slot.
See below:
* +0: 35% chance
* +1: 30%
* +2: 20%
* +3: 10%
* +4: 5%
These probabilities are approximate, as an 8-bit register is limited in the range of values it can take!
Well, now we know what exactly the game does when you encounter a surfing Pokémon...
What happens if we just... remove that `CheckOnWater`?
```diff
-; If the Pokemon is encountered by surfing, we need to give the levels some variety.
- call CheckOnWater
- jr nz, .ok
; Check if we buff the wild mon, and by how much.
call Random
cp 35 percent
jr c, .ok
inc b
cp 65 percent
jr c, .ok
inc b
cp 85 percent
jr c, .ok
inc b
cp 95 percent
jr c, .ok
inc b
```
Wow! Now any encountered wild Pokémon can vary up to 0-4 levels, in grass, in caves, in water... anywhere!
That is all you need to do to achieve this functionality, but it also has some caveats. Anywhere does mean anywhere...
Including Legendary Pokémon.
What if you don't want special wild encounters to have level variance?
Just load `wBattleType` into `a`, and check for the specific special encounter type before the first `Random` call.
```diff
+; If the Pokemon is encountered in a special way, skip randomizing level.
+ ld a, [wBattleType]
+ cp BATTLETYPE_SUICUNE
+ jr z, .ok
; Check if we buff the wild mon, and by how much.
call Random
cp 35 percent
jr c, .ok
inc b
cp 65 percent
jr c, .ok
inc b
cp 85 percent
jr c, .ok
inc b
cp 95 percent
jr c, .ok
inc b
```
Now when the player encounters a wildmon, it will now check the `wBattleType` and see if it is `BATTLETYPE_SUICUNE`. If it is `BATTLETYPE_SUICUNE`, then it will skip the random level variance and jump down to `.ok`.
You can add multiple checks for different encounter types, or even specify special encounter types that DO make use of the level variance.
Armed with this new level variance technology, entering an unedited Route 29 during the day and rummaging through the grass, you could now encounter:
* Rattata: Levels 2-6
* Sentret: Levels 2-7
* Pidgey: Levels 2-7
* Hoppip: Levels 3-7
Whichever way you decide to implement level variance, it makes for a much more diverse experience exploring the overworld.
|