-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Show IVs EVs in Summary Screen
Credits: Jaizu, Buffel Saft, AkimotoBubble, PokemonCrazy, Scyrous
This guide will walk you through modifying Pokémon Emerald's summary screen to cycle between a Pokémon's stats, Individual Values (IVs), and Effort Values (EVs) by pressing the 'A' button. The code is based on Pokecommunity posts found here and here. It has been reworked and expanded by Jaizu to include dynamic tilemap updates based on the current view, as well as some other minor enhancements. This guide assumes you are using DizzyEgg's colored stats by nature modification.
Example:
All of the edits listed below are done in src\pokemon_summary_screen.c
, with the exception of the tileset replacement.
- Replace summary screen tileset
- Forward declarations
- Update stats tilemap
- Implement skills page cycling
- Add switch prompt
Since we want to dynamically update the STATS text in the background (which is part of a tilemap), we have to edit our tileset to include the new IVS/EVS tiles.
Replace tiles.png
in graphics\summary_screen
with the image below.
Next, we need to forward declare BufferIvOrEvStats
and sStatsLeftColumnLayoutIVEV
. We'll be using these later on.
static void KeepMoveSelectorVisible(u8);
static void SummaryScreen_DestroyAnimDelayTask(void);
static void BufferStat(u8 *dst, s8 natureMod, u32 stat, u32 strId, u32 n);
+static void BufferIvOrEvStats(u8 mode);
// const rom data
#include "data/text/move_descriptions.h"
#include "data/text/nature_names.h"
And also:
static const u8 sMemoNatureTextColor[] = _("{COLOR LIGHT_RED}{SHADOW GREEN}");
static const u8 sMemoMiscTextColor[] = _("{COLOR WHITE}{SHADOW DARK_GRAY}");
static const u8 sStatsLeftColumnLayout[] = _("{DYNAMIC 0}/{DYNAMIC 1}\n{DYNAMIC 2}\n{DYNAMIC 3}");
+static const u8 sStatsLeftColumnLayoutIVEV[] = _("{DYNAMIC 0}\n{DYNAMIC 1}\n{DYNAMIC 2}");
static const u8 sStatsRightColumnLayout[] = _("{DYNAMIC 0}\n{DYNAMIC 1}\n{DYNAMIC 2}");
static const u8 sMovesPPLayout[] = _("{PP}{DYNAMIC 0}/{DYNAMIC 1}");
This next function (and accompanying defines) updates the tilemap based on currentStat
, modifies the taskData array accordingly, and sets the next task function to handle the player's input.
Place it between static void CloseSummaryScreen(u8 taskId)
and static void Task_HandleInput(u8 taskId)
.
#define currentStat taskData[3]
#define STAT_STATS 0
#define STAT_IVS 1
#define STAT_EVS 2
#define STATS_CORD_X 44
#define STATS_CORD_Y 102
#define STATS_STATS_BLOCK 169
#define EVS_STATS_BLOCK 218
#define IVS_STATS_BLOCK (EVS_STATS_BLOCK + 3)
#define STATS_BLANK_BLOCK 1241
// Update skills page tilemap
static void ChangeSummaryState(s16 *taskData, u8 taskId)
{
FillBgTilemapBufferRect(1, STATS_BLANK_BLOCK, STATS_CORD_X + 3, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, STATS_BLANK_BLOCK, STATS_CORD_X + 4, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, STATS_BLANK_BLOCK, STATS_CORD_X + 5, STATS_CORD_Y, 1, 1, 2);
switch (currentStat)
{
case STAT_STATS:
FillBgTilemapBufferRect(1, IVS_STATS_BLOCK, STATS_CORD_X, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, IVS_STATS_BLOCK + 1, STATS_CORD_X + 1, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, IVS_STATS_BLOCK + 2, STATS_CORD_X + 2, STATS_CORD_Y, 1, 1, 2);
taskData[3] = STAT_IVS;
break;
case STAT_IVS:
FillBgTilemapBufferRect(1, EVS_STATS_BLOCK, STATS_CORD_X, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, EVS_STATS_BLOCK + 1, STATS_CORD_X + 1, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, EVS_STATS_BLOCK + 2, STATS_CORD_X + 2, STATS_CORD_Y, 1, 1, 2);
taskData[3] = STAT_EVS;
break;
case STAT_EVS:
FillBgTilemapBufferRect(1, STATS_STATS_BLOCK, STATS_CORD_X, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, STATS_STATS_BLOCK + 1, STATS_CORD_X + 1, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, STATS_STATS_BLOCK + 2, STATS_CORD_X + 2, STATS_CORD_Y, 1, 1, 2);
FillBgTilemapBufferRect(1, STATS_STATS_BLOCK + 3, STATS_CORD_X + 3, STATS_CORD_Y, 1, 1, 2);
taskData[3] = STAT_STATS;
break;
}
CopyBgTilemapBufferToVram(1);
gTasks[taskId].func = Task_HandleInput;
}
Also make the following changes here:
static void BufferRightColumnStats(void)
{
const s8 *natureMod = gNatureStatTable[sMonSummaryScreen->summary.nature];
+ // Reset to STATS graphics
+ FillBgTilemapBufferRect(1, STATS_BLANK_BLOCK, STATS_CORD_X + 3, STATS_CORD_Y, 1, 1, 2);
+ FillBgTilemapBufferRect(1, STATS_BLANK_BLOCK, STATS_CORD_X + 4, STATS_CORD_Y, 1, 1, 2);
+ FillBgTilemapBufferRect(1, STATS_BLANK_BLOCK, STATS_CORD_X + 5, STATS_CORD_Y, 1, 1, 2);
+ FillBgTilemapBufferRect(1, STATS_STATS_BLOCK, STATS_CORD_X, STATS_CORD_Y, 1, 1, 2);
+ FillBgTilemapBufferRect(1, STATS_STATS_BLOCK + 1, STATS_CORD_X + 1, STATS_CORD_Y, 1, 1, 2);
+ FillBgTilemapBufferRect(1, STATS_STATS_BLOCK + 2, STATS_CORD_X + 2, STATS_CORD_Y, 1, 1, 2);
+ FillBgTilemapBufferRect(1, STATS_STATS_BLOCK + 3, STATS_CORD_X + 3, STATS_CORD_Y, 1, 1, 2);
+ CopyBgTilemapBufferToVram(1);
+
DynamicPlaceholderTextUtil_Reset();
BufferStat(gStringVar1, natureMod[STAT_SPATK - 1], sMonSummaryScreen->summary.spatk, 0, 3);
BufferStat(gStringVar2, natureMod[STAT_SPDEF - 1], sMonSummaryScreen->summary.spdef, 1, 3);
BufferStat(gStringVar3, natureMod[STAT_SPEED - 1], sMonSummaryScreen->summary.speed, 2, 3);
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsRightColumnLayout);
}
The following function handles the stats/IVs/EVs that get printed on top of the tilemap, depending on the mode.
Place it between static void PrintRibbonCount(void)
and static void BufferLeftColumnStats(void)
.
static void BufferIvOrEvStats(u8 mode)
{
u16 hp, hp2, atk, def, spA, spD, spe;
u8 *currHPString = Alloc(20);
const s8 *natureMod = gNatureStatTable[sMonSummaryScreen->summary.nature];
switch (mode)
{
case 0: // stats mode
default:
hp = sMonSummaryScreen->summary.currentHP;
hp2 = sMonSummaryScreen->summary.maxHP;
atk = sMonSummaryScreen->summary.atk;
def = sMonSummaryScreen->summary.def;
spA = sMonSummaryScreen->summary.spatk;
spD = sMonSummaryScreen->summary.spdef;
spe = sMonSummaryScreen->summary.speed;
break;
case 1: // iv mode
hp = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_HP_IV);
atk = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_ATK_IV);
def = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_DEF_IV);
spA = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_SPATK_IV);
spD = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_SPDEF_IV);
spe = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_SPEED_IV);
break;
case 2: // ev mode
hp = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_HP_EV);
atk = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_ATK_EV);
def = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_DEF_EV);
spA = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_SPATK_EV);
spD = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_SPDEF_EV);
spe = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_SPEED_EV);
break;
}
FillWindowPixelBuffer(sMonSummaryScreen->windowIds[PSS_DATA_WINDOW_SKILLS_STATS_LEFT], 0);
FillWindowPixelBuffer(sMonSummaryScreen->windowIds[PSS_DATA_WINDOW_SKILLS_STATS_RIGHT], 0);
switch (mode)
{
case 0:
default:
BufferStat(currHPString, 0, hp, 0, 3);
BufferStat(gStringVar1, 0, hp2, 1, 3);
BufferStat(gStringVar2, natureMod[STAT_ATK - 1], atk, 2, 7);
BufferStat(gStringVar3, natureMod[STAT_DEF - 1], def, 3, 7);
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsLeftColumnLayout);
PrintLeftColumnStats();
BufferStat(gStringVar1, natureMod[STAT_SPATK - 1], spA, 0, 3);
BufferStat(gStringVar2, natureMod[STAT_SPDEF - 1], spD, 1, 3);
BufferStat(gStringVar3, natureMod[STAT_SPEED - 1], spe, 2, 3);
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsRightColumnLayout);
PrintRightColumnStats();
break;
case 1:
case 2:
BufferStat(gStringVar1, 0, hp, 0, 7);
BufferStat(gStringVar2, 0, atk, 1, 7);
BufferStat(gStringVar3, 0, def, 2, 7);
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsLeftColumnLayoutIVEV);
PrintLeftColumnStats();
BufferStat(gStringVar1, 0, spA, 0, 3);
BufferStat(gStringVar2, 0, spD, 1, 3);
BufferStat(gStringVar3, 0, spe, 2, 3);
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsRightColumnLayout);
PrintRightColumnStats();
break;
}
Free(currHPString);
}
Subsequently, we will modify Task_HandleInput
and actually use our newly added functions. On the skills page, pressing the 'A' button will now cycle through stats, IVs, and EVs, with the tilemap updated to match the selected view. To provide auditory feedback consistent with other buttons, SE_SELECT
is played as well. Lastly, currentStat
will be reset to STAT_STATS
when leaving the skills page or when switching to a different Pokémon.
static void Task_HandleInput(u8 taskId)
{
+ s16 *taskData = gTasks[taskId].data;
+
if (MenuHelpers_ShouldWaitForLinkRecv() != TRUE && !gPaletteFade.active)
{
if (JOY_NEW(DPAD_UP))
{
+ currentStat = STAT_STATS;
ChangeSummaryPokemon(taskId, -1);
}
else if (JOY_NEW(DPAD_DOWN))
{
+ currentStat = STAT_STATS;
ChangeSummaryPokemon(taskId, 1);
}
else if ((JOY_NEW(DPAD_LEFT)) || GetLRKeysPressed() == MENU_L_PRESSED)
{
+ currentStat = STAT_STATS;
ChangePage(taskId, -1);
}
else if ((JOY_NEW(DPAD_RIGHT)) || GetLRKeysPressed() == MENU_R_PRESSED)
{
+ currentStat = STAT_STATS;
ChangePage(taskId, 1);
}
else if (JOY_NEW(A_BUTTON))
{
- if (sMonSummaryScreen->currPageIndex != PSS_PAGE_SKILLS)
+ if (sMonSummaryScreen->currPageIndex == PSS_PAGE_SKILLS)
{
- if (sMonSummaryScreen->currPageIndex == PSS_PAGE_INFO)
- {
- StopPokemonAnimations();
- PlaySE(SE_SELECT);
- BeginCloseSummaryScreen(taskId);
- }
- else // Contest or Battle Moves
- {
- PlaySE(SE_SELECT);
- SwitchToMoveSelection(taskId);
- }
+ // Cycle through IVs/EVs/stats on pressing A
+ PlaySE(SE_SELECT);
+ ChangeSummaryState(taskData, taskId);
+ BufferIvOrEvStats(currentStat);
+ }
+ else if (sMonSummaryScreen->currPageIndex == PSS_PAGE_INFO)
+ {
+ StopPokemonAnimations();
+ PlaySE(SE_SELECT);
+ BeginCloseSummaryScreen(taskId);
+ }
+ else // Contest or Battle Moves
+ {
+ PlaySE(SE_SELECT);
+ SwitchToMoveSelection(taskId);
}
}
else if (JOY_NEW(B_BUTTON))
{
StopPokemonAnimations();
PlaySE(SE_SELECT);
BeginCloseSummaryScreen(taskId);
}
}
}
Finally, we'll make some changes that add or remove the SWITCH prompt in the top-right corner, depending on whether the skills page is being viewed. After all, we want to communicate to the player that pressing 'A' now performs an action while on the skills page.
static void PutPageWindowTilemaps(u8 page)
{
u8 i;
+ LZDecompressWram(gSummaryPage_Skills_Tilemap, sMonSummaryScreen->bgTilemapBuffers[PSS_PAGE_SKILLS][1]);
+ CopyBgTilemapBufferToVram(1);
+
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TITLE);
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE);
ClearWindowTilemap(PSS_LABEL_WINDOW_BATTLE_MOVES_TITLE);
ClearWindowTilemap(PSS_LABEL_WINDOW_CONTEST_MOVES_TITLE);
switch (page)
{
case PSS_PAGE_INFO:
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TITLE);
PutWindowTilemap(PSS_LABEL_WINDOW_PROMPT_CANCEL);
if (InBattleFactory() == TRUE || InSlateportBattleTent() == TRUE)
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_RENTAL);
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TYPE);
break;
case PSS_PAGE_SKILLS:
+ PutWindowTilemap(PSS_LABEL_WINDOW_PROMPT_SWITCH);
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_TITLE);
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT);
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_RIGHT);
PutWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_EXP);
break;
And also:
static void ClearPageWindowTilemaps(u8 page)
{
u8 i;
switch (page)
{
case PSS_PAGE_INFO:
ClearWindowTilemap(PSS_LABEL_WINDOW_PROMPT_CANCEL);
if (InBattleFactory() == TRUE || InSlateportBattleTent() == TRUE)
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_RENTAL);
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_INFO_TYPE);
break;
case PSS_PAGE_SKILLS:
+ ClearWindowTilemap(PSS_LABEL_WINDOW_PROMPT_SWITCH);
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_LEFT);
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_STATS_RIGHT);
ClearWindowTilemap(PSS_LABEL_WINDOW_POKEMON_SKILLS_EXP);
+ CopyBgTilemapBufferToVram(1);
break;
And that's it! Keep in mind that this functionality also works in the Slateport Battle Tent and Battle Factory by default, letting you see the otherwise hidden IVs and EVs of rental Pokémon. If that's not something you want, you can use InBattleFactory() != TRUE && InSlateportBattleTent() != TRUE
to create an exception.