Skip to content
Scyrous edited this page Sep 2, 2024 · 2 revisions

Credits: Zeturic, Deokishisu

Adds a MOVE option to the ITEM menu which allows you to move held items directly between Pokémon in your party, instead of having to use the bag as a middle man. Originally posted by Zeturic in this pokecommunity post. This guide also includes the mail fix by Deokishisu in this post and the pokeemerald code is much more recent.

Lastly, some changes have been made compared to the code in the pokecommunity posts:

  • When two items are swapped, the dialogue box will now display all four buffered strings (two Pokémon, two items) in just two lines of dialogue, rather than four lines. This has been tested with max length Pokémon/item names in mind.

  • After moving/swapping items, the selection cursor will now stay on the second Pokémon instead of returning to the first. This matches the behavior seen when switching Pokémon or using Softboiled in the party menu.


1. In include/strings.h, end of the file:

+extern const u8 gMenuText_Move[];
 extern const u8 gText_MoveItemWhere[];
 extern const u8 gText_XsYAnd[];
 extern const u8 gText_XsYWereSwapped[];

 #endif // GUARD_STRINGS_H

2. In src/strings.c, end of the file:

 const u8 gText_Berry[] = _("BERRY");
 const u8 gText_Berries[] = _("BERRIES");
+const u8 gMenuText_Move[] = _("MOVE");
+const u8 gText_MoveItemWhere[] = _("Move item to where?");
+const u8 gText_XsYAnd[] = _("{STR_VAR_1}'s {STR_VAR_2} and\n");
+const u8 gText_XsYWereSwapped[] = _("{STR_VAR_1}'s {STR_VAR_2} were swapped!{PAUSE_UNTIL_PRESS}");

3. In src/party_menu.c:

 static void CursorCb_Cancel1(u8);
 static void CursorCb_Item(u8);
 static void CursorCb_Give(u8);
 static void CursorCb_TakeItem(u8);
+static void CursorCb_MoveItem(u8);
 static void CursorCb_Mail(u8);
 static void CursorCb_Read(u8);
 static void CursorCb_TakeMail(u8);
 static void CursorCb_Cancel2(u8);

Also:

