[pokeemerald] Add a Palette Command for Sprite Animations, Use it to show dual types in Summary Screen
Extra: Show dual types in battle as well
A Palette command can be useful so we can show sprites with different palette indexes in the same AnimCmd. We can't do this:
Spoiler:
Code:
static const union AnimCmd sSpriteAnim_TypeNormalFlying[] = {
ANIMCMD_FRAME(TYPE_NORMAL * 8, 30, FALSE, FALSE),
ANIMCMD_FRAME(TYPE_FLYING * 8, 30, FALSE, FALSE),
ANIMCMD_JUMP(0)
};
The only move that has a dual type in the series is Flying Press, but in-game we can only see its Fighting Type, we could take advantage of AnimCmd so we can show both types alternatively whenever the move is on-screen. This doesn't load additional palettes since all Type palettes are loaded in memory even if they aren't used. If you want to add more dual type moves to your rom, showing both types is a must and this is just a way to do so in Emerald that I figured out.
This is how it looks:
Spoiler:
![[PokeCommunity.com] Palette Command for Sprite Animations, Dual types animation in Summary Screen, Show dual types in battle as well [PokeCommunity.com] Palette Command for Sprite Animations, Dual types animation in Summary Screen, Show dual types in battle as well](https://i.imgur.com/1BuNVTT.gif)
![[PokeCommunity.com] Palette Command for Sprite Animations, Dual types animation in Summary Screen, Show dual types in battle as well [PokeCommunity.com] Palette Command for Sprite Animations, Dual types animation in Summary Screen, Show dual types in battle as well](https://i.imgur.com/1gxUY4B.gif)
NOTE: Vanilla Emerald does not have any dual type moves, implementing them is outside the scope of the tutorial. I will have a small note at the end to show how you can do this with Flying Press for the pokeemerald expansion but nothing more. I assume a newu8 type2
variable forstruct BattleMove
during the tutorial and no flags or effects, but you may be using.effect = EFFECT_TWO_TYPED_MOVE
and.argument
just like Flying Press in the expansion, or whatever other method you might have devised.
Here's a git branch, you can pull from it easily to add this to your code. Things to note when adding this:
- Check the changes in
include/pokemon.h
since you might not be using a .type2 variable. - In
src/battle_controller_player.c
you probably need to editbool8 SelectedMoveHasDoubleType(struct ChooseMoveStruct *moveInfo)
for your project. - As an example, Torchic learns Solar Beam and Muddy Water at level 1. Remove these. You might want to keep those move types in
src/data/battle_moves.h
or edit them out (Grass/Fire and Water/Ground respectively, using .type2) - In
src/pokemon_summary_screen.c
atvoid SetMoveTypeIcons(void)
, there's two checks for Grass/Fire and Water/Ground. You might need to edit those for your project.
We will be changing 8 files, I'll be referencing them by their filename alone.
Code:
gflib/sprite.c
gflib/sprite.h
include/battle_controllers.h
include/pokemon.h
src/battle_controller_player.c
src/pokemon_summary_screen.c
src/data/battle_moves.h
First thing, the easy one. Show dual type moves in battle
Spoiler:
Go to battle_controller_player.c.
Inside here you have to input your code that detects if a Move is dual type. I added the dummy
Now you might have noticed that if we changed the signature of
If the game is running 60 frames per second, this function will yield 0 every second and 30 every half of that, which is what we want. You also need to go to battle_controllers.h and add a
Back again into
So the else goes into that line we mentioned.
The updating check allows us to reset the timer if we are switching the cursor around in battle, it makes it look a bit more smooth if the timer only starts if we are holding a move.
The next check is basically showing the appropiate label of the second type every 30 frames, which under normal circumstances is half a second. Now if you wish you can go into game, get a mon with some dual type move and look at the result in a battle.
- 1. Change the definition
void MoveSelectionDisplayMoveType(void)
intovoid MoveSelectionDisplayMoveType(bool8)
- 2. Now go to the function
void HandleInputChooseMove(void)
, above it (anywhere as long as it is above the function), add this new func
Code:
//check if has two types
bool8 SelectedMoveHasDoubleType(struct ChooseMoveStruct *moveInfo){
if(gBattleMoves[moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]].type2 != TYPE_NONE)
return TRUE;
return FALSE;
}
Inside here you have to input your code that detects if a Move is dual type. I added the dummy
if(type2 is not none)
as an easy test. You can change the struct BattleMove
in pokemon.h to add a type2 variable if you wish.BUT I recommend to create a new flag for moves too because a variable will default to 0, which is equivalent to TYPE_NORMAl, not TYPE_NONE as you might expect, so all already existing moves will have by default TYPE_NORMAL as their type2 if you don't check if the type has a flag for it (for example, FLAG_DOUBLE_TYPED). All of this is not considered into the tutorial.
- 3. Now again look up for
HandleInputChooseMove
and after the ChooseMoveStruct variable declaration, add this
Code:
if(SelectedMoveHasDoubleType(moveInfo)) //check if selected move has two types
MoveSelectionDisplayMoveType(TRUE); //keep updating time and the label
Now you might have noticed that if we changed the signature of
MoveSelectionDisplayMoveType(void)[icode] we have to update all the references to it. Well yes, use the search function and make sure all other calls to this function get FALSE as their argument. This might be different for you, but I found calls at the lines 569, 582, 594, 607, 776, 786, 2650.
[LIST][*][b]4.[/b] Go to [icode]void MoveSelectionDisplayMoveType(void)
and update it to void MoveSelectionDisplayMoveType(bool8 updating)
. Just like before, add a new function above this function.[/list]
Code:
u8 GetTimer(struct ChooseMoveStruct *moveInfo) {
if(++moveInfo->moveTypeTimer > 60){
moveInfo->moveTypeTimer = 0;
}
return moveInfo->moveTypeTimer;
}
u8 moveTypeTimer;
into the struct ChooseMoveStruct
.Back again into
MoveSelectionDisplayMoveType
, add this just before the line StringCopy(txtPtr, gTypeNames[gBattleMoves[moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]].type]);
Code:
if(!updating)
moveInfo->moveTypeTimer = 0;
if(SelectedMoveHasDoubleType(moveInfo) && GetTimer(moveInfo) < 30)
StringCopy(txtPtr, gTypeNames[gBattleMoves[moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]].type2]);
else
The updating check allows us to reset the timer if we are switching the cursor around in battle, it makes it look a bit more smooth if the timer only starts if we are holding a move.
The next check is basically showing the appropiate label of the second type every 30 frames, which under normal circumstances is half a second. Now if you wish you can go into game, get a mon with some dual type move and look at the result in a battle.
Add a Palette Command for Sprite Animations
Spoiler:
Let's create new stuff for this command. Go to sprite.h
Palette command data
Palette variable into AnimCmd
Macro define for that, like ANIMCMD_FRAME, _LOOP etc.
We have the data and an easy way to put it in code, now let's add functionality.
In
Search and go to the
The important thing here is that we set
Now you have it, a perfect solution to change palettes mid-animation. This is how you can use it:
Which is very much like what we gonna use to...
- 1. Above
union AnimCmd
, add:
Palette command data
Code:
struct AnimPaletteCmd
{
u32 type:16;
u32 paletteNum:6;
};
Palette variable into AnimCmd
Code:
[color="dimgray"]union AnimCmd
{
s16 type;
struct AnimFrameCmd frame;
[color="lime"] struct AnimPaletteCmd palette;[/color]
struct AnimLoopCmd loop;
struct AnimJumpCmd jump;
};[/color]
Macro define for that, like ANIMCMD_FRAME, _LOOP etc.
Code:
#define ANIMCMD_PALETTE(_paletteNum) \
{.palette = {.type = -4, .paletteNum = _paletteNum}}
We have the data and an easy way to put it in code, now let's add functionality.
- 2. Go to sprite.c, register the functions:
static void AnimCmd_palette(struct Sprite *sprite);
to declare the function.In
const AnimCmdFunc sAnimCmdFuncs
, register the function you just declared, like this
Code:
static const AnimCmdFunc sAnimCmdFuncs[] =
{
[color="lime"] AnimCmd_palette,[/color]
AnimCmd_loop,
AnimCmd_jump,
AnimCmd_end,
AnimCmd_frame,
};
Search and go to the
void ContinueAnim(struct Sprite *sprite)
function, in there you will see that funcIndex
is set in a way so there's only 3 AnimCmd funcs (excluding frame, for some reason). Now there are 4, so change them numbers.
Code:
[color="dimgray"]...
type = sprite->anims[sprite->animNum][sprite->animCmdIndex].type;
[color="lime"]funcIndex = 4;[/color]
if (type < 0)
[color="lime"]funcIndex = type + 4;[/color]
sAnimCmdFuncs[funcIndex](sprite);[/color]
- 3. Finally, you can add this next function anywhere, maybe preferably around the AnimCmd_ functions.
Code:
void AnimCmd_palette(struct Sprite *sprite) {
s16 type;
s16 funcIndex;
//assign new palette
sprite->oam.paletteNum = sprite->anims[sprite->animNum][sprite->animCmdIndex].palette.paletteNum;
//advance one index in the animation like in ContinueAnim
sprite->animCmdIndex++;
type = sprite->anims[sprite->animNum][sprite->animCmdIndex].type;
funcIndex = 4;
if (type < 0)
funcIndex = type + 4;
sAnimCmdFuncs[funcIndex](sprite);
}
sprite->oam.paletteNum
to the palette number we specify in the struct, so it changes whenever we want. The code after that is just a copy paste of the second part of the ContinueAnim
function, this is to ensure that we come and change the sprite at the same time that we change the palette, so we don't get 1 frame of a misindexed sprite palette.Now you have it, a perfect solution to change palettes mid-animation. This is how you can use it:
Code:
const union AnimCmd animation[] = {
ANIMCMD_FRAME(myFirstSprite, 30, FALSE, FALSE),
ANIMCMD_PALETTE(firstSpritePalette),
ANIMCMD_FRAME(mySecondSprite, 30, FALSE, FALSE),
ANIMCMD_PALETTE(secondSpritePalette),
ANIMCMD_JUMP(0)
};
Which is very much like what we gonna use to...
Show dual types in Summary Screen
Spoiler:
All of these changes are in
Optional, personally recommended but it's just cleaning.
Similar to that optional step, to show double types we will have to do a new macro for the ease of our fingers and eyes.
And then create the new double type variables, at last!
And so on, for all the double types that you want to implement.
Note: I've changed the type palettes into defined constants so it's easier to get them. This is the list if you want to have that too. By default these were hard coded into
Now you have to add the new variables into the
Now you gotta go to the
The constants I used are
Now, the last step, for real. Look up the function
Notice how we check for both Fire/Grass and Grass/Fire and so on. This is because there's no need to add a entirely new index of double types if the summary screen will already show both of them every 30 frames. Also we can easily use the defines for the double types (like GRASS_FIRE and WATER_GROUND) as the first argument, as they have the same index value in both tables that we edited previously (which is the point of tables, right?).
If you want to know how I've tested this, go to battle_moves.h and add a
Making sure of course that in
Compile, build, start and make a new game, speedrun until you get the starter and check the Pokémon moves in the summary screen.
pokemon_summary_screen.c
.Optional, personally recommended but it's just cleaning.
Spoiler:
You might have seen that the type sprites look like this
(These should all be around line 750)
And so on for every type. Isn't this a bit cumbersome, as the only thing that changes is its type index? Well, this is optional but you can add a new define macro...
And change all of these variables into
+TYPE_FAIRY if you have that, or all that you might have implemented. Remember these variables already exist, you have to replace them all.
Code:
static const union AnimCmd sSpriteAnim_TypeNormal[] = {
ANIMCMD_FRAME(TYPE_NORMAL * 8, 0, FALSE, FALSE),
ANIMCMD_END
};
And so on for every type. Isn't this a bit cumbersome, as the only thing that changes is its type index? Well, this is optional but you can add a new define macro...
Code:
#define TYPE_ANIM(type) \
{ \
ANIMCMD_FRAME(type * 8, 0, FALSE, FALSE), \
ANIMCMD_END \
}
And change all of these variables into
Code:
static const union AnimCmd sSpriteAnim_TypeNormal[] = TYPE_ANIM(TYPE_NORMAL );
static const union AnimCmd sSpriteAnim_TypeFighting[] = TYPE_ANIM(TYPE_FIGHTING);
static const union AnimCmd sSpriteAnim_TypeFlying[] = TYPE_ANIM(TYPE_FLYING );
static const union AnimCmd sSpriteAnim_TypePoison[] = TYPE_ANIM(TYPE_POISON );
static const union AnimCmd sSpriteAnim_TypeGround[] = TYPE_ANIM(TYPE_GROUND );
static const union AnimCmd sSpriteAnim_TypeRock[] = TYPE_ANIM(TYPE_ROCK );
static const union AnimCmd sSpriteAnim_TypeBug[] = TYPE_ANIM(TYPE_BUG );
static const union AnimCmd sSpriteAnim_TypeGhost[] = TYPE_ANIM(TYPE_GHOST );
static const union AnimCmd sSpriteAnim_TypeSteel[] = TYPE_ANIM(TYPE_STEEL );
static const union AnimCmd sSpriteAnim_TypeMystery[] = TYPE_ANIM(TYPE_MYSTERY );
static const union AnimCmd sSpriteAnim_TypeFire[] = TYPE_ANIM(TYPE_FIRE );
static const union AnimCmd sSpriteAnim_TypeWater[] = TYPE_ANIM(TYPE_WATER );
static const union AnimCmd sSpriteAnim_TypeGrass[] = TYPE_ANIM(TYPE_GRASS );
static const union AnimCmd sSpriteAnim_TypeElectric[] = TYPE_ANIM(TYPE_ELECTRIC);
static const union AnimCmd sSpriteAnim_TypePsychic[] = TYPE_ANIM(TYPE_PSYCHIC );
static const union AnimCmd sSpriteAnim_TypeIce[] = TYPE_ANIM(TYPE_ICE );
static const union AnimCmd sSpriteAnim_TypeDragon[] = TYPE_ANIM(TYPE_DRAGON );
static const union AnimCmd sSpriteAnim_TypeDark[] = TYPE_ANIM(TYPE_DARK );
static const union AnimCmd sSpriteAnim_CategoryCool[] = TYPE_ANIM(CONTEST_CATEGORY_COOL + NUMBER_OF_MON_TYPES);
static const union AnimCmd sSpriteAnim_CategoryBeauty[] = TYPE_ANIM(CONTEST_CATEGORY_BEAUTY + NUMBER_OF_MON_TYPES);
static const union AnimCmd sSpriteAnim_CategoryCute[] = TYPE_ANIM(CONTEST_CATEGORY_CUTE + NUMBER_OF_MON_TYPES);
static const union AnimCmd sSpriteAnim_CategorySmart[] = TYPE_ANIM(CONTEST_CATEGORY_SMART + NUMBER_OF_MON_TYPES);
static const union AnimCmd sSpriteAnim_CategoryTough[] = TYPE_ANIM(CONTEST_CATEGORY_TOUGH + NUMBER_OF_MON_TYPES);
Similar to that optional step, to show double types we will have to do a new macro for the ease of our fingers and eyes.
Code:
#define DOUBLE_TYPE_ANIM(type1, type2, pal1, pal2) \
{ \
ANIMCMD_FRAME(type1 * 8, 30, FALSE, FALSE), \
ANIMCMD_PALETTE(pal2), \
ANIMCMD_FRAME(type2 * 8, 30, FALSE, FALSE), \
ANIMCMD_PALETTE(pal1), \
ANIMCMD_JUMP(0) \
}
And then create the new double type variables, at last!
Code:
static const union AnimCmd sSpriteAnim_TypeGrassFire[] = DOUBLE_TYPE_ANIM(TYPE_GRASS, TYPE_FIRE, PAL_TYPE_GRASS, PAL_TYPE_FIRE); // 15 and 13
static const union AnimCmd sSpriteAnim_TypeWaterGround[] = DOUBLE_TYPE_ANIM(TYPE_WATER, TYPE_GROUND, PAL_TYPE_WATER, PAL_TYPE_GROUND); // 14 and 13
Note: I've changed the type palettes into defined constants so it's easier to get them. This is the list if you want to have that too. By default these were hard coded into
sMoveTypeToOamPaletteNum
Spoiler:
Code:
#define PAL_TYPE_NORMAL 13
#define PAL_TYPE_FIGHTING 13
#define PAL_TYPE_FLYING 14
#define PAL_TYPE_POISON 14
#define PAL_TYPE_GROUND 13
#define PAL_TYPE_ROCK 13
#define PAL_TYPE_BUG 15
#define PAL_TYPE_GHOST 14
#define PAL_TYPE_STEEL 13
#define PAL_TYPE_MYSTERY 15
#define PAL_TYPE_FIRE 13
#define PAL_TYPE_WATER 14
#define PAL_TYPE_GRASS 15
#define PAL_TYPE_ELECTRIC 13
#define PAL_TYPE_PSYCHIC 14
#define PAL_TYPE_ICE 14
#define PAL_TYPE_DRAGON 15
#define PAL_TYPE_DARK 13
Now you have to add the new variables into the
sSpriteAnimTable_MoveTypes
table, I added a new define to keep track of how many double types I have too:
Code:
[color="dimgray"]static const union AnimCmd *const sSpriteAnimTable_MoveTypes[NUMBER_OF_MON_TYPES + CONTEST_CATEGORIES_COUNT [color="lime"]+ DOUBLE_TYPES_COUNT[/color][/color]] = {
...
sSpriteAnim_CategoryTough,
[color="lime"] sSpriteAnim_TypeGrassFire, //Double Types start down here
sSpriteAnim_TypeWaterGround,[/color]
};
Now you gotta go to the
sMoveTypeToOamPaletteNum
lookup table variable and register the first palette of the double types. This is very important so when we show the sprite for the first time, we get the correct palette at the start. Again, I defined new constants to keep track of the double type.
Code:
[color="dimgray"] ...
[NUMBER_OF_MON_TYPES + CONTEST_CATEGORY_TOUGH] = 13,
[color="lime"][GRASS_FIRE] = PAL_TYPE_GRASS , //or 15
[WATER_GROUND] = PAL_TYPE_WATER , //or 14[/color]
};[/color]
The constants I used are
Code:
#define GRASS_FIRE (NUMBER_OF_MON_TYPES + CONTEST_CATEGORIES_COUNT)
#define WATER_GROUND (NUMBER_OF_MON_TYPES + CONTEST_CATEGORIES_COUNT) + 1
Now, the last step, for real. Look up the function
void SetMoveTypeIcons(void)
and just before the call to SetTypeSpritePosAndPal
, you have to add your own logic to determine if a move is a double type and which index to get. I added this dummy code because I tested this with a Grass/Fire (Solar Beam) and a Water/Ground (Muddy Watter). This is the example.
Code:
[color="dimgray"]if (summary->moves[i] != MOVE_NONE){
[color="lime"]if((gBattleMoves[summary->moves[i]].type == TYPE_GRASS && gBattleMoves[summary->moves[i]].type2 == TYPE_FIRE)
|| (gBattleMoves[summary->moves[i]].type == TYPE_FIRE && gBattleMoves[summary->moves[i]].type2 == TYPE_GRASS)){ //Fire/Grass, Grass/Fire
SetTypeSpritePosAndPal(GRASS_FIRE, 85, 32 + (i * 16), i + SPRITE_ARR_ID_TYPE);
} else if((gBattleMoves[summary->moves[i]].type == TYPE_WATER && gBattleMoves[summary->moves[i]].type2 == TYPE_GROUND)
|| (gBattleMoves[summary->moves[i]].type == TYPE_GROUND && gBattleMoves[summary->moves[i]].type2 == TYPE_WATER)) { //Water/Ground, Ground/Water
SetTypeSpritePosAndPal(WATER_GROUND, 85, 32 + (i * 16), i + SPRITE_ARR_ID_TYPE);
} else[/color]
SetTypeSpritePosAndPal(gBattleMoves[summary->moves[i]].type, 85, 32 + (i * 16), i + SPRITE_ARR_ID_TYPE);
}
else[/color]
If you want to know how I've tested this, go to battle_moves.h and add a
.type2 = TYPE_FIRE
into MOVE_SOLAR_BEAM
and a .type2 = TYPE_WATER
into MOVE_MUDDY_WATER
Making sure of course that in
pokemon.h
there's a u8 type2;
variable in struct BattleMove
. Then go to the level_up_learnsets.h
file and edit any of the starters learnsets to add these new moves. For example into Torchic:
Code:
[color="dimgray"]static const u16 sTorchicLevelUpLearnset[] = {
LEVEL_UP_MOVE( 1, MOVE_SCRATCH),
LEVEL_UP_MOVE( 1, MOVE_GROWL),
[color="lime"]LEVEL_UP_MOVE( 1, MOVE_SOLAR_BEAM),
LEVEL_UP_MOVE( 1, MOVE_MUDDY_WATER),[/color]
LEVEL_UP_MOVE( 7, MOVE_FOCUS_ENERGY),
...[/color]
Compile, build, start and make a new game, speedrun until you get the starter and check the Pokémon moves in the summary screen.
That's it. Now, I recommend to create a generalized check for double types so you dont have to manually add them into
void SetMoveTypeIcons(void)
every time you create a new one.There's other ways to show double types in the summary screen, but those probably need to adjust the space in the summary screen (or create a new one entirely) so you can show two types at the same time in one move slot. This method is useful if you don't want to do any of that. Feel free to give feedback if you find any bugs whatsoever, or just get the code from the repo as that works for both pokeemerald and the expansion. Now, talking about that...
Thank you for checkin' out the tutorial.
Flying Press in the expansion.
Spoiler:
You might change the if check inside
or
And then go into
And replace .type2 with .argument in all instances. Remember that the index 0 is TYPE_NORMAL, use TYPE_NONE to check for no types.
SelectedMoveHasDoubleType(struct ChooseMoveStruct *moveInfo) (battle_controller_player.c)
into
Code:
if(gBattleMoves[moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]] == MOVE_FLYING_PRESS)
or
Code:
if(gBattleMoves[moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]].effect == EFFECT_DOUBLE_TYPED_MOVE && gBattleMoves[moveInfo->moves[gMoveSelectionCursor[gActiveBattler]]].argument != TYPE_NONE)
And then go into
SetMoveTypeIcons(void) (pokemon_summary_screen.c)
and make sure the check to select the Fighting/Flying double type is the same as the one in SelectedMoveHasDoubleTypeAnd replace .type2 with .argument in all instances. Remember that the index 0 is TYPE_NORMAL, use TYPE_NONE to check for no types.
Last edited: