Skip to content

Show IVs EVs in Summary Screen

Scyrous edited this page Aug 3, 2024 · 3 revisions

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:

Example

All of the edits listed below are done in src\pokemon_summary_screen.c, with the exception of the tileset replacement.


Contents

  1. Replace summary screen tileset
  2. Forward declarations
  3. Update stats tilemap
  4. Implement skills page cycling
  5. Add switch prompt

1. Replace summary screen tileset

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.

tiles.png


2. Forward declarations

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}");

3. Update stats tilemap

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);
 }

4. Implement skills page cycling

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);
         }
     }
 }

5. Add switch prompt

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.

Clone this wiki locally