enum {
     MENU_SUMMARY,
     MENU_SWITCH,
     MENU_CANCEL1,
     MENU_ITEM,
     MENU_GIVE,
     MENU_TAKE_ITEM,
+    MENU_MOVE_ITEM,
     MENU_MAIL,
     MENU_TAKE_MAIL,
     MENU_READ,

At the end of the file, add the following two functions:

void CursorCb_MoveItemCallback(u8 taskId)
{
    u16 item1, item2;
    u8 buffer[100];

    if (gPaletteFade.active || MenuHelpers_ShouldWaitForLinkRecv())
        return;

    switch (PartyMenuButtonHandler(&gPartyMenu.slotId2))
    {
    case 2:     // User hit B or A while on Cancel
        HandleChooseMonCancel(taskId, &gPartyMenu.slotId2);
        break;
    case 1:     // User hit A on a Pokemon
        // Pokemon can't give away items to eggs or themselves
        if (GetMonData(&gPlayerParty[gPartyMenu.slotId2], MON_DATA_IS_EGG)
            || gPartyMenu.slotId == gPartyMenu.slotId2)
        {
            PlaySE(SE_FAILURE);
            return;
        }

        if(GetMonData(&gPlayerParty[gPartyMenu.slotId2], MON_DATA_HELD_ITEM) >= ITEM_ORANGE_MAIL && GetMonData(&gPlayerParty[gPartyMenu.slotId2], MON_DATA_HELD_ITEM) <= ITEM_RETRO_MAIL)
        {
            PlaySE(SE_FAILURE);
            return;
        }

        PlaySE(SE_SELECT);
        gPartyMenu.action = PARTY_ACTION_CHOOSE_MON;

        // look up held items
        item1 = GetMonData(&gPlayerParty[gPartyMenu.slotId], MON_DATA_HELD_ITEM);
        item2 = GetMonData(&gPlayerParty[gPartyMenu.slotId2], MON_DATA_HELD_ITEM);

        // swap the held items
        SetMonData(&gPlayerParty[gPartyMenu.slotId], MON_DATA_HELD_ITEM, &item2);
        SetMonData(&gPlayerParty[gPartyMenu.slotId2], MON_DATA_HELD_ITEM, &item1);

        // update the held item icons
        UpdatePartyMonHeldItemSprite(
            &gPlayerParty[gPartyMenu.slotId],
            &sPartyMenuBoxes[gPartyMenu.slotId]
        );

        UpdatePartyMonHeldItemSprite(
            &gPlayerParty[gPartyMenu.slotId2],
            &sPartyMenuBoxes[gPartyMenu.slotId2]
        );

        // create the string describing the move
        if (item2 == ITEM_NONE)
        {
            GetMonNickname(&gPlayerParty[gPartyMenu.slotId2], gStringVar1);
            CopyItemName(item1, gStringVar2);
            StringExpandPlaceholders(gStringVar4, gText_PkmnWasGivenItem);
        }
        else
        {
            GetMonNickname(&gPlayerParty[gPartyMenu.slotId], gStringVar1);
            CopyItemName(item1, gStringVar2);
            StringExpandPlaceholders(buffer, gText_XsYAnd);

            StringAppend(buffer, gText_XsYWereSwapped);
            GetMonNickname(&gPlayerParty[gPartyMenu.slotId2], gStringVar1);
            CopyItemName(item2, gStringVar2);
            StringExpandPlaceholders(gStringVar4, buffer);
        }

        // display the string
        DisplayPartyMenuMessage(gStringVar4, TRUE);

        // update colors of selected boxes, move selection cursor to second mon
        AnimatePartySlot(gPartyMenu.slotId, 0);
        gPartyMenu.slotId = gPartyMenu.slotId2;
        AnimatePartySlot(gPartyMenu.slotId2, 1);

        // return to the main party menu
        ScheduleBgCopyTilemapToVram(2);
        gTasks[taskId].func = Task_UpdateHeldItemSprite;
        break;
    }
}

void CursorCb_MoveItem(u8 taskId)
{
    struct Pokemon *mon = &gPlayerParty[gPartyMenu.slotId];

    PlaySE(SE_SELECT);

    // delete old windows
    PartyMenuRemoveWindow(&sPartyMenuInternal->windowId[1]);
    PartyMenuRemoveWindow(&sPartyMenuInternal->windowId[0]);

    if (GetMonData(mon, MON_DATA_HELD_ITEM) != ITEM_NONE)
    {
        gPartyMenu.action = PARTY_ACTION_SWITCH;

        // show "Move item to where" in bottom left
        DisplayPartyMenuStdMessage(PARTY_MSG_MOVE_ITEM_WHERE);
        // update color of first selected box
        AnimatePartySlot(gPartyMenu.slotId, 1);

        // set up callback
        gPartyMenu.slotId2 = gPartyMenu.slotId;
        gTasks[taskId].func = CursorCb_MoveItemCallback;
    }
    else
    {
        // create and display string about lack of hold item
        GetMonNickname(mon, gStringVar1);
        StringExpandPlaceholders(gStringVar4, gText_PkmnNotHolding);
        DisplayPartyMenuMessage(gStringVar4, TRUE);

        // return to the main party menu
        ScheduleBgCopyTilemapToVram(2);
        gTasks[taskId].func = Task_UpdateHeldItemSprite;
    }
}

4. In include/constants/party_menu.h:

 #define PARTY_MSG_BOOST_PP_WHICH_MOVE       23
 #define PARTY_MSG_DO_WHAT_WITH_ITEM         24
 #define PARTY_MSG_DO_WHAT_WITH_MAIL         25
 #define PARTY_MSG_ALREADY_HOLDING_ONE       26
+#define PARTY_MSG_MOVE_ITEM_WHERE           27
 #define PARTY_MSG_NONE                      127

5. In src/data/party_menu.h:

struct
{
    const u8 *text;
    TaskFunc func;
} static const sCursorOptions[] =
{
     [MENU_SUMMARY] = {gText_Summary5, CursorCb_Summary},
     [MENU_SWITCH] = {gText_Switch2, CursorCb_Switch},
     [MENU_CANCEL1] = {gText_Cancel2, CursorCb_Cancel1},
     [MENU_ITEM] = {gText_Item, CursorCb_Item},
     [MENU_GIVE] = {gMenuText_Give, CursorCb_Give},
     [MENU_TAKE_ITEM] = {gText_Take, CursorCb_TakeItem},
+    [MENU_MOVE_ITEM] = {gMenuText_Move, CursorCb_MoveItem},
     [MENU_MAIL] = {gText_Mail, CursorCb_Mail},
     [MENU_TAKE_MAIL] = {gText_Take2, CursorCb_TakeMail},

Also:

static const u8 *const sActionStringTable[] =
{
     [PARTY_MSG_CHOOSE_MON]             = gText_ChoosePokemon,
     [PARTY_MSG_CHOOSE_MON_OR_CANCEL]   = gText_ChoosePokemonCancel,
     [PARTY_MSG_CHOOSE_MON_AND_CONFIRM] = gText_ChoosePokemonConfirm,
     [PARTY_MSG_MOVE_TO_WHERE]          = gText_MoveToWhere,
     [PARTY_MSG_TEACH_WHICH_MON]        = gText_TeachWhichPokemon,
     [PARTY_MSG_USE_ON_WHICH_MON]       = gText_UseOnWhichPokemon,
     [PARTY_MSG_GIVE_TO_WHICH_MON]      = gText_GiveToWhichPokemon,
     [PARTY_MSG_NOTHING_TO_CUT]         = gText_NothingToCut,
     [PARTY_MSG_CANT_SURF_HERE]         = gText_CantSurfHere,
     [PARTY_MSG_ALREADY_SURFING]        = gText_AlreadySurfing,
     [PARTY_MSG_CURRENT_TOO_FAST]       = gText_CurrentIsTooFast,
     [PARTY_MSG_ENJOY_CYCLING]          = gText_EnjoyCycling,
     [PARTY_MSG_ALREADY_IN_USE]         = gText_InUseAlready_PM,
     [PARTY_MSG_CANT_USE_HERE]          = gText_CantUseHere,
     [PARTY_MSG_NO_MON_FOR_BATTLE]      = gText_NoPokemonForBattle,
     [PARTY_MSG_CHOOSE_MON_2]           = gText_ChoosePokemon2,
     [PARTY_MSG_NOT_ENOUGH_HP]          = gText_NotEnoughHp,
     [PARTY_MSG_X_MONS_ARE_NEEDED]      = gText_PokemonAreNeeded,
     [PARTY_MSG_MONS_CANT_BE_SAME]      = gText_PokemonCantBeSame,
     [PARTY_MSG_NO_SAME_HOLD_ITEMS]     = gText_NoIdenticalHoldItems,
     [PARTY_MSG_UNUSED]                 = gText_EmptyString2,
     [PARTY_MSG_DO_WHAT_WITH_MON]       = gText_DoWhatWithPokemon,
     [PARTY_MSG_RESTORE_WHICH_MOVE]     = gText_RestoreWhichMove,
     [PARTY_MSG_BOOST_PP_WHICH_MOVE]    = gText_BoostPp,
     [PARTY_MSG_DO_WHAT_WITH_ITEM]      = gText_DoWhatWithItem,
     [PARTY_MSG_DO_WHAT_WITH_MAIL]      = gText_DoWhatWithMail,
     [PARTY_MSG_ALREADY_HOLDING_ONE]    = gText_AlreadyHoldingOne,
+    [PARTY_MSG_MOVE_ITEM_WHERE]        = gText_MoveItemWhere,
};

And also:

 static const u8 sPartyMenuAction_NoEntrySummaryCancel[] = {MENU_NO_ENTRY, MENU_SUMMARY, MENU_CANCEL1};
 static const u8 sPartyMenuAction_StoreSummaryCancel[] = {MENU_STORE, MENU_SUMMARY, MENU_CANCEL1};
-static const u8 sPartyMenuAction_GiveTakeItemCancel[] = {MENU_GIVE, MENU_TAKE_ITEM, MENU_CANCEL2};
+static const u8 sPartyMenuAction_GiveTakeItemCancel[] = {MENU_GIVE, MENU_TAKE_ITEM, MENU_MOVE_ITEM, MENU_CANCEL2};
 static const u8 sPartyMenuAction_ReadTakeMailCancel[] = {MENU_READ, MENU_TAKE_MAIL, MENU_CANCEL2};
 static const u8 sPartyMenuAction_RegisterSummaryCancel[] = {MENU_REGISTER, MENU_SUMMARY, MENU_CANCEL1};

And finally:

static const struct WindowTemplate sItemGiveTakeWindowTemplate =
{
     .bg = 2,
     .tilemapLeft = 23,
-    .tilemapTop = 13,
+    .tilemapTop = 11,
     .width = 6,
-    .height = 6,
+    .height = 8,
     .paletteNum = 14,
     .baseBlock = 0x39D,
};

That is all!

Clone this wiki locally