Make the Game Check Whether a Pokemon Can Learn a Certain Move

I've been working on a romhack called "Pokemon Coral Version" for a while. I've made a bit of progress and will eventually make a thread dedicated to it, but for now I've hit a wall.

I'm trying to change the way HMs work and have the game check whether any of your party Pokemon CAN learn the move instead of whether or not they HAVE learned the move. I've been messing around with a custom routine to make the game ask you if you want to surf when you check the water and have a Pokemon in your party that can learn Surf. It's based on "CanLearnTMHMMove" in "engine/tmhm.asm", but it isn't working. I'm not great with ASM, but I know some basic stuff and can usually brute force my way through simple ASM modifications, but I'm not having any luck with this.

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

event/overworld.asm has two places that check whether you can Surf: SurfFunction.TrySurf and TrySurfOW.

SurfFunction.TrySurf is called when you select Surf from the party menu, so you don't need to add your custom check to it.

That leaves TrySurfOW. You can see how it has a series of condition checks that jump to .quit if any of them fail. In particular, you want to change this one:

ld d, SURF
call CheckPartyMove
jr c, .quit

Basically replace CheckPartyMove with a new CheckPartyCanLearnMove routine. You can base it on the CheckPartyMove routine's code, and use the CanLearnTMHMMove routine from engine/tmhm.asm to check whether the Pokémon can learn Surf. Be sure to set CurPartyMon to the first one that can learn Surf, just like CheckpartyMove does, so it'll print "so-and-so used Surf!" correctly.

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

event/overworld.asm has two places that check whether you can Surf: SurfFunction.TrySurf and TrySurfOW.

SurfFunction.TrySurf is called when you select Surf from the party menu, so you don't need to add your custom check to it.

That leaves TrySurfOW. You can see how it has a series of condition checks that jump to .quit if any of them fail. In particular, you want to change this one:

ld d, SURF
call CheckPartyMove
jr c, .quit

Basically replace CheckPartyMove with a new CheckPartyCanLearnMove routine. You can base it on the CheckPartyMove routine's code, and use the CanLearnTMHMMove routine from engine/tmhm.asm to check whether the Pokémon can learn Surf. Be sure to set CurPartyMon to the first one that can learn Surf, just like CheckpartyMove does, so it'll print "so-and-so used Surf!" correctly.

Ok. So I set up a custom routine called "CheckPartyCanLearnMove" and called for that instead of "CheckPartyMove". I then copied "CheckPartyMove" and changed the label to "CheckPartyCanLearnMove".

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

Ok. So I set up a custom routine called "CheckPartyCanLearnMove" and called for that instead of "CheckPartyMove". I then copied "CheckPartyMove" and changed the label to "CheckPartyCanLearnMove".

Now I you say I can use the "CanLearnTMHMMove" routine. Where do I run that check? The obvious place would be here:

ld hl, PartyMon1Moves

I tried this, but checking the water once offsets the entire world's graphics by 2 tiles, and checking it again prompts the Surf text regardless of who's in the party.

You say that's "the obvious place", so it seems like you at least understand the gist of the routine, even if each individual line's purpose isn't clear. You're correct. This chunk checks if Pokémon e has move d:

You want it to check if Pokémon e can learn move d, assuming that d is a TM or HM move (since CanLearnTMHMMove relies on the learnable move sets in base data).

The problem is, you can't just call CanLearnTMHMMove and expect it to work. It takes its input in certain WRAM locations, not in registers d and e; and when it runs, it changes the values of the other registers, which you were depending on.

If you look at the source code of CanLearnTMHMMove, it takes as input a species in [CurPartySpecies] and a move in [wPutativeTMHMMove]. So you want to initialize those values before calling it. (Careful to initialize [CurPartySpecies] with the species, not the 0-to-5 index within the party that [CurPartyMon] holds.) You should also surround the call in push/pop pairs, i.e.:

push de
call CanLearnTMHMMove
pop de

