diff options
-rw-r--r-- | Make-wild-Pokémon-encounter-levels-vary.md | 378 |
1 files changed, 349 insertions, 29 deletions
diff --git a/Make-wild-Pokémon-encounter-levels-vary.md b/Make-wild-Pokémon-encounter-levels-vary.md index e7b5f4a..c235b12 100644 --- a/Make-wild-Pokémon-encounter-levels-vary.md +++ b/Make-wild-Pokémon-encounter-levels-vary.md @@ -1,41 +1,38 @@ -Pokémon Crystal, and the rest of the Gen 2 games use a slot-based encounter table, -rolling against the current area's table to generate a wildmon for the player to fight. +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 Level 3 Rattata, and a Level 4 Rattata require two slots) +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 for more unique species!), via two different methods: -* Custom encounter table with level probabilities +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: -or +1. Custom encounter table with level variation +2. Custom probabilities and level ranges for each encounter table. +3. Hijacking the surf variance code -* 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. -Each has their benefits; The former allows more fine-tuned control over the level variance, albeit a more invested change, -while the latter is much simpler to implement across the game, while 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. +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. [Understanding Bug Catching Contest Encounter Code](#1-understanding-bug-catching-contest-encounter-code) +1. [Method #1: Understanding Bug Catching Contest Encounter Code](#1-understanding-bug-catching-contest-encounter-code) 2. [Add max levels in probabilities](#2-add-max-levels-in-probabilities) 3. [Change wild encounter algorithm](#3-change-wild-encounter-algorithm) 4. [Other notes for Method 1](#4-Other-notes-for-Method-1) -5. [Method #2: Hijack the surf variance code](#5-Method-2-hijack-the-surf-variance-code) +5. [Method #2: Custom probabilities and level ranges for each encounter table](#5-method-2-custom-probabilities-and-level-ranges-for-each-encounter-table) +6. [Adjust data related to wildata constants](#6-adjust-data-related-to-wildata-constants) +7. [Edit the encounter tables](t#7-edit-the-encounter-tables) +8. [Fix space issues](#8-fix-space-issues) +9. [Method #3: Hijack the surf variance code](#5-Method-2-hijack-the-surf-variance-code) ## 1. Understanding Bug Catching Contest Encounter Code -First, we look at a snippet used in [events.asm](https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm) because we will use a portion of the code to replace a portion of wild 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. @@ -90,9 +87,9 @@ ChooseWildEncounter_BugContest:: ret ``` -This code is used to randomize wild encounters in the Bug Catching Contest. We then look at [bug_contest_mons.asm](https://github.com/pret/pokecrystal/blob/master/data/wild/bug_contest_mons.asm). +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 @@ -108,7 +105,7 @@ ContestMons: 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 (events.asm)[https://github.com/pret/pokecrystal/blob/master/engine/overworld/events.asm] to randomize regular wild encounters. +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. ## 2. Add max levels in probabilities @@ -116,6 +113,7 @@ We can see that there's a minimum and maximum level for every wild Pokémon. The 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 @@ -134,15 +132,15 @@ Edit [data/wild/probabilities.asm](https://github.com/pret/pokecrystal/blob/mast + db 4 ``` -The `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 mon is Lv. 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. ## 3. Change wild encounter algorithm -Finally, we change the wild encounter algorithm in [engine/overworld/wildmons.asm](https://github.com/pret/pokecrystal/blob/master/engine/overworld.wildmons.asm): +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 @@ -214,18 +212,340 @@ And there you have it! Wild encounters have been randomized. ## 4. Other notes for Method 1 -Take note that the slots in `MaxLevelGrass` and `MaxLevelWater` in [data/wild/probabilities.asm](https://github.com/pret/pokecrystal/blob/master/data/wild/probabilities.asm) is the same as the probability slots in probabilities.asm. Also, each slot is tied to the wild Pokémon slots in [data/wild](https://github.com/pret/pokecrystal/blob/master/data/wild)/<johto_grass/johto_water/kanto_grass/kanto_water>.asm. 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 slots in `MaxLevelGrass` and/or `MaxLevelWater`. +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 need to use 19 slots because each level is placed in one slot. So for example, you need to use 3 slots for Abra: Lv. 13 Abra, Lv. 14 Abra, and Lv. 15 Abra. 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! +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! + + +## 5. 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] + + 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 + ++.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. + +## 6. 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 + (1 + NUM_GRASSMON * 2) * 3 +-WATER_WILDDATA_LENGTH EQU 2 + (1 + NUM_WATERMON * 2) * 1 ++GRASS_WILDDATA_LENGTH EQU 2 + 3 + (NUM_GRASSMON * 4) * 3 ++WATER_WILDDATA_LENGTH EQU 2 + 1 + (NUM_WATERMON * 4) * 1 +``` + +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. + + +## 7. 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 +``` + +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._ + +## 8. 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. -## 5. Method #2: Hijack the surf variance code +## 9. Method #3: Hijack the surf variance code -The former tutorial made use of the surf variance code in a more complex way, allowing for more finer control over the tables. This method is a lot simpler to implement, and has a similar effect, but lacks that deeper control of the encounter levels. +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): |