mirror of
https://github.com/pret/pokeemerald.git
synced 2026-09-07 10:36:05 -05:00
Merge branch '_RHH/upcoming' into _RHH/pr/upcoming/lighting-expansion-v2
This commit is contained in:
395
docs/tutorials/how_to_code_entry.md
Normal file
395
docs/tutorials/how_to_code_entry.md
Normal file
@@ -0,0 +1,395 @@
|
||||
## How to use the code entry system
|
||||
|
||||
This system involves using the `EnterCode` special to prompt the player to enter a text string, and then the `GetCodeFeedback` special to perform some function based on the string entered. Using this system to make your own cheat codes or mystery gifts will involve both scripting and editing the `GetCodeFeedback` function itself to include your new functionality, and may involve further changes to the codebase if you want to implement something more far reaching (ie. a grindrunning mode).
|
||||
|
||||
This tutorial will use the example of entering the string "CaughtEmAll" to flag every Pokemon as caught
|
||||
|
||||
### 1. Choose where to initiaze your event scripting
|
||||
|
||||
This can be anywhere or anything, pre-existing or added by you in porymap. I usually like using signs for testing things but this can be anything. I'm going to give the main script a more generic name, and you can attach it to whatever object you like.
|
||||
|
||||
In that object's event script, add the following:
|
||||
```diff
|
||||
EventScript_CodeEntry::
|
||||
special EnterCode
|
||||
waitstate
|
||||
special GetCodeFeedback
|
||||
end
|
||||
```
|
||||
|
||||
This will prompt text entry from the object and prepare it to handle reading the entered text after it's been entered, but it won't do anything yet. Next we need to add our functionality to `GetCodeFeedback`.
|
||||
|
||||
### 2. Add code string and code function to `GetCodeFeedback`
|
||||
|
||||
You can find `GetCodeFeedback` in `src/field_specials.c`. Let's start by taking a look at the function:
|
||||
```
|
||||
void GetCodeFeedback(void)
|
||||
{
|
||||
static const u8 sText_SampleCode[] = _("SampleCode");
|
||||
if (!StringCompare(gStringVar2, sText_SampleCode))
|
||||
gSpecialVar_Result = 1;
|
||||
else
|
||||
gSpecialVar_Result = 0;
|
||||
}
|
||||
```
|
||||
|
||||
What this function does is compare the input string (`gStringVar2`) against a specified string (`sText_SampleCode`) and returns a value depending on whether the strings matched (`gSpecialVar_Result`). Note that due to the way `StringCompare` works, the comparison does need to be negated with `!`. By default, this sample setup returns 1 when the string "SampleCode" is entered by the player.
|
||||
|
||||
Let's leave that functionality alone in case we ever want to reference it again, and just add a brand new code instead. We want to use the string "CaughtEmAll" as our code, so we'll start by making a string for it, and a new conditional that checks if the entered string matches. We'll also want to make sure we return a new unique number for `gSpecialVar_Result` so our event script knows what happened.
|
||||
|
||||
```diff
|
||||
void GetCodeFeedback(void)
|
||||
{
|
||||
static const u8 sText_SampleCode[] = _("SampleCode");
|
||||
+ static const u8 sText_CaughtEmAll_[] = _("CaughtEmAll"); // Mark entire Pokedex as caught
|
||||
if (!StringCompare(gStringVar2, sText_SampleCode))
|
||||
gSpecialVar_Result = 1;
|
||||
+ else if (!StringCompare(gStringVar2, sText_CaughtEmAll))
|
||||
+ {
|
||||
+ // TODO
|
||||
+ gSpecialVar_Result = 2;
|
||||
+ }
|
||||
else
|
||||
gSpecialVar_Result = 0;
|
||||
}
|
||||
```
|
||||
|
||||
Great! Now we have a new case to handle our new code, but it still doesn't do anything. This is the part that will change dramatically depending on what you want to do, and you can do anything you want, from setting flags to calling other functions or anything else! Just make sure you do it from within the conditional that matches your code. In our case we want to iterate through the Pokedex to mark everything as caught, which I'll just do here for simplicity.
|
||||
|
||||
```diff
|
||||
void GetCodeFeedback(void)
|
||||
{
|
||||
static const u8 sText_SampleCode[] = _("SampleCode");
|
||||
static const u8 sText_CaughtEmAll_[] = _("CaughtEmAll"); // Mark entire Pokedex as caught
|
||||
if (!StringCompare(gStringVar2, sText_SampleCode))
|
||||
gSpecialVar_Result = 1;
|
||||
else if (!StringCompare(gStringVar2, sText_CaughtEmAll))
|
||||
{
|
||||
+ u32 i;
|
||||
+ for (i = 0; i < NATIONAL_DEX_COUNT; i++)
|
||||
+ {
|
||||
+ GetSetPokedexFlag(i + 1, FLAG_SET_CAUGHT);
|
||||
+ }
|
||||
gSpecialVar_Result = 2;
|
||||
}
|
||||
else
|
||||
gSpecialVar_Result = 0;
|
||||
}
|
||||
```
|
||||
|
||||
Awesome! Now our `GetCodeFeedback` function performs the task we want it to, and returns a 2 to our event script so it can handle the situation appropriately. That's our next and final step!
|
||||
|
||||
### 3. Handle new `GetCodeFeedback` case in event script
|
||||
|
||||
To clarify, this step is *optional*. You don't need to do anything else after `GetCodeFeedback` has run if you don't want to, as all the functionality is there; once that function finishes, everything in the Pokedex will be marked as caught.
|
||||
|
||||
The reason we might want to do this step, and the reason we pass results back to the event script in the first place, is so we can handle providing the player with some dialogue based on what they're doing.
|
||||
|
||||
Let's go back to our event script.
|
||||
|
||||
```
|
||||
EventScript_CodeEntry::
|
||||
special EnterCode
|
||||
waitstate
|
||||
special GetCodeFeedback
|
||||
end
|
||||
```
|
||||
|
||||
Maybe we first want to prompt the player with a message that says something like "Enter a code?"
|
||||
|
||||
```diff
|
||||
EventScript_CodeEntry::
|
||||
+ lockall
|
||||
+ msgbox EnterCode_EnterCodeText, MSGBOX_YESNO
|
||||
+ compare VAR_RESULT, 0
|
||||
+ goto_if_eq CodeExit
|
||||
special EnterCode
|
||||
waitstate
|
||||
special GetCodeFeedback
|
||||
end
|
||||
|
||||
+CodeExit::
|
||||
+ releaseall
|
||||
+ end
|
||||
+
|
||||
+EnterCode_EnterCodeText:
|
||||
+ .string "Enter a code?$"
|
||||
```
|
||||
|
||||
This is all straightforward scripting stuff, the sign will first give the player a YES / NO box and ask whether they'd like to enter a code. Let's now add some cases and messages that handle the different results of the code entry from `GetCodeFeedback`. Let's look at the sign first:
|
||||
|
||||
```diff
|
||||
EventScript_CodeEntry::
|
||||
lockall
|
||||
msgbox EnterCode_EnterCodeText, MSGBOX_YESNO
|
||||
compare VAR_RESULT, 0
|
||||
goto_if_eq CodeExit
|
||||
special EnterCode
|
||||
waitstate
|
||||
special GetCodeFeedback
|
||||
+ goto_if_eq VAR_RESULT, 0, CodeFailed
|
||||
+ goto_if_eq VAR_RESULT, 1, CodeSampleCode
|
||||
+ goto_if_eq VAR_RESULT, 2, CodeCaughtEmAll
|
||||
end
|
||||
```
|
||||
|
||||
Now we're handling cases for each of the possible return values from `GetCodeFeedback`! except we don't have any of those functions, so this will cause errors as the script has nothing to `goto_if_eq`. Let's write those too:
|
||||
|
||||
```diff
|
||||
CodeFailed::
|
||||
msgbox EnterCode_FailedText, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
|
||||
CodeSampleString::
|
||||
msgbox EnterCode_SucceededText, MSGBOX_DEFAULT
|
||||
msgbox CodeSampleCode_Text, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
|
||||
CodeCaughtEmAll::
|
||||
msgbox EnterCode_SucceededText, MSGBOX_DEFAULT
|
||||
msgbox CodeCaughtEmAll_Text, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
```
|
||||
|
||||
And lastly, we'll need to add all of the strings we now need to reference:
|
||||
|
||||
```
|
||||
EnterCode_FailedText:
|
||||
.string "...nothing happened.$"
|
||||
|
||||
EnterCode_SucceededText:
|
||||
.string "The code worked!$"
|
||||
|
||||
CodeSampleCode_Text
|
||||
.string "You entered the sample code!$"
|
||||
|
||||
CodeCaughtEmAll_Text
|
||||
.string "Encyclopedic knowledge fills your head.\n"
|
||||
.string "It's like you've caught 'em all!$"
|
||||
```
|
||||
|
||||
So to finish up, our event script file now looks like this, with all said and done:
|
||||
|
||||
```
|
||||
EventScript_CodeEntry::
|
||||
lockall
|
||||
msgbox EnterCode_EnterCodeText, MSGBOX_YESNO
|
||||
compare VAR_RESULT, 0
|
||||
goto_if_eq CodeExit
|
||||
special EnterCode
|
||||
waitstate
|
||||
special GetCodeFeedback
|
||||
goto_if_eq VAR_RESULT, 0, CodeFailed
|
||||
goto_if_eq VAR_RESULT, 1, CodeSampleCode
|
||||
goto_if_eq VAR_RESULT, 2, CodeCaughtEmAll
|
||||
end
|
||||
|
||||
CodeExit::
|
||||
releaseall
|
||||
end
|
||||
|
||||
CodeFailed::
|
||||
msgbox EnterCode_FailedText, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
|
||||
CodeSampleString::
|
||||
msgbox EnterCode_SucceededText, MSGBOX_DEFAULT
|
||||
msgbox CodeSampleCode_Text, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
|
||||
CodeCaughtEmAll::
|
||||
msgbox EnterCode_SucceededText, MSGBOX_DEFAULT
|
||||
msgbox CodeCaughtEmAll_Text, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
|
||||
EnterCode_EnterCodeText:
|
||||
.string "Enter a code?$"
|
||||
|
||||
EnterCode_FailedText:
|
||||
.string "...nothing happened.$"
|
||||
|
||||
EnterCode_SucceededText:
|
||||
.string "The code worked!$"
|
||||
|
||||
CodeSampleCode_Text
|
||||
.string "You entered the sample code!$"
|
||||
|
||||
CodeCaughtEmAll_Text
|
||||
.string "Encyclopedic knowledge fills your head.\n"
|
||||
.string "It's like you've caught 'em all!$"
|
||||
|
||||
```
|
||||
|
||||
And that's it! Feel free to expand this in whatever way you wish, the pattern can just be repeated as much as you like, and you can made the code called from `GetCodeFeedback` do whatever you like.
|
||||
|
||||
## Can I change the icon on the name entry screen?
|
||||
|
||||
Absolutely! In `naming_screen.c`, look for the `NamingScreen_CreateCodeIcon` function. It's very short. There's one relevant line that needs to be changed:
|
||||
|
||||
```
|
||||
spriteId = CreateObjectGraphicsSprite(OBJ_EVENT_GFX_MYSTERY_GIFT_MAN, SpriteCallbackDummy, 56, 37, 0);
|
||||
```
|
||||
|
||||
Just swap out `OBJ_EVENT_GFX_MYSTERY_GIFT_MAN` for whatever event object sprite you'd like to use instead. You may need to adjust the position (the 56 and 37 in this example) depending on your sprite.
|
||||
|
||||
## What about a mystery gift setup?
|
||||
|
||||
I'd like to cover this separately because it's best handled via `givemon` script commands, which means we don't do much in `GetCodeFeedback` other than return a unique identifier. I'm gonna reference @PCG06's mystery gift implementation which is based on this code entry system for a clean and really thorough example.
|
||||
|
||||
### 3. Mystery Gift `GetCodeFeedback`
|
||||
|
||||
Let's say you have two mystery gift mons and no other cases you want to handle, one for Celebi and one for Jirachi. Your `GetCodeFeedback` function will look something like this:
|
||||
|
||||
```
|
||||
{
|
||||
static const u8 sText_CodeCelebi[] = _("Celebi");
|
||||
static const u8 sText_CodeJirachi[] = _("Jirachi");
|
||||
if (!StringCompare(gStringVar2, sText_CodeCelebi))
|
||||
gSpecialVar_Result = 1;
|
||||
else if (!StringCompare(gStringVar2, sText_CodeJirachi))
|
||||
gSpecialVar_Result = 2;
|
||||
else
|
||||
gSpecialVar_Result = 0;
|
||||
}
|
||||
```
|
||||
and that's it, super simple. All of the other handling will have to be done on the scripting end, as we'll be leaning on `givemon` and its associated handling.
|
||||
|
||||
### 2. Mystery Gift Scripting
|
||||
|
||||
Let's return back to our EventScript_CodeEntry pattern from before, but instead use our new codes.
|
||||
|
||||
```
|
||||
EventScript_CodeEntry::
|
||||
lockall
|
||||
msgbox EnterCode_EnterCodeText, MSGBOX_YESNO
|
||||
compare VAR_RESULT, 0
|
||||
goto_if_eq CodeExit
|
||||
special EnterCode
|
||||
waitstate
|
||||
special GetCodeFeedback
|
||||
goto_if_eq VAR_RESULT, 0, CodeFailed
|
||||
goto_if_eq VAR_RESULT, 1, MysteryGift_EventScript_Celebi
|
||||
goto_if_eq VAR_RESULT, 2, MysteryGift_EventScript_Jirachi
|
||||
end
|
||||
```
|
||||
|
||||
Straightforward enough! The actual work is in writing `MysteryGift_EventScript_Celebi` and `MysteryGift_EventScript_Jirachi` to handle their givemons appropriately, prompt nicknaming, send them to the PC if the party is full, etc. We should also keep in mind that each Mystery Gift should only be entered once, so we'll want to track that with a flag; conveniently, expansion already has 15 flags we can use for the purpose. Let's do Celebi first.
|
||||
|
||||
```
|
||||
MysteryGift_EventScript_Celebi::
|
||||
goto_if_set FLAG_MYSTERY_GIFT_1, MysteryGift_EventScript_Redeemed
|
||||
bufferspeciesname STR_VAR_1, SPECIES_CELEBI
|
||||
setvar VAR_TEMP_TRANSFERRED_SPECIES, SPECIES_CELEBI
|
||||
givemon SPECIES_CELEBI, 100, ITEM_LIFE_ORB, ITEM_CHERISH_BALL, NATURE_TIMID, 0, MON_GENDERLESS, 0, 0, 4, 252, 252, 0, 31, 31, 31, 30, 31, 31, MOVE_ENERGY_BALL, MOVE_PSYCHIC, MOVE_NASTY_PLOT, MOVE_CELEBRATE, TRUE, FALSE, TYPE_PSYCHIC
|
||||
setflag FLAG_MYSTERY_GIFT_1
|
||||
call MysteryGift_EventScript_ReceivedMon
|
||||
releaseall
|
||||
end
|
||||
```
|
||||
|
||||
Walking through this, it's clear we'll need some more scripting. We first check if Celebi's corresponding Mystery Gift flag has been set, and if it has, we need to tell the player they've already redeemed it and can't again. If they haven't though, we get ourselves setup for the givemon, do the givemon, and set the mystery gift flag. Then we need soem more generic handling to prompt nicknaming and some fanfare.
|
||||
|
||||
Two things, then; an event script to handle the case where a mystery gift mon has already been redeemed, and an event script to handle when a mystery gift mon has successfully been received.
|
||||
|
||||
Just for the sake of simplicity, I'm going to handle entering a used mystery gift code the same way I'd handle an incorrect code. You're welcome to add more complex scripting if you prefer.
|
||||
|
||||
```
|
||||
MysteryGift_EventScript_Redeemed::
|
||||
msgbox EnterCode_FailedText, MSGBOX_DEFAULT
|
||||
releaseall
|
||||
end
|
||||
```
|
||||
|
||||
And then the scripto handle the player having successfully received a mon:
|
||||
|
||||
```
|
||||
MysteryGift_EventScript_ReceivedMon::
|
||||
msgbox MysteryGift_Text_SucceededText, MSGBOX_DEFAULT
|
||||
playfanfare MUS_OBTAIN_ITEM
|
||||
message MysteryGift_Text_ReceivedGiftMon
|
||||
waitfanfare
|
||||
goto_if_eq VAR_RESULT, MON_GIVEN_TO_PARTY, MysteryGift_EventScript_NicknamePartyMon
|
||||
goto_if_eq VAR_RESULT, MON_GIVEN_TO_PC, MysteryGift_EventScript_NicknamePCMon
|
||||
goto Common_EventScript_NoMoreRoomForPokemon
|
||||
msgbox MysteryGift_Text_PleaseVisitAgain, MSGBOX_DEFAULT
|
||||
end
|
||||
```
|
||||
|
||||
Almost done! Just need to handle the specific nicknaming scripts, and then add all of the text.
|
||||
|
||||
```
|
||||
MysteryGift_EventScript_NicknamePartyMon::
|
||||
msgbox gText_NicknameThisPokemon, MSGBOX_YESNO
|
||||
goto_if_eq VAR_RESULT, NO, MysteryGift_EventScript_Exit
|
||||
call Common_EventScript_GetGiftMonPartySlot
|
||||
call Common_EventScript_NameReceivedPartyMon
|
||||
goto MysteryGift_EventScript_Exit
|
||||
end
|
||||
|
||||
MysteryGift_EventScript_NicknamePCMon::
|
||||
msgbox gText_NicknameThisPokemon, MSGBOX_YESNO
|
||||
goto_if_eq VAR_RESULT, NO, MysteryGift_EventScript_TransferredToPC
|
||||
call Common_EventScript_NameReceivedBoxMon
|
||||
call Common_EventScript_TransferredToPC
|
||||
releaseall
|
||||
end
|
||||
|
||||
MysteryGift_EventScript_TransferredToPC::
|
||||
call Common_EventScript_TransferredToPC
|
||||
releaseall
|
||||
end
|
||||
|
||||
MysteryGift_Text_WelcomeToMysteryGiftSystem:
|
||||
.string "Hello, {PLAYER}!\p"
|
||||
.string "Welcome to the Mystery Gift System!\p"
|
||||
.string "Would you like to enter a code?$"
|
||||
|
||||
MysteryGift_Text_CurrentlyUnavailable:
|
||||
.string "I'm sorry, but the Mystery Gift System\n"
|
||||
.string "is currently unavailable.\p"
|
||||
.string "Please try again later.\p"
|
||||
.string "Thank you!$"
|
||||
|
||||
MysteryGift_Text_PleaseVisitAgain:
|
||||
.string "Please visit again!$"
|
||||
|
||||
MysteryGift_Text_EnterCode:
|
||||
.string "Please enter the code.$"
|
||||
|
||||
MysteryGift_Text_SucceededText:
|
||||
.string "The code was valid!\p"
|
||||
.string "Enjoy your gift!$"
|
||||
|
||||
MysteryGift_Text_FailedText:
|
||||
.string "The code was invalid!\p"
|
||||
.string "Would you like to enter a new code?$"
|
||||
|
||||
MysteryGift_Text_RedeemedText:
|
||||
.string "This code was already redeemed!\p"
|
||||
.string "Would you like you enter a new code?$"
|
||||
|
||||
MysteryGift_Text_ReceivedGiftMon:
|
||||
.string "{PLAYER} received a {STR_VAR_1}!$"
|
||||
```
|
||||
|
||||
Goodness, so much infrastructure scripting! The nice thing is that now that all the infrastructure is set up, much like before, adding new cases becomes really straightforward. With Celebi and all of the skeleton scripting finished, let's add Jirachi.
|
||||
|
||||
```
|
||||
MysteryGift_EventScript_Jirachi::
|
||||
goto_if_set FLAG_MYSTERY_GIFT_2, MysteryGift_EventScript_Redeemed
|
||||
bufferspeciesname STR_VAR_1, SPECIES_JIRACHI
|
||||
setvar VAR_TEMP_TRANSFERRED_SPECIES, SPECIES_JIRACHI
|
||||
givemon SPECIES_JIRACHI, 100, ITEM_LIFE_ORB, ITEM_CHERISH_BALL, NATURE_ADAMANT, 0, MON_GENDERLESS, 0, 252, 4, 252, 0, 0, 31, 31, 31, 31, 31, 31, MOVE_IRON_HEAD, MOVE_ZEN_HEADBUTT, MOVE_PLAY_ROUGH, MOVE_CELEBRATE, TRUE, FALSE, TYPE_STEEL
|
||||
setflag FLAG_MYSTERY_GIFT_2
|
||||
call MysteryGift_EventScript_ReceivedMon
|
||||
releaseall
|
||||
end
|
||||
```
|
||||
|
||||
And that's it! Super straightforward from here, just make sure to iterate `FLAG_MYSTERY_GIFT` each time you add a new mon, and of course add their code to both `GetCodeFeedback` and the main script controlling code entry.
|
||||
@@ -70,11 +70,11 @@ If your new Trainer Slide needs to check for beforen initalized, a function is d
|
||||
|
||||
```diff
|
||||
void SetTrainerSlideMessage(enum DifficultyLevel, u32, u32);
|
||||
void TryInitalizeFirstSTABMoveTrainerSlide(u32, u32, u32);
|
||||
void TryInitalizeTrainerSlidePlayerLandsFirstCriticalHit(u32);
|
||||
+ void TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit(u32);
|
||||
void TryInitalizeTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32);
|
||||
void TryInitalizeTrainerSlideEnemyMonUnaffected(u32);
|
||||
void TryInitializeFirstSTABMoveTrainerSlide(u32, u32, u32);
|
||||
void TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(u32);
|
||||
+ void TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(u32);
|
||||
void TryInitializeTrainerSlidePlayerLandsFirstSuperEffectiveHit(u32);
|
||||
void TryInitializeTrainerSlideEnemyMonUnaffected(u32);
|
||||
bool32 IsTrainerSlideInitialized(enum TrainerSlideType);
|
||||
```
|
||||
### `src/trainer_slide.c`
|
||||
@@ -111,7 +111,7 @@ The function that determines if a Slide should play has different function for m
|
||||
InitalizeTrainerSlide(slideId);
|
||||
}
|
||||
|
||||
+void TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit(u32 target)
|
||||
+void TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(u32 target)
|
||||
+{
|
||||
+ enum TrainerSlideType slideId = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT;
|
||||
+
|
||||
@@ -150,13 +150,13 @@ In `BattleTurnPassed`, most Trainer Slides are checked to see if they should run
|
||||
{
|
||||
PrepareStringBattle(STRINGID_CRITICALHIT, gBattlerAttacker);
|
||||
|
||||
+ TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit(gBattlerTarget);
|
||||
TryInitalizeTrainerSlidePlayerLandsFirstCriticalHit(gBattlerTarget);
|
||||
+ TryInitializeTrainerSlideEnemyLandsFirstCriticalHit(gBattlerTarget);
|
||||
TryInitializeTrainerSlidePlayerLandsFirstCriticalHit(gBattlerTarget);
|
||||
|
||||
gBattleCommunication[MSG_DISPLAY] = 1;
|
||||
```
|
||||
|
||||
The actual usage of `TryInitalizeTrainerSlideEnemyLandsFirstCriticalHit` is added and is checked whenever a critical hit is scored.
|
||||
The actual usage of `TryInitializeTrainerSlideEnemyLandsFirstCriticalHit` is added and is checked whenever a critical hit is scored.
|
||||
|
||||
### `test/battle/trainer_slides.c`
|
||||
```diff
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
# How to add a new trainer class
|
||||
|
||||
## Content
|
||||
* [Quick Summary](#quick-summary)
|
||||
* [The Graphics](#the-graphics)
|
||||
* [1. Edit the sprites](#2-edit-the-sprites)
|
||||
* [2. Register the sprites](#2-register-the-sprites)
|
||||
* [3. The Animation](#2-the-animation)
|
||||
* [4. Connecting pictures to the data](#2-connecting-pictures-to-the-data)
|
||||
* [The Data](#the-data)
|
||||
* [5. Defining the trainer class](#2-defining-the-trainer-class)
|
||||
* [Usage](#usage)
|
||||
|
||||
## Quick Summary
|
||||
(Page contains out of date information, [new instructions for Sprites here](https://github.com/rh-hideout/pokeemerald-expansion/pull/3597).)
|
||||
If you've done this before and just need a quick lookup, here's what files you need:
|
||||
1. GFX into [graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics)
|
||||
2. Palette into [graphics/trainers/palettes](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/palettes)
|
||||
3. Register sprites to [include/graphics.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/graphics.h)
|
||||
4. Point game to where graphic files are found: [src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h)
|
||||
5. Add animation to: [src/data/trainer_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_anims.h)
|
||||
6. Add the trainer to all three structs in: [src/data/trainer_graphics/front_pic_table.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_table.h)
|
||||
7. Add trainer to [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h)
|
||||
|
||||
## The Graphics
|
||||
|
||||
### 1. Edit the sprites
|
||||
We will start with a graphic that we want to use for our new trainer class. Unlike with adding Pokémon, the trainer sprites aren't sorted in individual folders, but rather in one folder:
|
||||
[graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics)
|
||||
|
||||
**Remember to limit yourself to 16 colors including transparency in the first slot!**
|
||||
|
||||
Export the pallette and place into the same folder.
|
||||
|
||||
### 2. Register the sprites
|
||||
Sadly, just putting the image files into the graphics folder is not enough. To use the sprites we have to register them, which is kind of tedious. First, create constants for the file paths.
|
||||
Edit [include/graphics.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/graphics.h):
|
||||
```diff
|
||||
extern const u32 gTrainerFrontPic_RubySapphireMay[];
|
||||
+ extern const u32 gTrainerFrontPic_myTrainerClass[];
|
||||
|
||||
extern const u32 gTrainerPalette_Hiker[];
|
||||
...
|
||||
|
||||
...
|
||||
extern const u32 gTrainerPalette_RubySapphireMay[];
|
||||
+ extern const u32 gTrainerPalette_myTrainerClass[];
|
||||
|
||||
extern const u8 gTrainerBackPic_Brendan[];
|
||||
```
|
||||
|
||||
Now link the graphic files.
|
||||
[src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h):
|
||||
```diff
|
||||
const u32 gTrainerPalette_RubySapphireBrendan[] = INCBIN_U32("graphics/trainers/palettes/ruby_sapphire_brendan.gbapal.lz");
|
||||
|
||||
const u32 gTrainerFrontPic_RubySapphireMay[] = INCBIN_U32("graphics/trainers/front_pics/ruby_sapphire_may_front_pic.4bpp.lz");
|
||||
const u32 gTrainerPalette_RubySapphireMay[] = INCBIN_U32("graphics/trainers/palettes/ruby_sapphire_may.gbapal.lz");
|
||||
|
||||
+ const u32 gTrainerFrontPic_Sheriff[] = INCBIN_U32("graphics/trainers/front_pics/myTrainerClass_front_pic.4bpp.lz");
|
||||
+ const u32 gTrainerPalette_Sheriff[] = INCBIN_U32("graphics/trainers/palettes/myTrainerClass.gbapal.lz");
|
||||
|
||||
const u8 gTrainerBackPic_Brendan[] = INCBIN_U8("graphics/trainers/back_pics/brendan_back_pic.4
|
||||
```
|
||||
|
||||
### 3. The Animation
|
||||
Add the Animation of the trainer here:
|
||||
[src/data/trainer_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_anims.h)
|
||||
|
||||
The trainers don't really move, but in theory they could, it's just that the animation defined for each trainer just shows one frame:
|
||||
|
||||
```diff
|
||||
static const union AnimCmd *const sAnims_RubySapphireMay[] ={
|
||||
sAnim_GeneralFrame0,
|
||||
};
|
||||
|
||||
+ static const union AnimCmd *const sAnims_MyTrainerClass[] ={
|
||||
+ sAnim_GeneralFrame0,
|
||||
+ };
|
||||
|
||||
const union AnimCmd *const *const gTrainerFrontAnimsPtrTable[] =
|
||||
{
|
||||
[TRAINER_PIC_HIKER] = sAnims_Hiker,
|
||||
[TRAINER_PIC_AQUA_GRUNT_M] = sAnims_AquaGruntM,
|
||||
[TRAINER_PIC_POKEMON_BREEDER_F] = sAnims_PokemonBreederF,
|
||||
...
|
||||
|
||||
...
|
||||
[TRAINER_PIC_RS_BRENDAN] = sAnims_RubySapphireBrendan,
|
||||
[TRAINER_PIC_RS_MAY] = sAnims_RubySapphireMay,
|
||||
+ [TRAINER_PIC_MYTRAINERCLASS] = sAnims_MyTrainerClass,
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Connecting the Pictures to the Data
|
||||
The last few things we have to do is prepare the graphics for usage. In [src/data/trainer_graphics/front_pic_table.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainer_graphics/front_pic_table.h) you'll find the structs, we need to add the trainer to all of these. You can just copy the last trainer type defined and edit it, but as far as I understand, these are what they do:
|
||||
|
||||
1. gTrainerFrontPicCoords: Pretty self explanatory. Coordinates like size and offset on the y-axis to position the sprite on screen.
|
||||
2. gTrainerFrontPicTable: Connects the trainer type with the image we defined earlier.
|
||||
3. gTrainerFrontPicPaletteTable: Connects the trainer type with the palette we defined earlier.
|
||||
|
||||
So, finally, it needs to look like this:
|
||||
```diff
|
||||
const struct MonCoords gTrainerFrontPicCoords[] =
|
||||
{
|
||||
[TRAINER_PIC_HIKER] = {.size = 8, .y_offset = 1},
|
||||
[TRAINER_PIC_AQUA_GRUNT_M] = {.size = 8, .y_offset = 1},
|
||||
...
|
||||
|
||||
...
|
||||
[TRAINER_PIC_RS_BRENDAN] = {.size = 8, .y_offset = 1},
|
||||
[TRAINER_PIC_RS_MAY] = {.size = 8, .y_offset = 1},
|
||||
+ [TRAINER_PIC_MYTRAINERCLASS] = {.size = 8, .y_offset = 1},
|
||||
};
|
||||
|
||||
#define TRAINER_SPRITE(trainerPic, sprite, size) [TRAINER_PIC_##trainerPic] = {sprite, size, TRAINER_PIC_##trainerPic}
|
||||
|
||||
const struct CompressedSpriteSheet gTrainerFrontPicTable[] =
|
||||
{
|
||||
TRAINER_SPRITE(HIKER, gTrainerFrontPic_Hiker, 0x800),
|
||||
TRAINER_SPRITE(AQUA_GRUNT_M, gTrainerFrontPic_AquaGruntM, 0x800),
|
||||
TRAINER_SPRITE(POKEMON_BREEDER_F, gTrainerFrontPic_PokemonBreederF, 0x800),
|
||||
TRAINER_SPRITE(COOLTRAINER_M, gTrainerFrontPic_CoolTrainerM, 0x800),
|
||||
...
|
||||
|
||||
...
|
||||
TRAINER_SPRITE(RS_BRENDAN, gTrainerFrontPic_RubySapphireBrendan, 0x800),
|
||||
TRAINER_SPRITE(RS_MAY, gTrainerFrontPic_RubySapphireMay, 0x800),
|
||||
+ TRAINER_SPRITE(MYTRAINERCLASS, gTrainerFrontPic_MyTrainerClass, 0x800),
|
||||
};
|
||||
|
||||
#define TRAINER_PAL(trainerPic, pal) [TRAINER_PIC_##trainerPic] = {pal, TRAINER_PIC_##trainerPic}
|
||||
|
||||
const struct CompressedSpritePalette gTrainerFrontPicPaletteTable[] =
|
||||
{
|
||||
TRAINER_PAL(HIKER, gTrainerPalette_Hiker),
|
||||
TRAINER_PAL(AQUA_GRUNT_M, gTrainerPalette_AquaGruntM),
|
||||
TRAINER_PAL(POKEMON_BREEDER_F, gTrainerPalette_PokemonBreederF),
|
||||
...
|
||||
|
||||
...
|
||||
TRAINER_PAL(RS_BRENDAN, gTrainerPalette_RubySapphireBrendan),
|
||||
TRAINER_PAL(RS_MAY, gTrainerPalette_RubySapphireMay),
|
||||
+ TRAINER_PAL(MYTRAINERCLASS, gTrainerPalette_MyTrainerClass),
|
||||
};
|
||||
|
||||
```
|
||||
### The Data
|
||||
#### 5. Defining the trainer class
|
||||
Finally, let's bring it all together by defining our new trainer class in [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h):
|
||||
|
||||
```diff
|
||||
#define TRAINER_PIC_RS_MAY 92
|
||||
+ #define TRAINER_PIC_MYTRAINERCLASS 93
|
||||
|
||||
#define TRAINER_BACK_PIC_BRENDAN 0
|
||||
#define TRAINER_BACK_PIC_MAY 1
|
||||
```
|
||||
Remember to count the number next to the trainer class up by one!
|
||||
|
||||
## Usage
|
||||
You can test your trainer type by going to [src/data/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainers.h) and changing a trainer type. For example:
|
||||
```diff
|
||||
[TRAINER_BRENDAN_PLACEHOLDER] =
|
||||
{
|
||||
.partyFlags = 0,
|
||||
.trainerClass = TRAINER_CLASS_RS_PROTAG,
|
||||
.encounterMusic_gender = TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
- .trainerPic = TRAINER_PIC_RS_BRENDAN,
|
||||
+ .trainerPic = TRAINER_PIC_MYTRAINERCLASS,
|
||||
.trainerName = _("BRENDAN"),
|
||||
.items = {},
|
||||
.doubleBattle = FALSE,
|
||||
.aiFlags = 0,
|
||||
.partySize = ARRAY_COUNT(sParty_BrendanLinkPlaceholder),
|
||||
.party = {.NoItemDefaultMoves = sParty_BrendanLinkPlaceholder},
|
||||
},
|
||||
```
|
||||
122
docs/tutorials/how_to_trainer_party_pool.md
Normal file
122
docs/tutorials/how_to_trainer_party_pool.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# How to use Trainer Party Pools
|
||||
Trainer Party Pools (TPP) is a way to introduce a bit of unpredictability to trainer battles by allowing trainer to generate parties from pools defined by the user.
|
||||
|
||||
The maximum number of mons that can be in a single trainer's pool is 255.
|
||||
|
||||
## Turning on TPP with `trainer.sparty`
|
||||
To use TPP with `trainers.party`, all that's needed is to define a `Party Size` that's smaller than than the number of defined mons for the trainer.
|
||||
|
||||
## Turning on TPP with `trainers.h`
|
||||
To use TPP with `trainers.h`, the trainer need to have the `.poolSize` field set to a value that's larger than the `.partySize` and equal to the number of mons defined in the trainer.
|
||||
|
||||
## How the pool works
|
||||
When generating a party for a trainer with a pool, the party is picked from the pool randomly according to rules set for the pool and tags assigned to individual mons in the pool.
|
||||
|
||||
### Pool Rules
|
||||
Pool rules are defined in `src/data/battle_pool_rules.h`. To begin with some default pools are defined, `defaultPoolRules` which any trainer that doesn't otherwise have a specified pool ruleset uses, and some custom rules for common scenarios.
|
||||
|
||||
- `POOL_RULESET_BASIC`, a ruleset that will pick a mon from the pool with the tag `MON_POOL_TAG_LEAD` if possible to put in the first slot and `MON_POOL_TAG_ACE` in the last slot, and not pick mons with those tags for any other position.
|
||||
- `POOL_RULESET_DOUBLES`, a ruleset that will pick up to two mons from the pool with the tag `MON_POOL_TAG_LEAD` if possible to put in the first two slots and `MON_POOL_TAG_ACE` in the last two slots, and not pick mons with those tags for any other position.
|
||||
- `POOL_RULESET_WEATHER_SINGLES`, a ruleset that will pick at most one mon with the tag `MON_POOL_TAG_WEATHER_SETTER` if possible, and at least one mon with the tag `MON_POOL_TAG_WEATHER_ABUSER` if possible, in addition to the same conditions as `POOL_RULESET_BASIC`.
|
||||
- `POOL_RULESET_WEATHER_DOUBLES`, a ruleset that will pick at most one mon with the tag `MON_POOL_TAG_WEATHER_SETTER` if possible, and at least one mon with the tag `MON_POOL_TAG_WEATHER_ABUSER` if possible, in addition to the same conditions as `POOL_RULESET_DOUBLES`.
|
||||
- `POOL_RULESET_SUPPORT_DOUBLES`, a ruleset that will pick at most one mon with the tag `MON_POOL_TAG_SUPPORT` if possible, in addition to the same conditions as `POOL_RULESET_DOUBLES`.
|
||||
|
||||
All these pools also have the options `.speciesClause`, `.excludeForms`, `.itemClause` and `.itemClauseExclusions` set to the values defined in `include/config/battle.h` under `B_POOL_RULE_<rule>`.
|
||||
|
||||
- `.speciesClause` if set to `TRUE` means that the same exact species as defined by `.species` can't be picked twice for the party from the pool.
|
||||
- `.excludeForms` if set to `FALSE` means that the same exact species as defined by NetDex number can't be picked twice for the party from the pool.
|
||||
- `.itemClause` if set to `TRUE` means that pokemon with the same held item can't be picked twice for the party from the pool.
|
||||
- `.itemClauseExclusions` if set to `TRUE` means that multiple pokemon with the same item can be picked for the party if the item is listed in `poolItemClauseExclusions`. By default `ITEM_ORAN_BERRY` and `ITEM_SITRUS_BERRY` are the only items in the list of exclusions.
|
||||
|
||||
Individual tags can have rules which change how they're included.
|
||||
By setting the `.tagMaxMembers[POOL_TAG_<tag>]` field to a number, only that many mons with that tag will at max be part of the party, or if set to `POOL_MEMBER_COUNT_NONE` no mons with this tag will be included, and if set to `POOL_MEMBER_COUNT_UNLIMITED` no restrictions on the number of mons with the tag will apply.
|
||||
|
||||
By setting `.tagRequired[POOL_TAG_<tag>]` option field to `TRUE`, this tag will be picked before any tags that are not required, after the tag has been picked for the pool it will be set to `FALSE` for that tag.
|
||||
|
||||
The tags `Lead` and `Ace` has special handling where they will be picked for the first or last party position respectively.
|
||||
|
||||
### Tags
|
||||
There are currently 8 tags specified in the TPP implementation, `Lead`, `Ace`, `Weather Setter`, `Weather Abuser`, `Support`, `Tag 5`, `Tag 6` and `Tag 7`.
|
||||
|
||||
If using `trainers.party`, these tags are applied to mons with the field `Tags: `, separated by `/`. Example `Tags: Lead / Weather Setter`
|
||||
|
||||
If using `trainers.h`, these tags are applied to mons with the field `.tags`, separated by `|`. Example: `.tags = MON_POOL_TAG_LEAD | MON_POOL_TAG_WEATHER_SETTER`
|
||||
|
||||
Pokemon can have up to 32 different tags, but anything beyond the 8 initial tags has to be implemented. The numbered tags can be renamed too to better signify their purpose for developers.
|
||||
|
||||
## Trainer options
|
||||
A few more trainer options are introduced in order to further customize how the pool picking process works.
|
||||
|
||||
- `Pool Pick Functions` (`.poolPickIndex`) controls which functons are used to pick mons from the pool, they're split into Lead, Ace, and Other.
|
||||
By default, only `Default<position>PickFunction` and `PickLowest` are implemented. Must be an `enum` value in `enum PoolPickFunctions`.
|
||||
- `Pool Prune` (`.poolPruneIndex`) controls if members in the pool should be removed before party members are picked from the pool.
|
||||
By default, only `POOL_PRUNE_NONE`, which doesn't remove anything from the pool, and `POOL_PRUNE_TEST`, which removes Wobbuffet from the pool, are implemented. Must be an `enum` value in `enum PoolPruneOptions`.
|
||||
|
||||
## Example pool
|
||||
```
|
||||
=== TRAINER_TIANA ===
|
||||
Name: TIANA
|
||||
Class: Lass
|
||||
Pic: Lass
|
||||
Gender: Female
|
||||
Music: Female
|
||||
Double Battle: Yes
|
||||
AI: Check Bad Move
|
||||
Party Size: 4
|
||||
Pool Rules: Weather Doubles
|
||||
Pool Pick Index: Default
|
||||
|
||||
Zigzagoon
|
||||
Level: 4
|
||||
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
|
||||
|
||||
Shroomish
|
||||
Level: 4
|
||||
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
|
||||
|
||||
Psyduck
|
||||
Level: 4
|
||||
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
|
||||
|
||||
Shellder
|
||||
Level: 4
|
||||
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
|
||||
|
||||
Mew
|
||||
Level: 4
|
||||
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
|
||||
Tags: Ace
|
||||
|
||||
Giratina
|
||||
Level: 4
|
||||
IVs: 0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe
|
||||
Tags: Ace
|
||||
|
||||
Vulpix
|
||||
Ability: Drought
|
||||
Level: 4
|
||||
Tags: Lead / Weather Setter
|
||||
|
||||
Torkoal
|
||||
Ability: Drought
|
||||
Level: 4
|
||||
Tags: Lead / Weather Setter
|
||||
|
||||
Bulbasaur
|
||||
Ability: Chlorophyll
|
||||
Level: 4
|
||||
Tags: Lead / Weather Abuser
|
||||
|
||||
Cherrim
|
||||
Level: 4
|
||||
Tags: Lead / Weather Abuser
|
||||
```
|
||||
Here Tiana has been given a pool that's set up for a double battle with weather. Using the default pool rule `Weather Doubles` it will only pick one of each of the weather setters and abusers which Tiana will lead with. Tiana will also pick either Mew or Giratina as her Ace mon, and the last slot will be filled with one of Zigzagoon, Shroomish, Psyduck or Shellder.
|
||||
|
||||
## Pool settings
|
||||
If no pool rule is specified in the trainer, the default rules will be used, which sets rules according to some defaults from `include/config/battle.h`.
|
||||
This file also has settings for other pool options.
|
||||
|
||||
- `B_POOL_SETTING_CONSISTENT_RNG`, `TRUE` or `FALSE`, the party generated will always be the same on a particular save (RNG dependant on trainerId and encountered trainer).
|
||||
- `B_POOL_SETTING_USE_FIXED_SEED`, `TRUE` or `FALSE`, the party generated will always be the same on a particular compiled ROM (RNG dependant on a chosen seed and encountered trainer).
|
||||
- `B_POOL_SETTING_FIXED_SEED`, seed to use for fixed seed, does nothing if `B_POOL_SETTING_USE_FIXED_SEED` is `FALSE`.
|
||||
98
docs/tutorials/how_to_trainer_pic.md
Normal file
98
docs/tutorials/how_to_trainer_pic.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# How to add a new trainer pic
|
||||
|
||||
## Content
|
||||
* [Quick Summary](#quick-summary)
|
||||
* [The Graphics](#the-graphics)
|
||||
* [1. Edit the sprites](#2-edit-the-sprites)
|
||||
* [2. Register the sprites](#2-register-the-sprites)
|
||||
* [3. Connecting pictures to the data](#2-connecting-pictures-to-the-data)
|
||||
* [The Data](#the-data)
|
||||
* [4. Defining the trainer pic](#2-defining-the-trainer-pic)
|
||||
* [Usage](#usage)
|
||||
|
||||
## Quick Summary
|
||||
If you've done this before and just need a quick lookup, here's what files you need:
|
||||
1. GFX into [graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics)
|
||||
2. Palette into [graphics/trainers/palettes](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/palettes)
|
||||
3. Point game to where graphic files are found: [src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h)
|
||||
4. Add trainer to [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h)
|
||||
|
||||
## The Graphics
|
||||
|
||||
### 1. Edit the sprites
|
||||
We will start with a graphic that we want to use for our new trainer pic. Unlike with adding Pokémon, the trainer sprites aren't sorted in individual folders, but rather in one folder:
|
||||
[graphics/trainers/front_pics](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/graphics/trainers/front_pics)
|
||||
|
||||
**Remember to limit yourself to 16 colors including transparency in the first slot!**
|
||||
|
||||
Export the palette and place into the same folder.
|
||||
|
||||
### 2. Register the sprites
|
||||
Sadly, just putting the image files into the graphics folder is not enough. To use the sprites we have to register them by linking the graphic files.
|
||||
[src/data/graphics/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h):
|
||||
```diff
|
||||
const u32 gTrainerPalette_RubySapphireBrendan[] = INCBIN_U32("graphics/trainers/palettes/ruby_sapphire_brendan.gbapal.lz");
|
||||
|
||||
const u32 gTrainerFrontPic_RubySapphireMay[] = INCBIN_U32("graphics/trainers/front_pics/ruby_sapphire.4bpp.lz");
|
||||
const u32 gTrainerPalette_RubySapphireMay[] = INCBIN_U32("graphics/trainers/palettes/ruby_sapphire_may.gbapal.lz");
|
||||
|
||||
+ const u32 gTrainerFrontPic_myTrainerClass[] = INCBIN_U32("graphics/trainers/front_pics/myTrainerClass.4bpp.lz");
|
||||
+ const u32 gTrainerPalette_myTrainerClass[] = INCBIN_U32("graphics/trainers/palettes/myTrainerClass.gbapal.lz");
|
||||
|
||||
const u8 gTrainerBackPic_Brendan[] = INCBIN_U8("graphics/trainers/back_pics/brendan.4bpp");
|
||||
```
|
||||
|
||||
### 3. Connecting the Pictures to the Data
|
||||
The last few things we have to do is prepare the graphics for usage. In [src/data/graphics/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/graphics/trainers.h) you'll find the gTrainerSprites struct, we need to add the trainer to this. You can just copy the last trainer type defined and edit it, but this is what it does: Connects the trainer type with the image we defined earlier.
|
||||
|
||||
So, finally, it needs to look like this:
|
||||
```diff
|
||||
define TRAINER_SPRITE(trainerPic, picFile, paletteFile, ...) \
|
||||
[trainerPic] = \
|
||||
{ \
|
||||
.frontPic = {picFile, TRAINER_PIC_SIZE, trainerPic}, \
|
||||
.palette = {paletteFile, trainerPic}, \
|
||||
.mugshotCoords = {DEFAULT(0, __VA_ARGS__), DEFAULT_2(0, __VA_ARGS__)}, \
|
||||
.mugshotRotation = DEFAULT_3(0x200, __VA_ARGS__), \
|
||||
}
|
||||
|
||||
const struct TrainerSprite gTrainerSprites[] =
|
||||
{
|
||||
TRAINER_SPRITE(TRAINER_PIC_HIKER, gTrainerFrontPic_Hiker, gTrainerPalette_Hiker),
|
||||
TRAINER_SPRITE(TRAINER_PIC_AQUA_GRUNT_M, gTrainerFrontPic_AquaGruntM, gTrainerPalette_AquaGruntM),
|
||||
...
|
||||
TRAINER_SPRITE(TRAINER_PIC_RS_MAY, gTrainerFrontPic_RubySapphireMay, gTrainerPalette_RubySapphireMay),
|
||||
TRAINER_SPRITE(TRAINER_PIC_MY_TRAINER_CLASS, gTrainerFrontPic_myTrainerClass, gTrainerPalette_myTrainerClass)
|
||||
};
|
||||
```
|
||||
### The Data
|
||||
#### 4. Defining the trainer pic
|
||||
Finally, let's bring it all together by defining our new trainer pic in [include/constants/trainers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/trainers.h):
|
||||
|
||||
```diff
|
||||
#define TRAINER_PIC_RS_MAY 92
|
||||
+ #define TRAINER_PIC_MY_TRAINER_CLASS 93
|
||||
|
||||
#define TRAINER_BACK_PIC_BRENDAN 0
|
||||
#define TRAINER_BACK_PIC_MAY 1
|
||||
```
|
||||
Remember to count the number next to the trainer pic up by one!
|
||||
|
||||
## Usage
|
||||
You can test your trainer type by going to [src/data/trainers](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/trainers.h) and changing a trainer type. For example:
|
||||
```diff
|
||||
[TRAINER_BRENDAN_PLACEHOLDER] =
|
||||
{
|
||||
.partyFlags = 0,
|
||||
.trainerClass = TRAINER_CLASS_RS_PROTAG,
|
||||
.encounterMusic_gender = TRAINER_ENCOUNTER_MUSIC_MALE,
|
||||
- .trainerPic = TRAINER_PIC_RS_BRENDAN,
|
||||
+ .trainerPic = TRAINER_PIC_MY_TRAINER_CLASS,
|
||||
.trainerName = _("BRENDAN"),
|
||||
.items = {},
|
||||
.doubleBattle = FALSE,
|
||||
.aiFlags = 0,
|
||||
.partySize = ARRAY_COUNT(sParty_BrendanLinkPlaceholder),
|
||||
.party = {.NoItemDefaultMoves = sParty_BrendanLinkPlaceholder},
|
||||
},
|
||||
```
|
||||
Reference in New Issue
Block a user