diff --git a/How-to-create-a-new-regular-trainer-battle.md b/How-to-create-a-new-regular-trainer-battle.md index 9b203c1..6774c22 100644 --- a/How-to-create-a-new-regular-trainer-battle.md +++ b/How-to-create-a-new-regular-trainer-battle.md @@ -1,14 +1,8 @@ -**HOW TO MAKE A REGULAR TRAINER BATTLE** -*** - If you're reading this then you've successfully been suckered into trying to make an actual game using one of pret's gen 3 decomps, poor sap that you are. Maybe your pokecrystal using gen 2 hacker friends told you how convenient and easy to use the pret projects were. And they're right! If you're using pokecrystal that is. But this isn't gen 2, this is gen 3, and gen 3 is a whole different beast. One of the most immediately recognizable features of a Pokemon game are the battles you have against other trainers, would you like to know how to create and insert one of these into your romhack? Well you've come to the right place. -*** - - -* **STEP 1: WRITE YOUR TRAINER'S QUOTES AND COME UP WITH THEIR TEAM** +## Step 1: Write your trainer's quotes and come up with their team This part is simple, right? You just need to write them an opening, beaten, and closing quote like so: @@ -24,11 +18,9 @@ This part is simple, right? You just need to write them an opening, beaten, and This is Tim, he likes spiders. -*** +## Step 2: Add the trainer to the overworld using Porymap -* **STEP 2: ADD THE TRAINER TO THE OVERWORLD USING PORYMAP** - -Crack open the map you plan on adding the trainer to in porymap and add a new object, adjust the sprite to your liking. +Crack open the map you plan on adding the trainer to in Porymap, and add a new object. Adjust the sprite to your liking. ![](https://i.imgur.com/9vvSb4U.png) @@ -36,172 +28,230 @@ See there on the sidebar where it says Script (NULL)? That's where we'll be link Set the **trainer type** to **TRAINER_TYPE_NORMAL** and give your trainer a **View Radius**, typically 1-4 depending on how far you want them to be able to spot you. -To open up the map's scripts file from the decomp, click the button on porymap that says "Open Map Scripts". +To open up the map's scripts file from the decomp, click the button in Porymap that says "Open Map Scripts". -*** +## Step 3: Writing your trainer's script -* **STEP 3: WRITING YOUR TRAINER'S SCRIPT** - -For a stock, basic, totally regular trainer battle you can pretty much copy and paste this script from the original pokeemerald. You will want to change the code labels and constants here (which I'll go over) to match your new trainer's data and location. - -`Route2_EventScript_BCTim:: ` +For a stock, basic, totally regular trainer battle, you can pretty much copy and paste this script from the original pokeemerald. You will want to change the code labels and constants here (which I'll go over) to match your new trainer's data and location. +``` +Route2_EventScript_BCTim:: trainerbattle_single TRAINER_BC_TIM, Route2_Text_BCTimIntro, Route2_Text_BCTimDefeated msgbox Route2_Text_BCTimPostBattle, MSGBOX_AUTOCLOSE - end` + end +``` -The top line with the two colons is the script header, you'll want to change the location and trainer names; in this case Route 2 and BCTim to your own trainer's. +The top line with the two colons is the script label. You'll want to change the location and trainer names in it -- in this case Route 2 and BCTim -- to match your own trainer. -The second line is declaring a trainer battle using the trainerbattle_single (for a single battle) script macro. After the space is the _OPPONENT CONSTANT_ in this case TRAINER_BC_TIM. +The second line is declaring a trainer battle using the `trainerbattle_single` (for a single battle) script macro. After the space is the _OPPONENT CONSTANT_ -- in this case, `TRAINER_BC_TIM`. -After the trainer constant are the first two _TEXT POINTERS_ these correspond to the opening quote (when the trainer runs up to battle you) and the defeat quote (when they slide in on the battle screen after their last pokemon is defeated). +After the trainer constant are two _TEXT POINTERS_. These correspond to the opening quote (when the trainer runs up to battle you) and the defeat quote (when they slide in on the battle screen after their last Pokemon is defeated). On the third line is the TEXT POINTER to the post-battle quote (the one when you talk to them on the overworld after beating them). -The fourth line is *end* it ends the script. +The fourth line is `end`. It ends the script. -*** +## Step 4: Defining your trainer's opponent constant -* **STEP 4: DEFINING YOUR TRAINER'S OPPONENT CONSTANT** +In the script, we used the opponent constant `TRAINER_BC_TIM`. But what is an opponent constant? -In the script we used the opponent constant TRAINER_BC_TIM, but what is an opponent constant? +Well, an opponent constant is a number defined for every trainer battle in the game in [include/constants/opponents.h](../blob/master/include/constants/opponents.h). -Well, an opponent constant is defined for every trainer battle in the game in **include\constants\opponents.h** - -In this file you will see a list of defines for all the trainers. In this example I'll be replacing the first entry on the list after index 0. But before I do that, why am I replacing a trainer instead of just adding one to the *end* of the list? +In this file, you will see a list of `#define`s for all the trainers. In this example, we'll be replacing the first entry on the list after index 0. ![](https://i.imgur.com/mXEkVOy.png) -Pokemon Emerald as it is already used up 855 out of a maximum 864 trainer battle slots. The reason these slots are limited as they come is because these opponent constants also handle the save flags for recognizing what trainers you've defeated already. +You may be wondering, "Why am I *replacing* a trainer instead of just adding one to the *end* of the list?" Pokemon Emerald as it is already used up 855 out of a maximum 864 trainer battle slots. The reason these slots are limited as they come is because these opponent constants also handle the save flags for recognizing what trainers you've defeated already. That means adding too many trainers to the game will require adding more flags, and changing how the save file is structured, and rendering any save files you currently have useless. Replacing a trainer is simpler and easier. -So with that out of the way lets replace number 1: TRAINER_SAWYER_1 +So with that out of the way, let's replace opponent constant number 1, `TRAINER_SAWYER_1`: -![](https://i.imgur.com/IRc0RMh.png) +```diff + #ifndef GUARD_CONSTANTS_OPPONENTS_H + #define GUARD_CONSTANTS_OPPONENTS_H + + #define TRAINER_NONE 0 +-#define TRAINER_SAWYER_1 1 ++#define TRAINER_BC_TIM 1 + #define TRAINER_GRUNT_AQUA_HIDEOUT_1 2 + #define TRAINER_GRUNT_AQUA_HIDEOUT_2 3 + #define TRAINER_GRUNT_AQUA_HIDEOUT_3 4 +``` -Just replace the constant like so, easy right? WRONG! +Just replace the constant like so. Easy, right? WRONG! -*** +## Step 5: Realizing that the opponent constant you just replaced was a Match Call trainer -* **STEP 5: REALIZING THAT THE TRAINER CONSTANT YOU JUST REPLACED IS A MATCH CALL TRAINER.** +Noticed that `TRAINER_SAWYER_1` has a `_1` after it? That's because Sawyer is a *Match Call* trainer, a trainer that can call you and ask for a rematch at a later point in the game. If you want to create your own Match Call trainers, that's something I don't know how to do yet either. I'll save writing that tutorial for someone more competent (probably me in another month or so). -Noticed that TRAINER_SAWYER_1 has a _1 after it? That's because Sawyer is a *Match Call* trainer, an Emerald feature similar to the Pokegear rematch calls in gen 2. If you want to create your own Match Call trainers that's something I don't know how to do yet either so I'll save it for someone more competent (probably me in another month or so) to write a tutorial on. +You don't have to do this part if the trainer you replaced isn't a Match Call trainer. -You don't have to do this part if the trainer you replaced isn't on a match call, but here's where you have to do some code deleting. +In [src/match_call.c](../blob/master/src/match_call.c#L115) starting on **line 115**, you'll find `sMatchCallTrainers`, which is a list of trainerIds and associated match call data. Use Ctrl+F to find the opponent constant you replaced (`TRAINER_SAWYER_1`) in the list, then delete the codeblock starting and ending with the brackets { } there. I have absolutely no idea what this may break, but your game will not build unless you cull that. -in **src\match_call.c** starting on **Line 115** you'll find a C data list of trainerIds and associated match call data. Crtl+F to find your opponent constant (TRAINER_SAWYER_1) then delete the codeblock starting and ending with the brackets { } there. I have absolutely no idea what this may break, but your game will not build unless you cull that. +## Step 6: Culling wherever in the game that old trainer of yours was referenced -*** +Because we're replacing a trainer slot and renaming its opponent constant, we also need to find anywhere that opponent constant was used and gut the scripts. -* **STEP 6: CULLING WHEREVER IN THE GAME THAT OLD TRAINER OF YOURS WAS REFERENCED** +In stock pokeemerald, you'll need to go to Mt. Chimney on Porymap and delete the overworld sprite object associated with Hiker Sawyer. -Because we're replacing a trainer slot we also need to go hunt down anywhere that opponent constant was referenced and gut the scripts. +You'll also need to comment out the lines where their scripts were. You can do this by adding `//` or `@` before every line, like so: -In my repo that I'm using as an example, all the hoenn maps themselves were already removed. But in your stock pokeemerald, you'll need to go to Mt. Chimney on porymap and delete the overworld sprite object associated with Hiker Sawyer. +``` +// MtChimney_EventScript_Sawyer:: @ 822F208 +// trainerbattle_single TRAINER_SAWYER_1, MtChimney_Text_SawyerIntro, MtChimney_Text_SawyerDefeat, MtChimney_EventScript_SawyerDefeated +// specialvar VAR_RESULT, ShouldTryRematchBattle +// compare VAR_RESULT, TRUE +// goto_if_eq MtChimney_EventScript_SawyerRematch +// msgbox MtChimney_Text_SawyerPostBattle, MSGBOX_DEFAULT +// release +// end -You'll also need to comment out the lines where their scripts were. Like so: +// MtChimney_EventScript_SawyerDefeated:: @ 822F234 +// special PlayerFaceTrainerAfterBattle +// waitmovement 0 +// msgbox MtChimney_Text_SawyerRegister, MSGBOX_DEFAULT +// register_matchcall TRAINER_SAWYER_1 +// release +// end -![](https://i.imgur.com/snFD0DR.png) +// MtChimney_EventScript_SawyerRematch:: @ 822F253 +// trainerbattle_rematch TRAINER_SAWYER_1, MtChimney_Text_SawyerRematchIntro, MtChimney_Text_SawyerRematchDefeat +// msgbox MtChimney_Text_SawyerPostRematch, MSGBOX_AUTOCLOSE +// end +``` -*** +## Step 7: Adding in the new text for your trainer -* **STEP 7: ADDING IN THE NEW TEXT FOR YOUR TRAINER** +In base pokeemerald, all the text for trainers is stored in [data/text/trainers.inc](../blob/master/data/text/trainers.inc). You don't *have* to put your new trainer's text here; you can instead put your new text pointers right underneath your trainer script in your map's scripts file. Once you've found the place to put your trainer's text, add it in like so: -In base pokeemerald, I guess because gamefreak decided to shove it all in another bank or something, all the text for trainers is stored in **data\text\trainers.inc** +```diff + Route2_EventScript_BCTim:: + trainerbattle_single TRAINER_BC_TIM, Route2_Text_BCTimIntro, Route2_Text_BCTimDefeated + msgbox Route2_Text_BCTimPostBattle, MSGBOX_AUTOCLOSE + end + ++Route2_Text_BCTimIntro: ++ .string "What's the matter? Scared of some\n" ++ .string "little spiders?$" ++ ++Route2_Text_BCTimDefeated: ++ .string "Tangled in a web of defeat!$" ++ ++Route2_Text_BCTimPostBattle: ++ .string "Arachnophobia? Now why would they need\n" ++ .string "to go and invent a word like that?$" +``` -You don't *have* to put your new trainer's text here. I mean you can if you want to be like gamefreak, but who wants to be like them? You can put your new text pointers right underneath your trainer script on your map's scripts file like so: - -![](https://i.imgur.com/2tC59mB.png) - -What's that? How do you format those lines of text? My son, my lad, let me teach you a trick to save you time and keep you from having to count out characters per line line a total troglodyte: abusing poryscript playground's automatic text formatting function! +What's that? How do you format those lines of text? My son, my lad, let me teach you a trick to save you time and keep you from having to count out pixels per line line a total troglodyte: abusing Poryscript playground's automatic text formatting function! Just go on [this page](https://www.huderlem.com/poryscript-playground/?switches=LANG=EN%20GAME_VERSION=EMERALD) and delete everything from the example script but the msgbox(format part, then paste in your new unformatted text like so and voila, instantly formatted text that won't overdraw the textboxes!: ![](https://i.imgur.com/OJbYAqF.png) -I mean, technically you could write your whole script with poryscript too. But I don't know how to do that so at least you can format your text instantly with it. +I mean, technically you could write your whole script with poryscript too. But I don't know how to do that, so at least you can format your text instantly with it. -*** +## Step 8: Tying your trainer script to the trainer in the map -* **STEP 8: TYING YOUR TRAINER SCRIPT TO THE TRAINER ON PORYMAP** +Now, go back to Porymap and change the part on your NPC that says Script (NULL) by copying and pasting the script label that marks the beginning of your trainer's script here. In this case `Route2_EventScript_BCTim`. -Now, go back to porymap and change the part on your NPC that says Script (NULL) by copy and pasting the script header that points to your trainer's script here. In this case **Route2_EventScript_BCTim**. +## Step 9: Defining your trainer's sprite, encounter music, trainer class, AI, and name -*** +For the next step, we'll open up [src/data/trainers.h](../blob/master/src/data/trainers.h) and find the data associated with the opponent constant we replaced. The final result will look like this: -* **STEP 9: DEFINING YOUR TRAINER'S SPRITE, ENCOUNTER MUSIC, TRAINER CLASS, AI, AND NAME** - -For the next step we'll open up **src\data\trainers.h**. - -![](https://i.imgur.com/Y6PEMHj.png) +```c + [TRAINER_BC_TIM] = + { + .partyFlags = 0, + .trainerClass = TRAINER_CLASS_BUG_CATCHER, + .encounterMusic_gender = TRAINER_ENCOUNTER_MUSIC_HIKER, + .trainerPic = TRAINER_PIC_BUG_CATCHER, + .trainerName = _("TIM"), + .items = {}, + .doubleBattle = FALSE, + .aiFlags = AI_SCRIPT_CHECK_BAD_MOVE | AI_SCRIPT_TRY_TO_FAINT | AI_SCRIPT_CHECK_VIABILITY, + .partySize = ARRAY_COUNT(sParty_BCTim), + .party = {.NoItemDefaultMoves = sParty_BCTim}, + }, +``` Here is where everything related to the trainer is linked to the opponent constant. You'll find another huge data list of every trainer in the game. We set quite a few parameters here: -**.partyflags** - -I don't know what this one does! Every trainer in stock Emerald has it's value set to 0. Don't worry about it I guess. - -**.trainerClass** - -Self explanatory, there's a list of class constants to be found in **include\constants\trainers.h** - -**.encounterMusic_gender** - -The music that plays when the trainer walks up to battle. A list of constants can be found in **include\constants\songs.h** - -**.trainerPic** - -Which sprite the trainer uses, again a list is found in **include\constants\trainers.h** - -**.items** - -What items (if any) the trainer uses in battle, with a limit of four. For example here's Flannery's items in her first gym battle: -`{ITEM_HYPER_POTION, ITEM_HYPER_POTION, ITEM_NONE, ITEM_NONE},` - -A list can be found in **include\constants\items.h** - -**.doublebattle** - -FALSE or TRUE, you know the deal. - -**.aiFlags** - -These determine which AI scripts the trainer is allowed to follow, a list can be found in **include\constants\battle_ai.h** - -**.partySize and .party** - -These two are POINTERS that need to point to the relevant data for your trainer as labeled in trainer_parties.h +* `.partyFlags`: Tells the game whether this trainer will have Pokemon with custom moves (`F_TRAINER_PARTY_CUSTOM_MOVESET`) and/or held items (`F_TRAINER_PARTY_HELD_ITEM`). 0 means they have neither. +* `.trainerClass`: Determines the title the trainer's name is prefixed with (e.g. "BUG CATCHER" or "HIKER") and how much money they'll give when they're defeated. There's a list of class constants to be found in [include/constants/trainers.h](../blob/master/include/constants/trainers.h). +* `.encounterMusic_gender`: The music that plays when the trainer walks up to battle. A list of constants can be found in [include/constants/trainers.h](../blob/master/include/constants/trainers.h#L354). +* `.trainerPic`: Which sprite the trainer uses. Again, a list is found in [include/constants/trainers.h](../blob/master/include/constants/trainers.h#L19). +* `.items`: What items (if any) the trainer uses in battle, with a limit of four. For example here's Flannery's items in her first gym battle: +`{ITEM_HYPER_POTION, ITEM_HYPER_POTION, ITEM_NONE, ITEM_NONE}`. A list of item constants can be found in [include/constants/items.h](../blob/master/include/constants/items.h). +* `.doubleBattle`: FALSE or TRUE, depending on whether battling this trainer should start a double battle. Note that double battles also require a slightly different script compared to a regular trainer battle and include an extra line of text; this field alone is not enough. +* `.aiFlags`: These determine which AI scripts the trainer is allowed to follow. A list can be found in [include/constants/battle_ai.h](../blob/master/include/constants/battle_ai.h#L37). +* `.partySize` and `.party`: These two are POINTERS that need to point to the relevant data for your trainer as labeled in trainer_parties.h Which brings us to our last major part: -*** +## Step 10: Defining the trainer's party Pokemon -* **STEP 10: DEFINING THE TRAINER'S PARTY POKEMON** +For this part we'll be in [src/data/trainer_parties.h](../blob/master/src/data/trainer_parties.h): -For this part we'll be in **src\data\trainer_parties.h** +```c +static const struct TrainerMonNoItemDefaultMoves sParty_BCTim[] = { + { + .iv = 0, + .lvl = 3, + .species = SPECIES_SPINARAK, + }, + { + .iv = 0, + .lvl = 4, + .species = SPECIES_JOLTIK, + } +}; +``` -![](https://i.imgur.com/TffvZ1k.png) +Here we get to give the trainer we made their data! For Bug Catcher Tim, the two defined Pokemon only have two parameters, `.iv` and `.lvl` along with their [species constants](../blob/master/include/constants/species.h). -Here we get to give the trainer we made their data! On our simple Bug Catcher Tim the two defined Pokemon only have two parameters **.iv** and **.lvl** along with their species constants. - -**.lvl** is obviously the level, but if you look around at the parties in stock Emerald you'll quickly notice the **.iv** parameter isn't simply IVs from 0-31. This value actually runs from 0-255, here's a formula from the code someone handed me that explains how it calculates out usable IVs from these numbers: - -`fixedIV = partyData[i].iv * 31 / 255;` - -Understand? +`.lvl` is obviously the Pokemon's level, but if you look around at the parties in stock Emerald you'll quickly notice the **.iv** parameter isn't simply IVs from 0-31. This value actually runs from 0-255. The game does math to scale this value into the proper 0-31 range. 255 becomes the highest possible IV (31), 0 becomes the lowest possible IV (0). IVs are the same for all stats. Anyway, Tim is a simple fella with simple mons. But what about Gym Leaders and opponents with craftier strategies like custom movesets and held items on their Pokemon? Well, have a look at Flannery's team from stock Emerald: -![](https://i.imgur.com/jFYC9Vd.png) +```c +static const struct TrainerMonItemCustomMoves sParty_Flannery1[] = { + { + .iv = 200, + .lvl = 24, + .species = SPECIES_NUMEL, + .heldItem = ITEM_NONE, + .moves = {MOVE_OVERHEAT, MOVE_TAKE_DOWN, MOVE_MAGNITUDE, MOVE_SUNNY_DAY} + }, + { + .iv = 200, + .lvl = 24, + .species = SPECIES_SLUGMA, + .heldItem = ITEM_NONE, + .moves = {MOVE_OVERHEAT, MOVE_SMOG, MOVE_LIGHT_SCREEN, MOVE_SUNNY_DAY} + }, + { + .iv = 250, + .lvl = 26, + .species = SPECIES_CAMERUPT, + .heldItem = ITEM_NONE, + .moves = {MOVE_OVERHEAT, MOVE_TACKLE, MOVE_SUNNY_DAY, MOVE_ATTRACT} + }, + { + .iv = 250, + .lvl = 29, + .species = SPECIES_TORKOAL, + .heldItem = ITEM_WHITE_HERB, + .moves = {MOVE_OVERHEAT, MOVE_SUNNY_DAY, MOVE_BODY_SLAM, MOVE_ATTRACT} + } +}; +``` -As you can see, at the top where Tim had TrainerMonNoItemDefaultMoves, Flannery has TrainerMonItemCustomMoves to declare that she is a baller and uses Pokemon with items and custom moves. +As you can see, at the top where Tim had TrainerMonNoItemDefaultMoves, Flannery has TrainerMonItemCustomMoves to declare that she is a baller and uses Pokemon with items and custom moves. Note that TrainerMonNoItemDefaultMoves vs TrainerMonItemCustomMoves vs other variants need to match `.partyFlags` and `.party` from the previous step. -the **.moves** and **.item** macros are also equally straightforward. Just be sure to also define the empty item and move slots as Flannery's team does here. +The **.moves** and **.item** fields are also equally straightforward. Just be sure to also define the empty item and move slots, as Flannery's team does here. -*** +## Step 11: Compile and test that sucker in-game -* **Step 11: COMPILE AND TEST THAT SUCKER INGAME** - -With all our preparations complete it's time to see Tim in the flesh. MAKE your game and see if he works! +With all our preparations complete, it's time to see Tim in the flesh. MAKE your game and see if he works! I bet he does, after all, you followed a tutorial :^)