-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Overhaul Fishing Rod Encounters
This is my first tutorial submission. I am not familiar with assembly code outside of rom hacking, and I'm only familiar with rom hacking thanks to this communities other pokered and pokecrystal tutorials. What follows was created out of trail, error, and help from chatgpt. That said, this is perfectly functional, but there are likely optimizations that can made.
In any case, my goal was to wholly overhaul how the game generates fishing encounters by mirroring the logic already in place for Grass and Surf encounters.
I started with Lotsob's tutorial below, which greatly improves on the fishing system by copying the routines and encounter groups for the Super Rod into the Old Rod and Good Rod effects, allowing for more versatile fishing encounters. It also includes an excellent write up on the current fishing system's shortcomings. I suggest giving this a read before proceeding.
https://github.com/pret/pokered/wiki/Improving-Fishing-Rod-Encounters
That solution is extremely reasonable, but it leaves a few very minor things to be desired:
- We cannot set unique encounter rates per map. Since the percent chance of hooking a bite is determined by the rod, we couldn't make it so Viridian City has a 75% chance and Cerulean Cave has a 25% chance of hooking a fish. Admittedly pretty unnecessary, but still. Grass and Surf encounters have encounter rates defined per map, why shouldn't fish?
- We cannot set unique probabilities per encounter slot. The fishing groups all have equal distribution. If you have 4 FishMons per group, then each are 25% rare. Now, you can double up Pokemon in multiple encounter slots to create rarity, but you're still bound by the equal distribution. This means no Pokemon can be rarer than 6.25% (1/16th) if you put 16 Pokemon in a given fish group. If we want a 1% rare Omanyte lurking around, we're out of luck.
The changes below will allow Fishing to use the same WildMonEncounterSlotChances table in probabilities.asm and call unique encounter data per map. This is probably overkill for most purposes, but I wanted to see if it was possible, and it's nice to have the encounter data match grass and water cleanly instead of using an entirely separate fishing groups file.
Let's get started.
First, we'll need to create wFishRate and wFishMons in wram.asm to match the existing Grass and Water code.
; This union spans 293 bytes.
UNION
wGrassRate:: db
wGrassMons:: ds WILDDATA_LENGTH - 1
-
- ds 8
wWaterRate:: db
wWaterMons:: ds WILDDATA_LENGTH - 1
+
+wFishRate:: db
+wFishMons:: ds WILDDATA_LENGTH - 1
NEXTU
; linked game's trainer name
Second, we'll need to create def_fish_wildmons and end_fish_wildmons Macros in asserts.asm to match the existing Grass and Water code.
MACRO? def_water_wildmons
;\1: encounter rate
DEF CURRENT_WATER_WILDMONS_RATE = \1
REDEF CURRENT_WATER_WILDMONS_LABEL EQUS "._def_water_wildmons_\1"
{CURRENT_WATER_WILDMONS_LABEL}:
db \1
ENDM
MACRO? end_water_wildmons
DEF x = @ - {CURRENT_WATER_WILDMONS_LABEL}
IF CURRENT_WATER_WILDMONS_RATE == 0
ASSERT 1 == x, \
"def_water_wildmons {d:CURRENT_WATER_WILDMONS_RATE}: expected 1 byte, got {d:x}"
ELSE
ASSERT WILDDATA_LENGTH == x, \
"def_water_wildmons {d:CURRENT_WATER_WILDMONS_RATE}: expected {d:WILDDATA_LENGTH} bytes, got {d:x}"
ENDC
ENDM
+MACRO? def_fish_wildmons
+;\1: encounter rate
+ DEF CURRENT_FISH_WILDMONS_RATE = \1
+ REDEF CURRENT_FISH_WILDMONS_LABEL EQUS "._def_fish_wildmons_\1"
+{CURRENT_FISH_WILDMONS_LABEL}:
+ db \1
+ENDM
+
+MACRO? end_fish_wildmons
+ DEF x = @ - {CURRENT_FISH_WILDMONS_LABEL}
+ IF CURRENT_FISH_WILDMONS_RATE == 0
+ ASSERT 1 == x, \
+ "def_fish_wildmons {d:CURRENT_FISH_WILDMONS_RATE}: expected 1 byte, got {d:x}"
+ ELSE
+ ASSERT WILDDATA_LENGTH == x, \
+ "def_fish_wildmons {d:CURRENT_FISH_WILDMONS_RATE}: expected {d:WILDDATA_LENGTH} bytes, got {d:x}"
+ ENDC
+ENDM
Rewrite LoadWildData in wild_mons.asm to handle our new Fish data. The old routine would check for Grass encounters, then skip to Water encounters, then close. Now, we're including one more check for Fish using the same logic.
LoadWildData::
ld hl, WildDataPointers
ld a, [wCurMap]
; get wild data for current map
ld c, a
ld b, 0
add hl, bc
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a ; hl now points to wild data for current map
ld a, [hli]
ld [wGrassRate], a
and a
jr z, .NoGrassData ; if no grass data, skip to surfing data
push hl
ld de, wGrassMons ; otherwise, load grass data
ld bc, WILDDATA_LENGTH - 1
call CopyData
pop hl
ld bc, WILDDATA_LENGTH - 1
add hl, bc
.NoGrassData
ld a, [hli]
ld [wWaterRate], a
and a
- ret z ; if no water data, we're done
+ jr z, .NoWaterData ; if no water data, skip to fishing data
+ push hl
ld de, wWaterMons ; otherwise, load surfing data
ld bc, WILDDATA_LENGTH - 1
- jp CopyData
+ call CopyData
+ pop hl
+ ld bc, WILDDATA_LENGTH - 1
+ add hl, bc
+.NoWaterData
+ ld a, [hli]
+ ld [wFishRate], a
+ and a
+ ret z ; if no fish data, we're done
+ ld de, wFishMons ; otherwise, load fish data
+ ld bc, WILDDATA_LENGTH - 1
+ jp CopyData
INCLUDE "data/wild/grass_water.asm"Here's where things get tedious. Instead of using one file super_rod.asm we are going to put fish data onto EVERY SINGLE MAP file in data > wild > maps. This also means we need to create a few new map files for areas (towns) that want fish encounters but that didn't previously have a map file in the wild folder, since no grass or surf encounters occur there.
Let's use Viridian City as an example. We need to create a pointer and include the file in grass_water.asm.
WildDataPointers:
table_width 2
dw NothingWildMons ; PALLET_TOWN
- dw NothingWildMons ; VIRIDIAN_CITY
+ dw ViridianCityWildMons ; VIRIDIAN_CITY
dw NothingWildMons ; PEWTER_CITY
. . .
INCLUDE "data/wild/maps/nothing.asm"
INCLUDE "data/wild/maps/Route1.asm"
+INCLUDE "data/wild/maps/ViridianCity.asm"
INCLUDE "data/wild/maps/Route2.asm"
INCLUDE "data/wild/maps/Route22.asm"We then have to actually make that file.
Create ViridianCity.asm in data > wild > maps. Notice def_fish_wildmons uses the exact same format as def_grass_wildmons and def_water_wildmons. It will follow the same percentages for each encounter slot defined in the WildMonEncounterSlotChances table in probabilities.asm. This allows us to set 10 (or more if you follow this guide) encounters with internally consistent rarities to our existing grass and surf WildMons.
ViridianCityWildMons:
def_grass_wildmons 0 ; encounter rate
end_grass_wildmons
def_water_wildmons 0 ; encounter rate
end_water_wildmons
def_fish_wildmons 192 ; encounter rate
db 5, MAGIKARP
db 10, POLIWAG
db 10, MAGIKARP
db 20, POLIWAG
db 10, MAGIKARP
db 20, POLIWAG
db 15, MAGIKARP
db 25, POLIWAG
db 15, MAGIKARP
db 25, POLIWHIRL
end_fish_wildmonsThis also means EVERY SINGLE MAP file should be appended with fish data. The routine we updated in Step 3 expects each file to end with Fish data now. Skipping this may lead to unintended encounters, presumably along the lines of the Cinnabar coast glitch, since data wouldn't be cleared properly. Or it could be totally fine. I really don't know.
def_water_wildmons 0 ; encounter rate
end_water_wildmons
+
+ def_fish_wildmons 0 ; encounter rate
+ end_fish_wildmonsLet's pause here, there are a few considerations to call out.
-
You will need to make files like the one above for several towns that have ponds or rivers like Viridian, Cerulean, Celadon, and Fuchsia.
-
The placement of the new MapNameWildMons pointer MUST MATCH the placement of the bank file in
map_header_banks.asm. This is not a concern for the towns above since they're already defined in the right order, but for new files like Vermilion City Port (yes, the port is a separate map file) and for Cerulean City Gym (if you want to fish there, why not) which are not yet included ingrass_water.asmit's critical to order them correctly. -
Somewhat unrelated, this is a good time to revisit surfing encounters via water_wildmons. In the base game, you won't find any surfing encounters outside of "SeaRoutes" like Route 19, 20, and 21. Seems silly that the ponds and rivers scattered over the rest of Kanto don't have surf encounters. As you go through and add fish encounter data for each map, you may also want to add more surfing encounters to those same bodies of water.
Now, all we've done so far is create architecture for FishMons mirroring that of GrassMons and WaterMons, but we can't actually access yet. In order to make use of all this, we need to edit ReadSuperRodData: in item_effects.asm. You may notice ItemUseSuperRod: above in the same file, that routine only governs the player using the rod out of the bag and loading the wild battle. All of the encounter data is handled within ReadSuperRodData: so there's no need to change anything in ItemUseSuperRod: itself. Let's replace ReadSuperRodData:.
This new routine takes inspiration from wild_encounters.asm. It accomplishes two new things. One, instead of having the Super Rod do a 50/50 chance if we get a bite, it uses the new FishRate from the given map to determine how frequently we'll get a Pokemon on the hook. Two, instead of reading the fishing groups, it now pulls from the FishMons encounter table per map, using the same logic as GrassMons, WaterMons, and probabilities.asm.
ReadSuperRodData:
- ; return e = 2 if no fish on this map
- . . .
- Delete Full Code
- . . .
- INCLUDE "data/wild/super_rod.asm"
+
+ ld a, [wFishRate]
+ and a
+ jr z, .NoFish
+
+; determine if there is a bite
+ call Random
+ ld b, a
+
+ ld a, [wFishRate]
+ cp b
+ jr c, .NoBite
+
+; determine encounter slot
+ call Random
+ ld b, a
+
+ ld hl, HardcodedFishEncounterSlotChances
+
+.determineEncounterSlot
+ ld a, [hli]
+ cp b
+ jr nc, .gotEncounterSlot
+ inc hl
+ jr .determineEncounterSlot
+
+.gotEncounterSlot
+ ; HL points to slot offset byte
+ ld c, [hl]
+ ld b, 0
+
+ ld hl, wFishMons
+ add hl, bc
+
+ ld b, [hl]
+ inc hl
+ ld c, [hl]
+
+ ld e, 1
+ ret
+
+.NoBite
+ ld e, 0
+ ret
+
+.NoFish
+ ld e, 2
+ ret
+
+; hardcode table became necessary since calling regular WildMonEncounterSlotChances in probabilities.asm wouldn't start the battle
+HardcodedFishEncounterSlotChances:
+ db 51, 0
+ db 103, 2
+ db 129, 4
+ db 155, 6
+ db 181, 8
+ db 207, 10
+ db 220, 12
+ db 233, 14
+ db 244, 16
+ db 255, 18Here's where my inexperience shows. I could not get the actual WildMonEncounterSlotChances in probabilities.asm to work in this routine. From my understanding, the data came through one byte off or was shifted one byte off either here or in ItemUseSuperRod:. This would typically cause no encounter to follow "Oh, a bite!", instead it would just return to a normal game state without launching into battle. I have not been able to find the correct workaround, but hardcoding the encounter table (and this allows you to either match the one in probabilities.asm or set your own rates unique to fishing) into the routine seems to work just fine.
-
We have only overwritten the Super Rod code. This means the Old Rod and Good Rod can still use their original routines, the routines from Lotsob's Tutorial, or simply be removed from the game entirely.
-
You'll need to consider what you want from a gameplay perspective. Personally, I removed the Old and Good Rods entirely and then renamed the Super Rod just "Fishing Rod" and made it available earlier in the game. If you do the same, the scripts and text for each of the fishing guru's need to be updated. I prefer this method to reduce redundant items, save bag space, shrink
item_effects.asm, deletegood_rod.asmandsuper_rod.asmand create operational symmetry with other encounters. All that said, I'm not sure if I've saved any space or not since each of the map files has grown.