Skip to content

Improve Partner Battle Code

Scyrous edited this page Oct 6, 2024 · 4 revisions

Credits: Scyrous, Ketsuban

In Pokémon Emerald, aside from the Battle Frontier, there's only one moment where you team up with another NPC in battle: Steven Stone, during the Mossdeep Space Center event. This tutorial will address various issues with the code related to that battle and offer improvements to bring it closer to the mechanics seen in modern games. This will also make it easier to introduce new partner battles to the game, without having to worry about the issues described below.


1. Prevent player from using medicine on the partner's Pokémon

In src\party_menu.c, edit ItemUseCB_Medicine:

void ItemUseCB_Medicine(u8 taskId, TaskFunc task)
{
     u16 hp = 0;
     struct Pokemon *mon = &gPlayerParty[gPartyMenu.slotId];
     u16 item = gSpecialVar_ItemId;
     bool8 canHeal, cannotUse;

+    if (IsMultiBattle() == TRUE && (gPartyMenu.slotId == 1 || gPartyMenu.slotId == 4 || gPartyMenu.slotId == 5))
+    {
+        cannotUse = TRUE;
+    }
+    else if (NotUsingHPEVItemOnShedinja(mon, item) == FALSE)
-    if (NotUsingHPEVItemOnShedinja(mon, item) == FALSE)
     {
         cannotUse = TRUE;
     }

This change ensures that medicine items (potions, berries, full heals, etc.) can’t be used on your partner's Pokémon during a multi-battle. If the player tries, the message "It won't have any effect." will be displayed.


2. Prevent player from restoring the partner Pokémon's PP

In src\party_menu.c, edit TryUsePPItem:

static void TryUsePPItem(u8 taskId)
{
     u16 move = MOVE_NONE;
     s16 *moveSlot = &gPartyMenu.data1;
     u16 item = gSpecialVar_ItemId;
     struct PartyMenu *ptr = &gPartyMenu;
     struct Pokemon *mon;

+    if (IsMultiBattle() == TRUE && (ptr->slotId == 1 || ptr->slotId == 4 || ptr->slotId == 5))
+    {
+        gPartyMenuUseExitCallback = FALSE;
+        PlaySE(SE_SELECT);
+        DisplayPartyMenuMessage(gText_WontHaveEffect, TRUE);
+        ScheduleBgCopyTilemapToVram(2);
+        gTasks[taskId].func = Task_ClosePartyMenuAfterText;
+    }
+    else if (ExecuteTableBasedItemEffect_(ptr->slotId, item, *moveSlot))
-    if (ExecuteTableBasedItemEffect_(ptr->slotId, item, *moveSlot))
     {
         gPartyMenuUseExitCallback = FALSE;
         PlaySE(SE_SELECT);
         DisplayPartyMenuMessage(gText_WontHaveEffect, TRUE);
         ScheduleBgCopyTilemapToVram(2);
         gTasks[taskId].func = Task_ClosePartyMenuAfterText;
     }

This change prevents the use of PP-restoring items (ethers, elixirs, leppa berries, etc.) on the partner's Pokémon. If attempted, the same "It won't have any effect." message will appear.


3. Prevent partner's Pokémon from gaining experience

In src\battle_script_commands.c, edit Cmd_getexp (case 2):

+else if ((GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_LEVEL) == MAX_LEVEL) || (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && gBattleStruct->expGetterMonId >= 3))
-else if ((GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_LEVEL) == MAX_LEVEL))
 {
    *(&gBattleStruct->sentInPokes) >>= 1;
    gBattleScripting.getexpState = 5;
    gBattleMoveDamage = 0; // used for exp

This is arguably the most important change: ensuring that the partner's Pokémon don’t gain experience. In the vanilla game, under rare circumstances, Steven's Metang can actually level up. If cheats are applied that multiply experience gained, Steven's Pokémon might even evolve or learn new moves, causing the game to treat them as if they belong to the player. While this can't happen in the vanilla game, modifying Steven's party or adding more partner battles can cause these issues to occur. As of generation 4, partner Pokémon don't gain experience whatsoever. Ultimately, this change just makes the whole mechanic a lot more flexible.


4. Ensure player's Pokémon get 100% of the experience

In src\battle_script_commands.c, edit Cmd_getexp (case 1):

+if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) == SPECIES_NONE || GetMonData(&gPlayerParty[i], MON_DATA_HP) == 0 || GetMonData(&gPlayerParty[i], MON_DATA_LEVEL) == MAX_LEVEL || ((gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER) && i >= 3))
-if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) == SPECIES_NONE || GetMonData(&gPlayerParty[i], MON_DATA_HP) == 0)
     continue;
 if (gBitTable[i] & sentIn)
     viaSentIn++;

This change ensures that the partner's Pokémon are ignored when distributing experience, allowing the player's team to receive 100% of the experience instead of only half. Though unrelated to the partner battle code, this change also prevents level 100 Pokémon from "absorbing" experience that would otherwise go to the player's other Pokémon. It ensures a more efficient experience distribution by preventing level 100 Pokémon from affecting the overall experience gain for other party members.


5. Player cannot whiteout until partner is also out of Pokémon

In src\battle_script_commands.c, edit Cmd_checkteamslost:

    if (gBattleControllerExecFlags)
        return;

-    // Get total HP for the player's party to determine if the player has lost
-    if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER && gPartnerTrainerId == TRAINER_STEVEN_PARTNER)
-    {
-        // In multi battle with Steven, skip his Pokémon
-        for (i = 0; i < MULTI_PARTY_SIZE; i++)
-        {
-            if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) && !GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG))
-                HP_count += GetMonData(&gPlayerParty[i], MON_DATA_HP);
-        }
-    }
-    else
-    {
-        for (i = 0; i < PARTY_SIZE; i++)
-        {
-            if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) && !GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG)
-             && (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !(gBattleStruct->arenaLostPlayerMons & gBitTable[i])))
-            {
-                HP_count += GetMonData(&gPlayerParty[i], MON_DATA_HP);
-            }
-        }
-    }
+    for (i = 0; i < PARTY_SIZE; i++)
+    {
+        if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) && !GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG)
+            && (!(gBattleTypeFlags & BATTLE_TYPE_ARENA) || !(gBattleStruct->arenaLostPlayerMons & gBitTable[i])))
+        {
+            HP_count += GetMonData(&gPlayerParty[i], MON_DATA_HP);
+        }
+    }

This change allows the battle to continue even if all of the player’s Pokémon faint, aligning with the mechanic introduced in the generation 4 games. After all, the partner NPC still has a chance to win the battle. The player will still need to press A whenever a Pokémon faints. Other than that, the battle will play out automatically.

Be aware that if the partner wins and the player has no alive Pokémon in their party, the user will need to heal the player's party immediately afterwards (preferably via a special). Not doing so would leave the player with a fully fainted party, which can cause all sorts of major bugs and glitches.

If both the player and the partner's Pokémon faint, you might want to customize the ''out of usable Pokémon'' string for extra clarity. By default it'll say the player is out of usable Pokémon. While not exactly wrong, it's a bit odd to display this string directly after the partner is out of Pokémon. Ideally, the message should say both the player and partner are out of Pokémon, followed by the typical player whiteout message.

And that's it!

Clone this wiki locally