(If you study how CheckPartyMove works, you can see that the value of de needs to be preserved, but bc and hl don't.)

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

First off, thank you so much for bearing with me. It's pretty clear that I don't completely understand what I'm doing yet.

Rangi wrote:

The problem is, you can't just call CanLearnTMHMMove and expect it to work. It takes its input in certain WRAM locations, not in registers d and e; and when it runs, it changes the values of the other registers, which you were depending on.

Right. Ok. That makes sense.

If you look at the source code of CanLearnTMHMMove, it takes as input a species in [CurPartySpecies] and a move in [wPutativeTMHMMove]. So you want to initialize those values before calling it. (Careful to initialize [CurPartySpecies] with the species, not the 0-to-5 index within the party that [CurPartyMon] holds.)

This is the part I'm struggling to understand. How would I initialize the values? Is there somewhere I can study a similar situation so I can get an idea of how it works?

Explanation of the correct code: "and X" sets a to the result of a & X, and sets the z(ero) flag if the result is 0. Thus "and a" calculates a & a, which will not change the value of a, but will set the z flag if a == 0.

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

Ok. So with all of these changes, surf now works only if I have at least 2 Pokemon in my party regardless of if they can learn Surf or not. If I do say yes, it says '[2nd Pokemon] USED SURF'. Also, the world distorts whenever I check the water.

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

By the way, it may help you to put comments over every few lines of code explaining what they're supposed to do. For one thing, it guides whoever's reading the code, whether it's somebody else or yourself a month later. It also explicitly says what you meant to do so you can notice if the code will actually do something else. Plus just the act of explaining what you want to do, in small but meaningful steps, can often make you realize the solution to problems.

Like "Check if a monster in your party can learn move d" is a good example—it concisely explains what the routine is supposed to do and what input it takes—but it's rather broad. On the other hand, a comment like "compare a with c" on the line "cp c" would be too limited. Look for logical "paragraphs" of code that you aren't yet able to take in all at once, study what they're doing, and summarize them in a top comment. Eventually you'll start having larger paragraphs because you get used to common patterns of code. Like this:

You might want to add a comment like "hl = PartyMon1Moves + PARTYMON_STRUCT_LENGTH * e", or better yet "Set hl to the e'th party mon's moves". But later on you'll be familiar with the AddNTimes routine, and can just skim over those lines when reading the code, without needing the comment. Which means you'll be able to understand and summarize and write larger, more complicated routines.

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

coraldev wrote:

Ok. So with all of these changes, surf now works only if I have at least 2 Pokemon in my party regardless of if they can learn Surf or not. If I do say yes, it says '[2nd Pokemon] USED SURF'. Also, the world distorts whenever I check the water.

Interesting. I haven't run any of this code, so I've missed something about it. I can help debug it later, but this might be an opportunity for you to learn how to use the BGB emulator's debugger.

You right-click on the emulator and go to "Other→Debugger" to open it.

Then go to the menu "Debug→breakpoints". You're debugging the CheckPartyCanLearnMove routine, so in the "PC=" box, paste "CheckPartyCanLearnMove" and click Add. (You can see that I've set a breakpoint to the CheckPartyMove routine.) (BGB knows that the label name "CheckPartyCanLearnMove" corresponds to a certain address in the ROM because of the .sym file generated by rgbds. Make sure that the sym file and the gbc file exist together, with the same name.)

A breakpoint is a point in the code that breaks execution. In other words, when you talk to the water and try to Surf, the game will pause as soon as the program counter reaches that routine. Then you can press F3 to step through the code line by line, or F9 to continue executing normally. While you're stepping through the lines, you can observe the register values in the top-right corner, and the WRAM values in the bottom-right. (Right-click the WRAM and click "Go to"; then you can paste a variable name from wram.asm and it'll jump to it.) Keep a mental model (or a pencil-and-paper model) of what you expect the code to be doing, and compare it with what you observe the code actually doing.

I'm really not sure what the bug is in this case. But it's most likely to occur in the new code, and the entire body of CanLearnTMHMMove is the largest block of new code you've introduced. So maybe try stubbing out the "call CanLearnTMHMMove"—replace it with a dummy return value, like "ld c, 1" or "ld c, 0". Then if it runs fine you know something in CanLearnTMHMMove is causing the bugs, and can work around it.

Re: Make the Game Check Whether a Pokemon Can Learn a Certain Move

coraldev wrote:

Ok. So with all of these changes, surf now works only if I have at least 2 Pokemon in my party regardless of if they can learn Surf or not. If I do say yes, it says '[2nd Pokemon] USED SURF'. Also, the world distorts whenever I check the water.

On second thought, try using "farcall CanLearnTMHMMove" instead of "call". (You need farcall when the routine you're calling exists in a different ROM bank than the one you're in.)