6 Move Item
Scyrous edited this page 2024-10-20 12:55:35 +02:00

Credits: Zeturic, Deokishisu, Scyrous

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.

  • After selecting MOVE in the ITEM menu, the text in the bottom-left corner will now read ''Move {STR_VAR_2} to where?'' instead of ''Move item to where?'', with STR_VAR_2 being the item you're attempting to move. This makes it much easier to identify what item the Pokémon is currently holding.

  • BUGFIX: when BattlePyramidChooseMonHeldItems is called, the party menu window displays the options TAKE/TOSS/CANCEL. Since we're adding a new MOVE option and adjusting the window size, we need to ensure this specific Battle Pyramid window retains its original dimensions, otherwise there will be a blank entry at the bottom.


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 {STR_VAR_2} 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,

Also in DisplaySelectionWindow:

     case SELECTWINDOW_ITEM:
         window = sItemGiveTakeWindowTemplate;
         break;
+    case SELECTWINDOW_PYRAMID:
+        window = sItemPyramidTakeTossWindowTemplate;
+        break;
     case SELECTWINDOW_MAIL:
         window = sMailReadTakeWindowTemplate;
         break;

In CreateSelectionWindow:

         item = GetMonData(mon, MON_DATA_HELD_ITEM);
         if (item != ITEM_NONE)
         {
             SetPartyMonSelectionActions(gPlayerParty, gPartyMenu.slotId, GetPartyMenuActionsType(mon));
-            DisplaySelectionWindow(SELECTWINDOW_ITEM);
+            DisplaySelectionWindow(SELECTWINDOW_PYRAMID);
             CopyItemName(item, gStringVar2);
             DisplayPartyMenuStdMessage(PARTY_MSG_ALREADY_HOLDING_ONE);
         }

And in CursorCb_Cancel2:

     else
     {
-        DisplaySelectionWindow(SELECTWINDOW_ITEM);
+        DisplaySelectionWindow(SELECTWINDOW_PYRAMID);
         CopyItemName(GetMonData(mon, MON_DATA_HELD_ITEM), gStringVar2);
         DisplayPartyMenuStdMessage(PARTY_MSG_ALREADY_HOLDING_ONE);
     }

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];
    u16 item = GetMonData(mon, MON_DATA_HELD_ITEM);

    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 {STR_VAR_2} to where?" in bottom left
        CopyItemName(item, gStringVar2);
        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

Also:

 #define SELECTWINDOW_ACTIONS  0
 #define SELECTWINDOW_ITEM     1
 #define SELECTWINDOW_MAIL     2
 #define SELECTWINDOW_MOVES    3
+#define SELECTWINDOW_PYRAMID  4

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},

In const sActionStringTable[]:

     [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,
};

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

That is all!