Improving Fishing Rod Encounters - pret/pokered GitHub Wiki
Introduction
As you probably know, fishing in the vanilla games is a largely fruitless endeavor, to the point that many experienced players ignore it almost entirely. The purpose of this tutorial is to show how to make fishing encounters far more viable for teambuilding during the average playthrough. First, for those who are curious, we'll go through exactly what makes the rods so awful. If that doesn't interest you, feel free to skip to the following section to get straight to the part where we update and improve the rods.
This is a fairly long process, so let's begin!
Why Are the Rods So Terrible?
To start things off, let's look at the code for the Old Rod. This can be found in engine\items\item_effects.asm; in an unmodified file it should be line 1826. The code looks like this:
ItemUseOldRod:
call FishingInit
jp c, ItemUseNotTime
lb bc, 5, MAGIKARP
ld a, $1 ; set bite
jr RodResponse
Yep, that's it. The Old Rod is hard-coded to hook ONLY level 5 Magikarp. Terrible.
Next, let's take a peek at the code for the Good Rod (which immediately follows in the file, starting at line 1833):
ItemUseGoodRod:
call FishingInit
jp c, ItemUseNotTime
.RandomLoop
call Random
srl a
jr c, .SetBite
and %11
cp 2
jr nc, .RandomLoop
; choose which monster appears
ld hl, GoodRodMons
add a
ld c, a
ld b, 0
add hl, bc
ld b, [hl]
inc hl
ld c, [hl]
and a
.SetBite
ld a, 0
rla
xor 1
jr RodResponse
INCLUDE "data/wild/good_rod.asm"
Looks better, right? Well, if we head over to data/wild/good_rod.asm, we'll find that the Good Rod has a 50/50 chance of hooking either Goldeen or Poliwag at level 10, regardless of fishing location. Not quite as bad as the Old Rod, but still laughable.
Now let's move down to the Super Rod. The code for this is written in two chunks. The first chunk comes directly after the Good Rod code, starting on line 1861:
ItemUseSuperRod:
call FishingInit
jp c, ItemUseNotTime
call ReadSuperRodData
ld a, e
RodResponse:
ld [wRodResponse], a
dec a ; is there a bite?
jr nz, .next
; if yes, store level and species data
ld a, 1
ld [wMoveMissed], a
ld a, b ; level
ld [wCurEnemyLevel], a
ld a, c ; species
ld [wCurOpponent], a
.next
ld hl, wWalkBikeSurfState
ld a, [hl] ; store the value in a
push af
push hl
ld [hl], 0
farcall FishingAnim
pop hl
pop af
ld [hl], a
ret
The second chunk is actually near the very end of the file, starting at line 2843:
ReadSuperRodData:
; return e = 2 if no fish on this map
; return e = 1 if a bite, bc = level,species
; return e = 0 if no bite
ld a, [wCurMap]
ld de, 3 ; each fishing group is three bytes wide
ld hl, SuperRodData
call IsInArray
jr c, .ReadFishingGroup
ld e, $2 ; $2 if no fishing groups found
ret
.ReadFishingGroup
; hl points to the fishing group entry in the index
inc hl ; skip map id
; read fishing group address
ld a, [hli]
ld h, [hl]
ld l, a
ld b, [hl] ; how many mons in group
inc hl ; point to data
ld e, $0 ; no bite yet
.RandomLoop
call Random
srl a
ret c ; 50% chance of no battle
and %11 ; 2-bit random number
cp b
jr nc, .RandomLoop ; if a is greater than the number of mons, regenerate
; get the mon
add a
ld c, a
ld b, $0
add hl, bc
ld b, [hl] ; level
inc hl
ld c, [hl] ; species
ld e, $1 ; $1 if there's a bite
ret
INCLUDE "data/wild/super_rod.asm"
Now THIS looks promising! If you open data/wild/super_rod.asm you'll find that Super Rod encounters are structured as follows:
- There are 10 distinct Fishing Groups
- Each area of the game with water tiles is assigned one of the groups
- Each Fishing group is assigned up to four Pokemon encounter slots
If you look through the encounter groups at data/wild/super_rod.asm, you'll find that they're quite lackluster, but this still provides a great template to build on. Now let's get to work improving all the rods!
Making All the Rods Function Like the Super Rod, Part 1:
If you didn't read the preceding section, go ahead and open engine\items\item_effects.asm. We'll start with a little housekeeping in the ItemUsePtrTable, right at the beginning of the file. We're going to replace each individual UseRod routine with a single generic one:
...
dw UnusableItem ; EXP_ALL
- dw ItemUseOldRod ; OLD_ROD
- dw ItemUseGoodRod ; GOOD_ROD
- dw ItemUseSuperRod ; SUPER_ROD
+ dw ItemUseRod ; OLD_ROD
+ dw ItemUseRod ; GOOD_ROD
+ dw ItemUseRod ; SUPER_ROD
dw ItemUsePPUp ; PP_UP
...
Next, we'll eliminate the ItemUseOldRod and ItemUseGoodRod routines and repurpose ItemUseSuperRod to be the generic, all-purpose routine:
-ItemUseOldRod:
- call FishingInit
- jp c, ItemUseNotTime
- lb bc, 5, MAGIKARP
- ld a, $1 ; set bite
- jr RodResponse
-
-ItemUseGoodRod:
- call FishingInit
- jp c, ItemUseNotTime
-.RandomLoop
- call Random
- srl a
- jr c, .SetBite
- and %11
- cp 2
- jr nc, .RandomLoop
- ; choose which monster appears
- ld hl, GoodRodMons
- add a
- ld c, a
- ld b, 0
- add hl, bc
- ld b, [hl]
- inc hl
- ld c, [hl]
- and a
-.SetBite
- ld a, 0
- rla
- xor 1
- jr RodResponse
-
-INCLUDE "data/wild/good_rod.asm"
-
-ItemUseSuperRod:
+ItemUseRod:
call FishingInit
jp c, ItemUseNotTime
- call ReadSuperRodData
+ call ReadRodData
ld a, e
RodResponse:
...
Note that we deleted the line INCLUDE "data/wild/good_rod.asm". This is a necessary bit of code and will be restored in a different place shortly.
Similarly, call ReadSuperRodData has been replaced with a generic call ReadRodData. This will be defined and addressed in the following section.
Making All the Rods Function Like the Super Rod, Part 2:
We remain within the same file here (engine\item\item_effects.asm), but move down near the very end of the file. Around line 2817 you'll find the routine ReadSuperRodData:. Like earlier, we'll be repurposing the Super Rod code to function as the generic code that covers all rod encounters. We'll start by creating a list of checks so that the game knows which rod we're using:
-ReadSuperRodData:
+ReadRodData:
; return e = 2 if no fish on this map
; return e = 1 if a bite, bc = level,species
; return e = 0 if no bite
+ ld a, [wCurItem]
+ cp OLD_ROD
+ ld hl, OldRodData
+ jr z, .FindFishingGroup
+ cp GOOD_ROD
+ ld hl, GoodRodData
+ jr z, .FindFishingGroup
+ ld hl, SuperRodData
+
+.FindFishingGroup
ld a, [wCurMap]
ld de, 3 ; each fishing group is three bytes wide
- ld hl, SuperRodData
call IsInArray
jr c, .ReadFishingGroup
ld e, $2 ; $2 if no fishing groups found
ret
.ReadFishingGroup
...
Now we need to INCLUDE the encounter data for the Old and Good Rods. Move down past the .RandomLoop section and simply insert these lines:
...
ld c, [hl] ; species
ld e, $1 ; $1 if there's a bite
ret
+INCLUDE "data/wild/old_rod.asm"
+INCLUDE "data/wild/good_rod.asm"
INCLUDE "data/wild/super_rod.asm"
; reloads map view and processes sprite data
; for items that cause the overworld to be displayed
ItemUseReloadOverworldData:
...
One problem remains: data/wild/old_rod.asm doesn't exist! Fear not: we'll fix that in the next section.
Making All the Rods Function Like the Super Rod, Part 3:
Still with me? Good... here's the part where we restore full functionality to all the rods. Go open the following two files:
data\wild\super_rod.asmdata\wild\good_rod.asm
The simplest thing to do here is to highlight all the text in data\wild\super_rod.asm and copy it. Move over to data\wild\good_rod.asm, highlight all the text, and paste over it. Finally, change all occurrences of the word 'Super' to 'Good' (mind your capital and lower case letters!). Your Good Rod file should now look like this (presented in its entirety):
; good rod encounters
GoodRodData:
; map, fishing group
dbw PALLET_TOWN, .Group1
dbw VIRIDIAN_CITY, .Group1
dbw CERULEAN_CITY, .Group3
dbw VERMILION_CITY, .Group4
dbw CELADON_CITY, .Group5
dbw FUCHSIA_CITY, .Group10
dbw CINNABAR_ISLAND, .Group8
dbw ROUTE_4, .Group3
dbw ROUTE_6, .Group4
dbw ROUTE_10, .Group5
dbw ROUTE_11, .Group4
dbw ROUTE_12, .Group7
dbw ROUTE_13, .Group7
dbw ROUTE_17, .Group7
dbw ROUTE_18, .Group7
dbw ROUTE_19, .Group8
dbw ROUTE_20, .Group8
dbw ROUTE_21, .Group8
dbw ROUTE_22, .Group2
dbw ROUTE_23, .Group9
dbw ROUTE_24, .Group3
dbw ROUTE_25, .Group3
dbw CERULEAN_GYM, .Group3
dbw VERMILION_DOCK, .Group4
dbw SEAFOAM_ISLANDS_B3F, .Group8
dbw SEAFOAM_ISLANDS_B4F, .Group8
dbw SAFARI_ZONE_EAST, .Group6
dbw SAFARI_ZONE_NORTH, .Group6
dbw SAFARI_ZONE_WEST, .Group6
dbw SAFARI_ZONE_CENTER, .Group6
dbw CERULEAN_CAVE_2F, .Group9
dbw CERULEAN_CAVE_B1F, .Group9
dbw CERULEAN_CAVE_1F, .Group9
db -1 ; end
; fishing groups
; number of monsters, followed by level/monster pairs
.Group1:
db 2
db 15, TENTACOOL
db 15, POLIWAG
.Group2:
db 2
db 15, GOLDEEN
db 15, POLIWAG
.Group3:
db 3
db 15, PSYDUCK
db 15, GOLDEEN
db 15, KRABBY
.Group4:
db 2
db 15, KRABBY
db 15, SHELLDER
.Group5:
db 2
db 23, POLIWHIRL
db 15, SLOWPOKE
.Group6:
db 4
db 15, DRATINI
db 15, KRABBY
db 15, PSYDUCK
db 15, SLOWPOKE
.Group7:
db 4
db 5, TENTACOOL
db 15, KRABBY
db 15, GOLDEEN
db 15, MAGIKARP
.Group8:
db 4
db 15, STARYU
db 15, HORSEA
db 15, SHELLDER
db 15, GOLDEEN
.Group9:
db 4
db 23, SLOWBRO
db 23, SEAKING
db 23, KINGLER
db 23, SEADRA
.Group10:
db 4
db 23, SEAKING
db 15, KRABBY
db 15, GOLDEEN
db 15, MAGIKARP
Next, we'll open a fresh new file. Once again, paste all the text from data\wild\super_rod.asm, then edit every occurrence of the word 'Super' to say 'Old' instead. Your file should look like this:
; old rod encounters
OldRodData:
; map, fishing group
dbw PALLET_TOWN, .Group1
dbw VIRIDIAN_CITY, .Group1
dbw CERULEAN_CITY, .Group3
dbw VERMILION_CITY, .Group4
dbw CELADON_CITY, .Group5
dbw FUCHSIA_CITY, .Group10
dbw CINNABAR_ISLAND, .Group8
dbw ROUTE_4, .Group3
dbw ROUTE_6, .Group4
dbw ROUTE_10, .Group5
dbw ROUTE_11, .Group4
dbw ROUTE_12, .Group7
dbw ROUTE_13, .Group7
dbw ROUTE_17, .Group7
dbw ROUTE_18, .Group7
dbw ROUTE_19, .Group8
dbw ROUTE_20, .Group8
dbw ROUTE_21, .Group8
dbw ROUTE_22, .Group2
dbw ROUTE_23, .Group9
dbw ROUTE_24, .Group3
dbw ROUTE_25, .Group3
dbw CERULEAN_GYM, .Group3
dbw VERMILION_DOCK, .Group4
dbw SEAFOAM_ISLANDS_B3F, .Group8
dbw SEAFOAM_ISLANDS_B4F, .Group8
dbw SAFARI_ZONE_EAST, .Group6
dbw SAFARI_ZONE_NORTH, .Group6
dbw SAFARI_ZONE_WEST, .Group6
dbw SAFARI_ZONE_CENTER, .Group6
dbw CERULEAN_CAVE_2F, .Group9
dbw CERULEAN_CAVE_B1F, .Group9
dbw CERULEAN_CAVE_1F, .Group9
db -1 ; end
; fishing groups
; number of monsters, followed by level/monster pairs
.Group1:
db 2
db 15, TENTACOOL
db 15, POLIWAG
.Group2:
db 2
db 15, GOLDEEN
db 15, POLIWAG
.Group3:
db 3
db 15, PSYDUCK
db 15, GOLDEEN
db 15, KRABBY
.Group4:
db 2
db 15, KRABBY
db 15, SHELLDER
.Group5:
db 2
db 23, POLIWHIRL
db 15, SLOWPOKE
.Group6:
db 4
db 15, DRATINI
db 15, KRABBY
db 15, PSYDUCK
db 15, SLOWPOKE
.Group7:
db 4
db 5, TENTACOOL
db 15, KRABBY
db 15, GOLDEEN
db 15, MAGIKARP
.Group8:
db 4
db 15, STARYU
db 15, HORSEA
db 15, SHELLDER
db 15, GOLDEEN
.Group9:
db 4
db 23, SLOWBRO
db 23, SEAKING
db 23, KINGLER
db 23, SEADRA
.Group10:
db 4
db 23, SEAKING
db 15, KRABBY
db 15, GOLDEEN
db 15, MAGIKARP
Now save this file with the file name old_rod.asm. Make sure it has the .asm file extension, and save it in the same folder alongside the other two rods' encounter data.
Congratulations! All three fishing rods are now fully functional!
One small snag you may have picked up on... all the rods now fish up the EXACT same sets of encounters. In the final section we'll cover how to modify the encounters so you can customize them to your liking! For now, though, a couple of QoL things that might interest you...
Modifying Rod Success Rates and Expanding Encounter Tables
Now let's talk about how we can change the odds of a rod hooking a Pokemon. Back over in engine\items\item_effects.asm, find your newly modified ReadRodData routine and move down to the .RandomLoop section, which starts like this:
.RandomLoop
call Random
srl a
ret c ; 50% chance of no battle
...
Basically, the game generates a random number 0-255, then (by way of the srl a instruction) checks if that random number is even or odd. If it's odd, the rod fails to hook a Pokemon. This means that only 50% of potential results will produce a battle. If you want to make it a 100% chance, simply try this:
.RandomLoop
call Random
- srl a
- ret c ; 50% chance of no battle
...
That's it! With those two lines gone, your rods will never fail again.
Now, look at the very next bit of code:
...
and %11 ; 2-bit random number
cp b
jr nc, .RandomLoop ; if a is greater than the number of mons, regenerate
...
The and %11 instruction essentially limits the size of your rod encounter tables to only 4 Mons. While I have not personally tested an extensive variety of changes, I HAVE found that this works:
- and %11 ; 2-bit random number
+ and $f ; 4-bit random number
cp b
jr nc, .RandomLoop ; if a is greater than the number of mons, regenerate
...
This allows your rod encounter tables (detailed in the below sections) to hold up to SIXTEEN Mons. I tested this particular change VERY extensively...
(I recorded the encounter rates while having a full table of 16 Mons over the course of 5,000 fishing encounters... YOU'RE WELCOME!)
Not only did all 16 potential Mons appear, but they did so with practically equal chances. I also did some lighter testing with <16 Mons in the table, and all those seem to appear at equal rates as well, so it seems to work fine.
Now finally, we get to adjust exactly what we can fish up!
Customizing the Rod Encounters
Rest assured; all the heavy stuff is now behind us. All the remaining work is very simple, if perhaps tedious. Let's look at data\wild\old_rod.asm to get started. We can see that the encounter data is structured as follows (repeated from the explanation section at the beginning):
- There are 10 distinct Fishing Groups
- Each area of the game with water tiles is assigned one of the groups
- Each Fishing group is assigned up to four Pokemon encounter slots
We'll start by messing with the encounters in Group 1. Each entry contains a number (for level) and name (for species). Try making the following changes:
.Group1:
db 2
- db 15, TENTACOOL
- db 15, POLIWAG
+ db 10, MAGIKARP
+ db 10, GOLDEEN
Looking at the top section of this file, where the maps and fishing groups are listed, we can see that Group 1 is associated with the water in Pallet Town and Viridian City. This means that the Old Rod will catch either a level 10 Magikarp or Goldeen in either of these locations. Prior to this edit you would catch a level 15 Tentacool or Poliwag instead. Go make your game and give it a try.
But what if we want to expand the list of Pokemon in a specific group? Sticking with Group 1, we'll edit the line that says db 2. The 2, as you may guess, tells us how many group members there are. Let's make the following edits:
.Group1:
- db 2
+ db 4 ; with the above change, you can use a no. =<16, just make sure your no. of group members matches
db 10, MAGIKARP
db 10, GOLDEEN
+ db 10, HORSEA
+ db 10, PSYDUCK
Now, once you make the game, your fishing encounters in Pallet Town and Viridian City will include Horsea and Psyduck alongside the Magikarp and Goldeen that were already there. With this knowledge you can now edit each group however you desire.
Changing an area's Fishing Group assignment is extremely simple. Try the following edit:
...
dbw PALLET_TOWN, .Group1
dbw VIRIDIAN_CITY, .Group1
dbw CERULEAN_CITY, .Group3
- dbw VERMILION_CITY, .Group4
+ dbw VERMILION_CITY, .Group1
dbw CELADON_CITY, .Group5
dbw FUCHSIA_CITY, .Group10
...
With this change, Vermilion City will now have identical fishing encounters to Pallet Town and Viridian City, since Group 1 is assigned to all three locations.
Finally, we'll wrap things up by creating an entirely new fishing group. Again, this process is actually really easy. We're at the end of the tutorial, so let's try something silly and fun with these edits. Go to the very end of the file and insert your new group:
...
.Group10:
db 4
db 23, SEAKING
db 15, KRABBY
db 15, GOLDEEN
db 15, MAGIKARP
+.Group11:
+ db 4
+ db 2, RHYDON
+ db 2, DITTO
+ db 2, DIGLETT
+ db 2, MAGMAR
Now scroll up and assign your new group to a location... let's say, Fuchsia City. Why not?
...
dbw CELADON_CITY, .Group6
- dbw FUCHSIA_CITY, .Group6
+ dbw FUCHSIA_CITY, .Group11
dbw CINNABAR_ISLAND, .Group8
...
With this edit, you can use your Old Rod in the Fuchsia Fishing Guru's backyard pond to hook Rhydon, Ditto, Diglett, or Magmar... all at level 2!
Note: I have not found whether there is a maximum number of fishing groups you can create. Given that there are only 32 fishable areas in the game (There are 33 listed locations, but Cerulean Cave 2F has no water...), you could perhaps create a customized list for each area, for each rod, if you're industrious enough.
With that, we've FINALLY reached the end of the tutorial. Best of luck to you as you edit all these encounters; it's certainly a commitment.
Thanks for sticking around through this long process, and I hope it's helpful to you.