mirror of https://github.com/pret/pokeemerald.git
copied over from my personal fork where i was drafting it
parent
ed1cb04f99
commit
ee5a2578d9
|
@ -0,0 +1,391 @@
|
||||||
|
Beginning with Pokémon X and Y, you could earn EXP by catching a Wild Pokémon. Wouldn't it be nice to backport this feature to Pokémon Emerald? Well, it's *relatively* easy.
|
||||||
|
|
||||||
|
This tutorial has two parts. The "[Figuring it out](#Figuring-it-out)" section walks you through the process of figuring out how to implement Catch EXP from scratch: looking at game systems, understanding them, making code changes, testing them, and fixing bugs as they come up. The "[Just the code changes](#Just-the-code-changes)" section lists only the three relatively small code changes you need to make in order to get a bug-free Catch EXP feature.
|
||||||
|
|
||||||
|
If you look at the table of contents, you'll notice that "figuring it out" is a much, much longer read; this is because I try to walk you step by step through how to implement something like this, including finding functions we need to edit and reasoning about what could be causing some of the bugs we run into when setting this up from scratch. If you're not used to making these kinds of edits, or if you struggle with troubleshooting bugs, I think it might be a good idea to tough it out, read all this *mucho texto*, and see if it can teach you anything about solving the kinds of problems you run into when making code changes in Pokémon Emerald.
|
||||||
|
|
||||||
|
# Figuring it out
|
||||||
|
|
||||||
|
## Where do we start?
|
||||||
|
|
||||||
|
A helpful thing to know is that Game Freak actually built an entire scripting language for the battle system, a lot like the scripting language used for overworld events, and they scripted a lot more than you'd expect. All of the move effects are scripted, but so are things like most of the process of catching a Wild Pokémon. The game's *execution flow* bounces back and forth between hardcoded C and scripts like a ping-pong ball in order to accomplish what Game Freak wanted. This may sound disorganized (and it kind of is!), but it means that Game Freak's designers could fine-tune basic behaviors of the battle system without having to edit the deeper engine. For example, when you catch a Wild Pokémon, what should happen first? Should you get a chance to rename it, or should you see its Pokédex entry? Each of these is a script command, so the core functionality is in the engine's C code, but the script decides when -- and in what order -- to activate it.
|
||||||
|
|
||||||
|
### The "successful catch" script
|
||||||
|
|
||||||
|
We can find the scripts for Poké Balls' battle effects in `data/battle_scripts_2.s`. Let's look at the script for a successful catch:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
BattleScript_SuccessBallThrow::
|
||||||
|
jumpifhalfword CMP_EQUAL, gLastUsedItem, ITEM_SAFARI_BALL, BattleScript_PrintCaughtMonInfo
|
||||||
|
incrementgamestat GAME_STAT_POKEMON_CAPTURES
|
||||||
|
BattleScript_PrintCaughtMonInfo::
|
||||||
|
printstring STRINGID_GOTCHAPKMNCAUGHTPLAYER
|
||||||
|
trysetcaughtmondexflags BattleScript_TryNicknameCaughtMon
|
||||||
|
printstring STRINGID_PKMNDATAADDEDTODEX
|
||||||
|
waitstate
|
||||||
|
setbyte gBattleCommunication, 0
|
||||||
|
displaydexinfo
|
||||||
|
BattleScript_TryNicknameCaughtMon::
|
||||||
|
printstring STRINGID_GIVENICKNAMECAPTURED
|
||||||
|
waitstate
|
||||||
|
setbyte gBattleCommunication, 0
|
||||||
|
trygivecaughtmonnick BattleScript_GiveCaughtMonEnd
|
||||||
|
givecaughtmon
|
||||||
|
printfromtable gCaughtMonStringIds
|
||||||
|
waitmessage B_WAIT_TIME_LONG
|
||||||
|
goto BattleScript_SuccessBallThrowEnd
|
||||||
|
BattleScript_GiveCaughtMonEnd::
|
||||||
|
givecaughtmon
|
||||||
|
BattleScript_SuccessBallThrowEnd::
|
||||||
|
setbyte gBattleOutcome, B_OUTCOME_CAUGHT
|
||||||
|
finishturn
|
||||||
|
```
|
||||||
|
|
||||||
|
That's a lot, so let's break it down. The lines that aren't indented, and that end with two colons, are *labels*. That is, they define *named locations* for code. We can reference them from C code (for example, the hardcoded game engine can decide to run the script code starting at `BattleScript_SuccessBallThrow`), and we can also "jump" to them if certain conditions are met. Let's look at just the first few lines of the script to get a handle on this idea:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
BattleScript_SuccessBallThrow::
|
||||||
|
jumpifhalfword CMP_EQUAL, gLastUsedItem, ITEM_SAFARI_BALL, BattleScript_PrintCaughtMonInfo
|
||||||
|
incrementgamestat GAME_STAT_POKEMON_CAPTURES
|
||||||
|
BattleScript_PrintCaughtMonInfo::
|
||||||
|
```
|
||||||
|
|
||||||
|
So that first command roughly translates to: "We want to jump to another spot if a two-byte value (a 'halfword') is equal to some other value. The values we want to compare are the value stored in the `gLastUsedItem` variable, and `ITEM_SAFARI_BALL`. If they're equal, we want to jump to `BattleScript_PrintCaughtMonInfo`." You can see that our *jump target*, the "print caught 'mon info" label, is just two lines down, so if we "take the jump," we'll be skipping the next line, the next command.
|
||||||
|
|
||||||
|
The command that we're potentially skipping roughly translates to, "We keep track of how many Pokémon the player has caught. Please increase that game stat by one." So what we're doing is, if the player used a Safari Ball, then we *skip* increasing the player's "Pokémon captures" counter; but if the player used any other ball, then we increase that count. We only want to keep track of normal captures under normal circumstances, you see; the Safari Zone is so different that we don't even want to count it.
|
||||||
|
|
||||||
|
Let's keep reading onward.
|
||||||
|
|
||||||
|
```asm
|
||||||
|
BattleScript_PrintCaughtMonInfo::
|
||||||
|
printstring STRINGID_GOTCHAPKMNCAUGHTPLAYER
|
||||||
|
trysetcaughtmondexflags BattleScript_TryNicknameCaughtMon
|
||||||
|
printstring STRINGID_PKMNDATAADDEDTODEX
|
||||||
|
waitstate
|
||||||
|
setbyte gBattleCommunication, 0
|
||||||
|
displaydexinfo
|
||||||
|
BattleScript_TryNicknameCaughtMon::
|
||||||
|
```
|
||||||
|
|
||||||
|
So we print some text to tell the player that they successfully caught the Pokémon. Then, we try and set the caught Pokémon's "owned" flag in the Pokédex. If that fails (because the flag is already set), then we skip ahead to `BattleScript_TryNicknameCaughtMon`. The code that we might end up skipping is responsible for a few things, but mainly, it displays the caught Pokémon's Pokédex data; this would be the first time the player sees that information. (I promise that me showing you this will turn out to be relevant.)
|
||||||
|
|
||||||
|
You'll note that the `trysetcaughtmondexflags` command and the `jumpifhalfword` command both perform conditional jumps, but they look pretty different. There isn't really a consistent convention in how these script commands "look," so don't get too hung up on trying to spot a pattern.
|
||||||
|
|
||||||
|
|
||||||
|
### So how do we award EXP?
|
||||||
|
|
||||||
|
Remember two sections ago when I said that a helpful thing to know is that Game Freak actually built an entire scripting language for the battle system, a lot like the scripting language used for overworld events, and they scripted a lot more than you'd expect?
|
||||||
|
|
||||||
|
> A helpful thing to know is that Game Freak actually built an entire scripting language for the battle system, a lot like the scripting language used for overworld events, and they scripted a lot more than you'd expect. All of the move effects are scripted, but so are things like most of the process of catching a Wild Pokémon.
|
||||||
|
|
||||||
|
Well, it turns out, there's a script command for awarding EXP, too, because winning a battle is also a script. If you go to `data/battle_scripts_1.s` and Ctrl + F for `xp`, you'll find a lot of stuff related to the move Explosion, but you'll *eventually* find *this:*
|
||||||
|
|
||||||
|
```asm
|
||||||
|
BattleScript_GiveExp::
|
||||||
|
setbyte sGIVEEXP_STATE, 0
|
||||||
|
getexp BS_TARGET
|
||||||
|
end2
|
||||||
|
```
|
||||||
|
|
||||||
|
So there's a script command named `getexp`, and it takes a single parameter, which seems to be the Pokémon whose defeat is the source of that EXP. If we search for all uses of the `getexp` command, we'll find that before we run the command, we always run `setbyte sGIVEEXP_STATE, 0`, too. So that sounds simple enough: let's just add that to the catch script.
|
||||||
|
|
||||||
|
We can also add some code comments by prefixing them with the `@` symbol: these `*.s` files are assembly files, and when the assembler (like a compiler, but not) sees a `@`, it ignores all other text until the end of the line. We can use that to annotate our changes with some explanations.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
BattleScript_SuccessBallThrow::
|
||||||
|
jumpifhalfword CMP_EQUAL, gLastUsedItem, ITEM_SAFARI_BALL, BattleScript_PrintCaughtMonInfo
|
||||||
|
incrementgamestat GAME_STAT_POKEMON_CAPTURES
|
||||||
|
BattleScript_PrintCaughtMonInfo::
|
||||||
|
printstring STRINGID_GOTCHAPKMNCAUGHTPLAYER
|
||||||
|
+ @
|
||||||
|
+ @ ROM hack edit: give catch EXP:
|
||||||
|
+ @
|
||||||
|
+ setbyte sGIVEEXP_STATE, 0
|
||||||
|
+ getexp BS_TARGET
|
||||||
|
+ @
|
||||||
|
trysetcaughtmondexflags BattleScript_TryNicknameCaughtMon
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing in-game
|
||||||
|
|
||||||
|
Compile the ROM as usual, and then start up Emerald. If you have an existing save file (and haven't made any changes to savegame data since your last compile), then you can use that. Otherwise, play up until you have some Poké Balls, and then park by some tall grass and save the game. We're not going to change how the savegame is formatted during this tutorial, so you should be able to reuse this save file for all of the tests we're going to run. (Spoiler: There'll be a few.)
|
||||||
|
|
||||||
|
If you enter a wild battle and catch a new Pokémon, you should see this:
|
||||||
|
|
||||||
|
https://github.com/pret/pokeemerald/assets/831497/6a0d52fd-c322-4b2c-9105-9f6c4a7ddbc9
|
||||||
|
|
||||||
|
That... mostly works! There's some jank to it, though. The music for a successful capture starts to play, but then it gets cut off by the music for winning a wild battle. Why does that happen? Well, the only thing we added was code to grant some EXP, so I guess we need to look at how the `getexp` command actually works.
|
||||||
|
|
||||||
|
## The C code for battle script commands
|
||||||
|
|
||||||
|
Let's head to `src/battle_script_commands.c`. Each of the C functions that defines a command is named after the command itself, so if we Ctrl + F for `getexp`, we'll find the function we want: `Cmd_getexp`. Beginning at line 3241 and ending at line 3518, this is a bit of a big one! So before we continue, let's talk about how battle scripts actually *work*.
|
||||||
|
|
||||||
|
### How battle script and their commands work
|
||||||
|
|
||||||
|
1. Under the hood, a battle script is just a blob of bytes. The battle engine is told to read bytes at a given location, so it sets the variable `gBattlescriptCurrInstr` ("battle script current instruction") to refer to that location. Then it reads the first byte it sees there and goes, "Ah! This must be a command ID." It looks up the <var>n</var>-th command in its dictionary of script commands and runs that command's C function.
|
||||||
|
|
||||||
|
2. A typical script command will begin by reading additional bytes starting at (`gBattlescriptCurrInstr + 1`), to retrieve whatever parameters it uses. For example, `jumpifhalfword` would read the byte at (`gBattlescriptCurrInstr + 1`) to get the kind of comparison we want to run (e.g. `CMP_EQUAL` to check if two values are equal).
|
||||||
|
|
||||||
|
3. The script command would then *increase* `gBattlescriptCurrInstr`. If you think of `gBattlescriptCurrInstr` as an arrow pointing at the spot we want to read from, then "increasing" it just moves the arrow forward. Now, bear in mind that we didn't increase `gBattlescriptCurrInstr` *before* running the script command; so, when the script command ran, `gBattlescriptCurrInstr` was the location of the command itself, not the options that the script wanted to give it.
|
||||||
|
|
||||||
|
We need to understand this stuff because `getexp`... doesn't work like that.
|
||||||
|
|
||||||
|
### How `getexp` works
|
||||||
|
|
||||||
|
The `getexp` command has to do a *lot* of calculations, and Game Freak didn't want to lag the game, so they decided to split those calculations into six batches. The `getexp` command relies on a counter in order to know what batch it needs to run next. The counter is called `gBattleScripting.getexpState` in C, and `sGIVEEXP_STATE` in scripts: that's our `setbyte sGIVEEXP_STATE, 0` command.
|
||||||
|
|
||||||
|
Here's the clever devilry: `getexp` doesn't increase `gBattlescriptCurrInstr` when it finishes, *unless* it's finished the sixth batch. So what ends up happening?
|
||||||
|
|
||||||
|
1. The script sets `gBattleScripting.getexpState` to 0.
|
||||||
|
1. The script runs `getexp`, so `gBattlescriptCurrInstr` points to the `getexp` instruction in the script.
|
||||||
|
1. `Cmd_getexp` sees that `gBattleScripting.getexpState` is 0, so it runs the zeroth batch (computers start counting from zero, not one).
|
||||||
|
1. `Cmd_getexp` finishes running and increases `gBattleScripting.getexpState` to 1 on its way out.
|
||||||
|
1. The battle script engine wants to run the next command, which is the command at `gBattlescriptCurrInstr`... except that we didn't increase `gBattlescriptCurrInstr`, didn't move it forward, so it *still points to the `getexp` instruction.*` The one we just ran.
|
||||||
|
1. ...So the script runs `getexp` again.
|
||||||
|
1. `Cmd_getexp` sees that `gBattleScripting.getexpState` is now 1, so it runs the first batch (humans would call this the "second" batch).
|
||||||
|
1. `Cmd_getexp` finishes running and increases `gBattleScripting.getexpState` to 2 on its way out.
|
||||||
|
1. The battle script engine wants to run the next command. We still haven't increased `gBattlescriptCurrInstr`, so...
|
||||||
|
1. ...we run the same `getexp` instruction *yet again*.
|
||||||
|
1. `Cmd_getexp` sees that `gBattleScripting.getexpState` is now 2, so it runs the second batch (humans would call this the "third" batch).
|
||||||
|
1. `Cmd_getexp` finishes running and increases `gBattleScripting.getexpState` to 3 on its way out.
|
||||||
|
1. The battle script engine wants to run the next command. We still haven't increased `gBattlescriptCurrInstr`. Do you see what we're doing here?
|
||||||
|
1. We run the same `getexp` instruction again.
|
||||||
|
1. `Cmd_getexp` sees that `gBattleScripting.getexpState` is now 3, so it runs the third batch (humans would call this the "fourth" batch).
|
||||||
|
1. `Cmd_getexp` finishes running and increases `gBattleScripting.getexpState` to 4 on its way out.
|
||||||
|
1. The battle script engine wants to run the "next" (haha) command.
|
||||||
|
1. We run the same `getexp` instruction again.
|
||||||
|
1. `Cmd_getexp` sees that `gBattleScripting.getexpState` is now 4, so it runs the fourth batch (humans would call this the "fifth" batch).
|
||||||
|
1. `Cmd_getexp` finishes running and increases `gBattleScripting.getexpState` to 5 on its way out.
|
||||||
|
1. The battle script engine wants to run the "next" (haha) command.
|
||||||
|
1. We run the same `getexp` instruction again.
|
||||||
|
1. `Cmd_getexp` sees that `gBattleScripting.getexpState` is now 5, so it runs the ***last*** batch... and as part of *that batch*, it finally increases `gBattlescriptCurrInstr`.
|
||||||
|
1. `Cmd_getexp` finishes running. We're finally free.
|
||||||
|
1. The battle script engine wants to run the next command. Finally, it sees the command after `getexp`, which for our catch script is `trysetcaughtmondexflags`.
|
||||||
|
|
||||||
|
So now that we know how `getexp` is structured, it'll be easier to read it. All the code is broken into a big `switch` statement that divides the code into these six batches, but since we know they're all going to run sequentially anyway (just not on the same frame), we can pretty much just ignore the `switch` and mentally read the whole `Cmd_getexp` function as one big chunk of code.
|
||||||
|
|
||||||
|
### So where's that music coming from?
|
||||||
|
|
||||||
|
I'll save you some time: it's in what humans would call the "third" batch, or `case 2`. Beginning at line 3345:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// music change in wild battle after fainting a poke
|
||||||
|
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) && gBattleMons[0].hp != 0 && !gBattleStruct->wildVictorySong)
|
||||||
|
{
|
||||||
|
BattleStopLowHpSound();
|
||||||
|
PlayBGM(MUS_VICTORY_WILD);
|
||||||
|
gBattleStruct->wildVictorySong++;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ah! They want the victory theme to start playing when you're told how much EXP you're given, so they just... jammed the code for it right into `getexp`. After all, the only way to gain EXP during a wild battle is to faint the one (1) wild Pokémon you're battling. There's no other way to gain EXP in a wild battle. Nope. Can't happen.
|
||||||
|
|
||||||
|
Okay, so how do we fix it? We need to make the game double-check that the battle *isn't* ending with a capture, but how can we know, at *this* point in the code, whether the wild Pokémon has been caught?
|
||||||
|
|
||||||
|
Well, what does catching a wild Pokémon actually *do*? Is there something that we can detect? Some variable being set somewhere, perhaps?
|
||||||
|
|
||||||
|
## What does catching a wild Pokémon actually *do*?
|
||||||
|
|
||||||
|
Let's look back at the script for successfully catching a wild Pokémon:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
BattleScript_SuccessBallThrow::
|
||||||
|
jumpifhalfword CMP_EQUAL, gLastUsedItem, ITEM_SAFARI_BALL, BattleScript_PrintCaughtMonInfo
|
||||||
|
incrementgamestat GAME_STAT_POKEMON_CAPTURES
|
||||||
|
BattleScript_PrintCaughtMonInfo::
|
||||||
|
printstring STRINGID_GOTCHAPKMNCAUGHTPLAYER
|
||||||
|
```
|
||||||
|
|
||||||
|
That's everything that happens before we try to `getexp`, and hm, nope, there's nothing in there that we can check for. Okay, but how do we actually *get* to the `BattleScript_SuccessBallThrow` label? If we try to Ctrl + F the battle script files, we won't find any other uses of the label name.
|
||||||
|
|
||||||
|
Well, here's the trick. Remember back in the earlier explanation of the catch script, when I said that the lines that aren't indented, and that end with two colons, are *labels* that define *named locations* for code, and we can reference them from C code, and we can also "jump" to them if certain conditions are met?
|
||||||
|
|
||||||
|
> The lines that aren't indented, and that end with two colons, are *labels*. That is, they define *named locations* for code. We can reference them from C code (for example, the hardcoded game engine can decide to run the script code starting at `BattleScript_SuccessBallThrow`), and we can also "jump" to them if certain conditions are met.
|
||||||
|
|
||||||
|
You might be able to see where this is going: when the game decides that you've successfully caught a Pokémon, it sets the `gBattlescriptCurrInstr` "arrow" to point to `BattleScript_SuccessBallThrow`. Everything in battles is scripted except when it isn't, so let's Ctrl + F inside of `src/battle_script_commands.c` for `BattleScript_SuccessBallThrow`.
|
||||||
|
|
||||||
|
That takes us to line 9938, which is in the middle of `Cmd_handleballthrow`. There are actually two different places where we use C to have the script jump to `BattleScript_SuccessBallThrow` -- and an important thing to understand is that from the script's perspective, the jump is instant, but from C's perspective, we only jump when it's time to run the next script instruction; the rest of `Cmd_handleballthrow` still runs. So what does it do?
|
||||||
|
|
||||||
|
```c
|
||||||
|
SetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_POKEBALL, &gLastUsedItem);
|
||||||
|
```
|
||||||
|
|
||||||
|
It doesn't do anything else to "remember," within C, that a successful capture has occurred, because it doesn't really need to *remember* that; we've decided that the next script instruction to run should be the "successful capture" script, and that'll do whatever it needs to do. `Cmd_handleballthrow` doesn't do anything else to "remember" the situation in C, but it does this *one* line of code that we can take advantage of. When you catch a Pokémon, the game modifies that Pokémon to store the type of Poké Ball you used, so that you can see that ball type when viewing the Pokémon's stats.
|
||||||
|
|
||||||
|
We *set* that information. We can *get* that information.
|
||||||
|
|
||||||
|
## Back to the music code!
|
||||||
|
|
||||||
|
You can look up the `GetMonData` and `SetMonData` functions in `include/pokemon.h` to see how they work, but I'll save you the trouble for now. Here's how we want to change `getexp`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
// music change in wild battle after fainting a poke
|
||||||
|
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) && gBattleMons[0].hp != 0 && !gBattleStruct->wildVictorySong)
|
||||||
|
{
|
||||||
|
+ if (GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_POKEBALL) == ITEM_NONE) {
|
||||||
|
BattleStopLowHpSound();
|
||||||
|
PlayBGM(MUS_VICTORY_WILD);
|
||||||
|
gBattleStruct->wildVictorySong++;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the wild Pokémon *doesn't* have a Poké Ball type set, then it must not have been captured. The only way we can reach `getexp` during a wild battle is if the Pokémon is captured or defeated, so it must have been defeated, so *now* we can let the victory music play.
|
||||||
|
|
||||||
|
## Testing the music fix
|
||||||
|
|
||||||
|
Let's try in-game and see how it works.
|
||||||
|
|
||||||
|
https://github.com/pret/pokeemerald/assets/831497/60a12dfa-2235-4903-8b63-ab83132ad691
|
||||||
|
|
||||||
|
The music issue is solved! However, there's another subtle problem that we see when I capture Wurmple. If we level up from catch EXP, and then are shown the caught Pokémon's Pokédex entry, then all the text is shifted to the side, and it wraps around to the other end of the screen. That's... Well, that's not *great*.
|
||||||
|
|
||||||
|
## So how does text display work?
|
||||||
|
|
||||||
|
Okay, this one would actually be a lot harder to find, because it would require that you dig through the Pokédex user interface, which is one of *the* most complex UIs in the entire game, so I'm going to give you the short version.
|
||||||
|
|
||||||
|
* UIs in this game define **paint windows**, just called "windows" by Game Freak, which reserve portions of the GBA's VRAM for graphics data.
|
||||||
|
* Game Freak has a **text printer** system that is designed to draw text onto those paint windows.
|
||||||
|
* The Game Boy Advance divides graphics into four "background" ("BG") layers that use 8-by-8-pixel tiles, and an "object" ("OBJ") layer for sprites.
|
||||||
|
* Paint windows only use background tiles, and each window can only exist on a single layer.
|
||||||
|
|
||||||
|
If you're testing with mGBA, then you should be able to go to the **Tools** menu, navigate to **Game state views**, and choose to **View map**. This will let us look at each of the background layers. We can see that the Pokédex text is all on layer 2, and the visual parts of the UI (minus Wurmple's sprite) are on layer 3.
|
||||||
|
|
||||||
|
![A screenshot of mGBA's map view, showing BG layer 2. The Pokédex text is visible, though it shows as black text on a black background, since the layer behind it isn't being shown.](https://github.com/pret/pokeemerald/assets/831497/8818a535-30f5-4d0d-a7c9-1869ac106d33)
|
||||||
|
|
||||||
|
Hm, nope, that actually looks normal. There's no shifting within the background layer itself; the text printer is doing its job properly. The GBA *can* be told to shift an entire BG layer around on the screen, and when it does so, they wraparound. Checking for that is a little advanced, but we can do it. Go to **Tools**, navigate to **Game state views**, and select **View I/O registers**. The I/O registers are just special variables that a ROM's code can use to talk directly to the GBA's hardware. Each register has a special purpose and its value has a special meaning.
|
||||||
|
|
||||||
|
Let's select register `0x4000018: BG2HOFS`: "background 2 horizontal offset."
|
||||||
|
|
||||||
|
![A screenshot of mGBA's I/O register view, pointed at BG2HOFS. Bits 5, 7, and 8 are set, producing a value of 416, and mGBA shows both the bits (as checkboxes) and the human-readable value.](https://github.com/pret/pokeemerald/assets/831497/4c4b41e9-e543-4ed3-abd6-8726a364b3fb)
|
||||||
|
|
||||||
|
According to this, there's a horizontal offset of 416 pixels. Let's try unchecking all of the checkboxes and clicking "apply," and see if that fixes anything, and... nothing happened! If we switch to any other I/O register in mGBA and then switch back to `BG2HOFS`, then it shows 416 again. It's like we never even changed the value. Did mGBA mess up somehow? Or is the game somehow *re-shifting* the background every frame, as if it just *really doesn't want* us messing with it? Where could the code be doing that?
|
||||||
|
|
||||||
|
Well, this problem we're having -- it only happened when we leveled up, right? How does that work?
|
||||||
|
|
||||||
|
How does leveling up...
|
||||||
|
|
||||||
|
...in battle...
|
||||||
|
|
||||||
|
It... It's a script command. It's *always* a script command.
|
||||||
|
|
||||||
|
## Let's look at `drawlvlupbox`
|
||||||
|
|
||||||
|
The `BattleScript_LevelUp` script in `data/battle_scripts_1.s` calls the `drawlvlupbox` function whether or not we've leveled up. The `Cmd_drawlvlupbox` function is very similar to `getexp`: it's another one of those commands that runs itself over and over in order to divide its work up across multiple frames. We can see that it sets a bunch of variables that look like `gBattle_BG2_Y`. I guess the battle engine checks these variables on every frame and uses them to decide where the four BG layers should be shifted to. It's probably meant as a way to let move animations easily manipulate graphics on the BG layers.
|
||||||
|
|
||||||
|
Why is `drawlvlupbox` shifting a BG layer, though, I wonder? Well, PRET left this handy-dandy code comment in `Cmd_drawlvlupbox`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// If the Pokémon getting exp is not in-battle then
|
||||||
|
// slide out a banner with their name and icon on it.
|
||||||
|
// Otherwise skip ahead.
|
||||||
|
if (IsMonGettingExpSentOut())
|
||||||
|
gBattleScripting.drawlvlupboxState = 3;
|
||||||
|
else
|
||||||
|
gBattleScripting.drawlvlupboxState = 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
Ah, I see! If a Pokémon gains EXP when it's not on the field, the game displays a cute little banner showing its icon and name... and it uses a sliding animation! We only run `case 1` and `case 2` in `Cmd_drawlvlupbox` if we want to draw that banner. Case 1 calls the `InitLevelUpBanner` function, and if we Ctrl + F that,...
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void InitLevelUpBanner(void)
|
||||||
|
{
|
||||||
|
gBattle_BG2_Y = 0;
|
||||||
|
gBattle_BG2_X = LEVEL_UP_BANNER_START;
|
||||||
|
```
|
||||||
|
|
||||||
|
Aha! They're placing that banner on BG layer 2, so they want to shift the layer for it. Keep running Ctrl + F for `LEVEL_UP_BANNER_START` and you'll find that it's a constant set to 416, the exact amount by which our background was shifted!
|
||||||
|
|
||||||
|
## Fixing the BG shift
|
||||||
|
|
||||||
|
If we go back to our "successful catch" script and read it, one command should jump out at us: `displaydexinfo`. I bet the C code for that will be named `Cmd_displaydexinfo`. Let's find it.
|
||||||
|
|
||||||
|
Looking at the code, we can see that this is *yet another* script command that runs itself over and over. This time, though, it's because it needs to wait for you to click through the Pokédex entry before it allows the script to continue. We can see that in `case 1`, it calls a function named `DisplayCaughtMonDexPage`, so let's reset the battle-specific BG variables *before* we call that. In fact, let's reset all of them just to be real sure. When we looked at the Pokédex screen's BG layers in mGBA, we saw that they're all set up so that they'd only display properly if they're not shifted.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
case 1:
|
||||||
|
if (!gPaletteFade.active)
|
||||||
|
{
|
||||||
|
FreeAllWindowBuffers();
|
||||||
|
+ gBattle_BG0_X = 0;
|
||||||
|
+ gBattle_BG0_Y = 0;
|
||||||
|
+ gBattle_BG1_X = 0;
|
||||||
|
+ gBattle_BG1_Y = 0;
|
||||||
|
+ gBattle_BG2_X = 0;
|
||||||
|
+ gBattle_BG2_Y = 0;
|
||||||
|
+ gBattle_BG3_X = 0;
|
||||||
|
+ gBattle_BG3_Y = 0;
|
||||||
|
gBattleCommunication[TASK_ID] = DisplayCaughtMonDexPage(SpeciesToNationalPokedexNum(species),
|
||||||
|
gBattleMons[gBattlerTarget].otId,
|
||||||
|
gBattleMons[gBattlerTarget].personality);
|
||||||
|
gBattleCommunication[0]++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing the BG fix
|
||||||
|
|
||||||
|
Let's give it another try.
|
||||||
|
|
||||||
|
https://github.com/pret/pokeemerald/assets/831497/aa051a34-336a-4cac-b950-f571408f4aed
|
||||||
|
|
||||||
|
It works! The music is correct, and we don't glitch out the Pokédex text! We've implemented catch EXP.
|
||||||
|
|
||||||
|
It took some digging, but we didn't actually have to change a lot of code. Let's recap...
|
||||||
|
|
||||||
|
|
||||||
|
# Just the code changes
|
||||||
|
|
||||||
|
In `data/battle_scripts_2.s`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
BattleScript_SuccessBallThrow::
|
||||||
|
jumpifhalfword CMP_EQUAL, gLastUsedItem, ITEM_SAFARI_BALL, BattleScript_PrintCaughtMonInfo
|
||||||
|
incrementgamestat GAME_STAT_POKEMON_CAPTURES
|
||||||
|
BattleScript_PrintCaughtMonInfo::
|
||||||
|
printstring STRINGID_GOTCHAPKMNCAUGHTPLAYER
|
||||||
|
+ @
|
||||||
|
+ @ ROM hack edit: give catch EXP:
|
||||||
|
+ @
|
||||||
|
+ setbyte sGIVEEXP_STATE, 0
|
||||||
|
+ getexp BS_TARGET
|
||||||
|
+ @
|
||||||
|
trysetcaughtmondexflags BattleScript_TryNicknameCaughtMon
|
||||||
|
```
|
||||||
|
|
||||||
|
In `Cmd_getexp` in `src/battle_script_commands.c`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
// music change in wild battle after fainting a poke
|
||||||
|
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) && gBattleMons[0].hp != 0 && !gBattleStruct->wildVictorySong)
|
||||||
|
{
|
||||||
|
+ if (GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_POKEBALL) == ITEM_NONE) {
|
||||||
|
BattleStopLowHpSound();
|
||||||
|
PlayBGM(MUS_VICTORY_WILD);
|
||||||
|
gBattleStruct->wildVictorySong++;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In `Cmd_displaydexinfo` in `src/battle_script_commands.c`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
case 1:
|
||||||
|
if (!gPaletteFade.active)
|
||||||
|
{
|
||||||
|
FreeAllWindowBuffers();
|
||||||
|
+ gBattle_BG0_X = 0;
|
||||||
|
+ gBattle_BG0_Y = 0;
|
||||||
|
+ gBattle_BG1_X = 0;
|
||||||
|
+ gBattle_BG1_Y = 0;
|
||||||
|
+ gBattle_BG2_X = 0;
|
||||||
|
+ gBattle_BG2_Y = 0;
|
||||||
|
+ gBattle_BG3_X = 0;
|
||||||
|
+ gBattle_BG3_Y = 0;
|
||||||
|
gBattleCommunication[TASK_ID] = DisplayCaughtMonDexPage(SpeciesToNationalPokedexNum(species),
|
||||||
|
gBattleMons[gBattlerTarget].otId,
|
||||||
|
gBattleMons[gBattlerTarget].personality);
|
||||||
|
gBattleCommunication[0]++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
```
|
Loading…
Reference in New Issue