-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Improving Fishing Rod Encounters
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!
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 RodResponseYep, 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
retThe 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!
If you didn't read the preceding section, go ahead and open engine\items\item_effects.asm. We'll start by modifying the Old Rod code to match the functionality of the Super Rod code. The Old Rod code begins on line 1833 (in an unmodified file); you can also use your editor's Find function and search for "ItemUseOldRod". Once you find the spot, make the following changes:
ItemUseOldRod:
call FishingInit
jp c, ItemUseNotTime
- lb bc, 5, MAGIKARP
- ld a, $1 ; set bite
+ call ReadOldRodData
+ ld a, e
jr RodResponseNote: this change will render the game temporarily "unmakeable" due to the line call ReadOldRodData, which references something that doesn't exist... yet. We'll fix this in Part 2 below. For now, let's do the same update to the Good Rod code, located immediately after the stuff we just changed:
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
+ call ReadGoodRodData
+ ld a, e
jr RodResponse
-
-INCLUDE "data/wild/good_rod.asm"Similarly, call ReadGoodRodData causes issues that will be solved shortly. But for now, we've reached the end of Part 1! There is no need to modify the Super Rod data; you may notice that the new code for the Old and Good Rod is actually modeled based directly on the Super Rod code.
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:. We'll be inserting the following code directly preceding it:
...
INCLUDE "data/tilesets/water_tilesets.asm"
+ReadOldRodData:
+; 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, OldRodData
+ 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/old_rod.asm"
+
+ReadGoodRodData:
+; 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, GoodRodData
+ 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/good_rod.asm"
+
ReadSuperRodData:
...Now we've eliminated the call issues, but there's one final problem to address: INCLUDE "data/wild/old_rod.asm" breaks things because we're trying to include a file that doesn't exist... yet. Fortunately, we'll fix that in the next section.
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, MAGIKARPNext, 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, MAGIKARPNow 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 next (and final!) section we'll cover how to modify the encounters so you can customize them to your liking!
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, GOLDEENLooking 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
db 10, MAGIKARP
db 10, GOLDEEN
+ db 10, HORSEA
+ db 10, PSYDUCKNow, 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.
Note: To the best of my knowledge, each group has a maximum size of 4. Higher numbers and longer lists will not create any errors when you make the game, but group entries beyond the 4th Pokemon just seem to be ignored entirely. They'll never be encountered despite being listed.
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, MAGMARNow 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.