Skip to content

Holding Select Allows For A Second Register Item

voloved edited this page Aug 31, 2023 · 5 revisions

By devolov
*Goal: Make it possible to register two items; one by pressing select as usual, and one by holding select.
twoselectopt

Add this recolored SEL image into graphics/bag/ as select_button_hold.png.

--------------------- graphics/bag/select_button_hold.png ---------------------
select_button_hold

Add the new item to register into the save data. Saving it in a way where it takes space from unused_3598 allows save compatibility older sav files.

------------------------------- include/global.h -------------------------------
index 3567c7b9f..8c93e2961 100644
@@ -1019,9 +1019,10 @@ struct SaveBlock1
     /*0x322C*/ struct MysteryGiftSave mysteryGift;
               struct ItemSlot bagPocket_Medicine[BAG_MEDICINE_COUNT];
               struct ItemSlot bagPocket_BattleItems[BAG_BATTLEITEMS_COUNT];
               struct ItemSlot bagPocket_Treasures[BAG_TREASURES_COUNT];
-    /*0x3598*/ u8 unused_3598[0x180];
+    /*0x3598*/ u8 unused_3598[0x17E];
+    /*0x3716*/ u16 registeredLongItem; // registered for long press of SELECT button
     /*0x3718*/ u32 trainerHillTimes[NUM_TRAINER_HILL_MODES];
     /*0x3728*/ struct RamScript ramScript;
     /*0x3B14*/ struct RecordMixingGift recordMixingGift;
     /*0x3B24*/ u8 seen2[NUM_DEX_FLAG_BYTES];

Add in logic to detect when the player either held the Select key or released it before the holding timeout.

The hold time-out here is set to 60 cycles, which is about 1.5 seconds measured off a stopwatch.

-------------------------- src/field_control_avatar.c --------------------------
index 45c1c803e..b6eaaf023 100644
@@ -40,8 +40,9 @@
 static EWRAM_DATA u8 sWildEncounterImmunitySteps = 0;
 static EWRAM_DATA u16 sPrevMetatileBehavior = 0;
 static EWRAM_DATA u8 sCurrentDirection = 0;
 static EWRAM_DATA u8 sPreviousDirection = 0;
+static EWRAM_DATA u8 sPlayerSelectHoldFrames = 0;
 
 u8 gSelectedObjectEvent;
 
 static void SetDirectionFromHeldKeys(u16 heldKeys);
@@ -88,8 +89,10 @@ void FieldClearPlayerInput(struct FieldInput *input)
     input->input_field_1_0 = FALSE;
     input->input_field_1_1 = FALSE;
     input->input_field_1_2 = FALSE;
     input->input_field_1_3 = FALSE;
+    input->input_field_1_6 = FALSE;
+    input->input_field_1_7 = FALSE;
     input->dpadDirection = 0;
 }
 
 void FieldGetPlayerInput(struct FieldInput *input, u16 newKeys, u16 heldKeys)
@@ -109,8 +112,21 @@ void FieldGetPlayerInput(struct FieldInput *input, u16 newKeys, u16 heldKeys)
             if (newKeys & A_BUTTON)
                 input->pressedAButton = TRUE;
             if (newKeys & B_BUTTON)
                 input->pressedBButton = TRUE;
+
+            if (sPlayerSelectHoldFrames == 60)
+                input->input_field_1_7 = TRUE;
+            if (JOY_HELD(SELECT_BUTTON))
+                sPlayerSelectHoldFrames = sPlayerSelectHoldFrames < 0xFF ? sPlayerSelectHoldFrames + 1 : 0xFF;
+            else if (sPlayerSelectHoldFrames != 0)
+            {
+               if (sPlayerSelectHoldFrames < 60)
+                    input->input_field_1_6 = TRUE;
+                sPlayerSelectHoldFrames = 0;
+            }
         }
 
         if (heldKeys & (DPAD_UP | DPAD_DOWN | DPAD_LEFT | DPAD_RIGHT))
         {
@@ -197,9 +213,12 @@ int ProcessPlayerFieldInput(struct FieldInput *input)
         PlaySE(SE_WIN_OPEN);
         ShowStartMenu();
         return TRUE;
     }
-    if (input->pressedSelectButton && UseRegisteredKeyItemOnField() == TRUE)
+    if (input->input_field_1_6 && UseRegisteredKeyItemOnField(FALSE) == TRUE)
+        return TRUE;
+    
+    if (input->input_field_1_7 && UseRegisteredKeyItemOnField(TRUE) == TRUE)
         return TRUE;
 
     if(TX_DEBUG_SYSTEM_ENABLE == TRUE && TX_DEBUG_SYSTEM_IN_MENU == FALSE && input->input_field_1_2)
     {
------------------------ include/field_control_avatar.h ------------------------
index e02fcd5af..99ceb154c 100644
@@ -16,10 +16,10 @@ struct FieldInput
     bool8 input_field_1_2:1;
     bool8 input_field_1_3:1;
     bool8 input_field_1_4:1;
     bool8 input_field_1_5:1;
-    bool8 input_field_1_6:1;
-    bool8 input_field_1_7:1;
+    bool8 input_field_1_6:1;  // Used to detect registered item tapping select
+    bool8 input_field_1_7:1;  // Used to detect registered item holding select
     u8 dpadDirection;
 };
 
 void FieldClearPlayerInput(struct FieldInput *pStruct);

Add supporting logic in the item menu to allow for registering with the new option.

------------------------------- src/item_menu.c -------------------------------
index 04608de70..066ff9859 100755
@@ -95,8 +95,10 @@ enum {
     ACTION_BY_NAME,
     ACTION_BY_TYPE,
     ACTION_BY_AMOUNT,
     ACTION_BY_NUMBER,
+    ACTION_REGISTER_TAP,
+    ACTION_REGISTER_HOLD,
     ACTION_DUMMY,
 };
 
 enum {
@@ -203,8 +205,10 @@ static void BagMenu_MoveCursorCallback(s32, bool8, struct ListMenu *);
 static void BagMenu_ItemPrintCallback(u8, u32, u8);
 static void ItemMenu_UseOutOfBattle(u8);
 static void ItemMenu_Toss(u8);
 static void ItemMenu_Register(u8);
+static void ItemMenu_RegisterHold(u8);
+static void ItemMenu_CheckWhichRegister(u8);
 static void ItemMenu_Give(u8);
 static void ItemMenu_Cancel(u8);
 static void ItemMenu_UseInBattle(u8);
 static void ItemMenu_CheckTag(u8);
@@ -294,18 +298,21 @@ static const struct ListMenuTemplate sItemListMenu
+static const u8 sMenuText_Tap[] = _("Tap");
+static const u8 sMenuText_Hold[] = _("Hold");
+static const u8 sText_RegisterHow[] = _("Register this\nitem by tapping or\nholding SELECT?");
 static const struct MenuAction sItemMenuActions[] = {
     [ACTION_USE]               = {gMenuText_Use,      ItemMenu_UseOutOfBattle},
     [ACTION_TOSS]              = {gMenuText_Toss,     ItemMenu_Toss},
-    [ACTION_REGISTER]          = {gMenuText_Register, ItemMenu_Register},
+    [ACTION_REGISTER]          = {gMenuText_Register, ItemMenu_CheckWhichRegister},
     [ACTION_GIVE]              = {gMenuText_Give,     ItemMenu_Give},
     [ACTION_CANCEL]            = {gText_Cancel2,      ItemMenu_Cancel},
     [ACTION_BATTLE_USE]        = {gMenuText_Use,      ItemMenu_UseInBattle},
     [ACTION_CHECK]             = {gMenuText_Check,    ItemMenu_UseOutOfBattle},
     [ACTION_WALK]              = {gMenuText_Walk,     ItemMenu_UseOutOfBattle},
-    [ACTION_DESELECT]          = {gMenuText_Deselect, ItemMenu_Register},
+    [ACTION_DESELECT]          = {gMenuText_Deselect, ItemMenu_CheckWhichRegister},
     [ACTION_CHECK_TAG]         = {gMenuText_CheckTag, ItemMenu_CheckTag},
     [ACTION_CONFIRM]           = {gMenuText_Confirm,  Task_FadeAndCloseBagMenu},
     [ACTION_SHOW]              = {gMenuText_Show,     ItemMenu_Show},
     [ACTION_GIVE_FAVOR_LADY]   = {gMenuText_Give2,    ItemMenu_GiveFavorLady},
@@ -313,8 +320,10 @@ static const struct MenuAction sItemMenuActions[] = {
     [ACTION_BY_NAME]           = {sMenuText_ByName,   ItemMenu_SortByName},
     [ACTION_BY_TYPE]           = {sMenuText_ByType,   ItemMenu_SortByType},
     [ACTION_BY_NUMBER]         = {sMenuText_ByNumber, ItemMenu_SortByID},
     [ACTION_BY_AMOUNT]         = {sMenuText_ByAmount, ItemMenu_SortByAmount},
+    [ACTION_REGISTER_TAP]      = {sMenuText_Tap,      ItemMenu_Register},
+    [ACTION_REGISTER_HOLD]     = {sMenuText_Hold,     ItemMenu_RegisterHold},
     [ACTION_DUMMY]             = {gText_EmptyString2, NULL}
 };
 
 // these are all 2D arrays with a width of 2 but are represented as 1D arrays
@@ -373,8 +382,14 @@ static const u8 sContextMenuItems_FavorLady[] = {
 static const u8 sContextMenuItems_QuizLady[] = {
     ACTION_CONFIRM_QUIZ_LADY, ACTION_CANCEL
 };
 
+static const u8 sRegisterOptions[] =
+{
+    ACTION_REGISTER_TAP, ACTION_REGISTER_HOLD,
+    ACTION_DUMMY,        ACTION_CANCEL
+};
+
 static const TaskFunc sContextMenuFuncs[] = {
     [ITEMMENULOCATION_FIELD] =                  Task_ItemContext_Normal,
     [ITEMMENULOCATION_BATTLE] =                 Task_ItemContext_Normal,
     [ITEMMENULOCATION_PARTY] =                  Task_ItemContext_GiveToParty,
@@ -407,8 +422,9 @@ static const struct ScrollArrowsTemplate sBagScrollArrowsTemplate = {
     .palNum = 0,
 };
 
 static const u8 sRegisteredSelect_Gfx[] = INCBIN_U8("graphics/bag/select_button.4bpp");
+static const u8 sRegisteredSelectLong_Gfx[] = INCBIN_U8("graphics/bag/select_button_hold.4bpp");
 
 enum {
     COLORID_NORMAL,
     COLORID_POCKET_NAME,
@@ -695,8 +711,9 @@ void VBlankCB_BagMenuRun(void)
 #define tListTaskId        data[0]
 #define tListPosition      data[1]
 #define tQuantity          data[2]
 #define tNeverRead         data[3]
+#define tIsRegisterHold    data[5]
 #define tItemCount         data[8]
 #define tMsgWindowId       data[10]
 #define tPocketSwitchDir   data[11]
 #define tPocketSwitchTimer data[12]
@@ -982,9 +999,9 @@ static void BagMenu_MoveCursorCallback(s32 itemIndex, bool8 onInit, struct ListM
 static void BagMenu_ItemPrintCallback(u8 windowId, u32 itemIndex, u8 y)
 {
     u16 itemId;
     u16 itemQuantity;
     int offset;
+    int selOff;
 
     if (itemIndex != LIST_CANCEL)
     {
         if (gBagMenu->toSwapPos != NOT_SWAPPING)
@@ -1017,14 +1034,20 @@ static void BagMenu_ItemPrintCallback(u8 windowId, u32 itemIndex, u8 y)
             offset = GetStringRightAlignXOffset(FONT_NARROW, gStringVar4, 119);
             BagMenu_Print(windowId, FONT_NARROW, gStringVar4, offset, y, 0, 0, TEXT_SKIP_DRAW, COLORID_NORMAL);
         }
         else
         {
             // Print registered icon
             if (gSaveBlock1Ptr->registeredItem && gSaveBlock1Ptr->registeredItem == itemId)
                 BlitBitmapToWindow(windowId, sRegisteredSelect_Gfx, 96, y - 1, 24, 16);
+            else if (gSaveBlock1Ptr->registeredLongItem && gSaveBlock1Ptr->registeredLongItem == itemId)
+                BlitBitmapToWindow(windowId, sRegisteredSelectLong_Gfx, 96, y - 1, 24, 16);
         }
     }
 }
 
@@ -1682,9 +1712,9 @@ static void OpenContextMenu(u8 taskId)
                 else{
                     gBagMenu->contextMenuNumItems = ARRAY_COUNT(sContextMenuItems_KeyItemsPocket);
                     memcpy(&gBagMenu->contextMenuItemsBuffer, &sContextMenuItems_KeyItemsPocket, sizeof(sContextMenuItems_KeyItemsPocket));
                 }
-                if (gSaveBlock1Ptr->registeredItem == gSpecialVar_ItemId)
+                if (gSaveBlock1Ptr->registeredItem == gSpecialVar_ItemId || gSaveBlock1Ptr->registeredLongItem == gSpecialVar_ItemId)
                     gBagMenu->contextMenuItemsBuffer[1] = ACTION_DESELECT;
                 if (gSpecialVar_ItemId == ITEM_MACH_BIKE || gSpecialVar_ItemId == ITEM_ACRO_BIKE)
                 {
                     if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_MACH_BIKE | PLAYER_AVATAR_FLAG_ACRO_BIKE))
@@ -1976,20 +2006,64 @@ static void ItemMenu_Register(u8 taskId)
 {
     s16 *data = gTasks[taskId].data;
     u16 *scrollPos = &gBagPosition.scrollPosition[gBagPosition.pocket];
     u16 *cursorPos = &gBagPosition.cursorPosition[gBagPosition.pocket];
+    u16 *registerSlot;
 
-    if (gSaveBlock1Ptr->registeredItem == gSpecialVar_ItemId)
-        gSaveBlock1Ptr->registeredItem = 0;
+    registerSlot = tIsRegisterHold ? &gSaveBlock1Ptr->registeredLongItem : &gSaveBlock1Ptr->registeredItem;
+    if (*registerSlot == gSpecialVar_ItemId)
+        *registerSlot = 0;
     else
-        gSaveBlock1Ptr->registeredItem = gSpecialVar_ItemId;
+        *registerSlot = gSpecialVar_ItemId;
+    tIsRegisterHold = FALSE;
     DestroyListMenuTask(tListTaskId, scrollPos, cursorPos);
     LoadBagItemListBuffers(gBagPosition.pocket);
     tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, *scrollPos, *cursorPos);
     ScheduleBgCopyTilemapToVram(0);
     ItemMenu_Cancel(taskId);
 }
 
+static void ItemMenu_RegisterHold(u8 taskId)
+{
+    gTasks[taskId].tIsRegisterHold = TRUE;
+    gTasks[taskId].func = ItemMenu_Register;
+}
+
+static void AddRegisterSubMenu(void)
+{
+    gBagMenu->contextMenuItemsPtr = sRegisterOptions;
+    memcpy(&gBagMenu->contextMenuItemsBuffer, &sRegisterOptions, NELEMS(sRegisterOptions));
+    gBagMenu->contextMenuNumItems = NELEMS(sRegisterOptions);    
+    StringExpandPlaceholders(gStringVar4, sText_RegisterHow);
+    FillWindowPixelBuffer(1, PIXEL_FILL(0));
+    BagMenu_Print(1, 1, gStringVar4, 3, 1, 0, 0, 0, 0);
+    PrintContextMenuItemGrid(BagMenu_AddWindow(ITEMWIN_2x2), 2, 2);
+}
+
+static void Task_LoadRegisterOptions(u8 taskId)
+{
+    s16 *data = gTasks[taskId].data;
+    if (gSaveBlock1Ptr->registeredItem == gSpecialVar_ItemId){
+        // If the item is already registered, then deselect it.
+        gTasks[taskId].func = ItemMenu_Register;
+    }
+    else if (gSaveBlock1Ptr->registeredLongItem == gSpecialVar_ItemId){
+        // If the item is already registered, then deselect it.
+        gTasks[taskId].func = ItemMenu_RegisterHold;
+    }
+    else{
+        BagDestroyPocketScrollArrowPair();
+        RemoveContextWindow();
+        AddRegisterSubMenu();
+        gTasks[taskId].func = Task_ItemContext_MultipleRows;
+    }
+}
+
+static void ItemMenu_CheckWhichRegister(u8 taskId)
+{
+    gTasks[taskId].func = Task_LoadRegisterOptions;
+}
+
 static void ItemMenu_Give(u8 taskId)
 {
     RemoveContextWindow();
     if (!IsWritingMailAllowed(gSpecialVar_ItemId))
@@ -2101,35 +2175,39 @@ static void Task_ItemContext_GiveToPC(u8 taskId)
 }
 
 #define tUsingRegisteredKeyItem data[3] // See usage in item_use.c
 
-bool8 UseRegisteredKeyItemOnField(void)
+bool8 UseRegisteredKeyItemOnField(bool8 isRegisterHold)
 {
     u8 taskId;
+    u16 item = isRegisterHold ? gSaveBlock1Ptr->registeredLongItem : gSaveBlock1Ptr->registeredItem;
 
     if (InUnionRoom() == TRUE || InBattlePyramid() || InBattlePike() || InMultiPartnerRoom() == TRUE)
         return FALSE;
     HideMapNamePopUpWindow();
     ChangeBgY_ScreenOff(0, 0, BG_COORD_SET);
-    if (gSaveBlock1Ptr->registeredItem != ITEM_NONE)
+    if (item != ITEM_NONE)
     {
-        if (CheckBagHasItem(gSaveBlock1Ptr->registeredItem, 1) == TRUE)
+        if (CheckBagHasItem(item, 1) == TRUE)
         {
             LockPlayerFieldControls();
             FreezeObjectEvents();
             PlayerFreeze();
             StopPlayerAvatar();
-            gSpecialVar_ItemId = gSaveBlock1Ptr->registeredItem;
-            taskId = CreateTask(ItemId_GetFieldFunc(gSaveBlock1Ptr->registeredItem), 8);
+            gSpecialVar_ItemId = item;
+            taskId = CreateTask(ItemId_GetFieldFunc(item), 8);
             gTasks[taskId].tUsingRegisteredKeyItem = TRUE;
             return TRUE;
         }
         else
         {
-            gSaveBlock1Ptr->registeredItem = ITEM_NONE;
+            item = ITEM_NONE;
         }
     }
-    ScriptContext_SetupScript(EventScript_SelectWithoutRegisteredItem);
+    if (isRegisterHold)
+        ScriptContext_SetupScript(EventScript_SelectWithoutRegisteredLongItem);
+    else
+        ScriptContext_SetupScript(EventScript_SelectWithoutRegisteredItem);
     return TRUE;
 }
 
 #undef tUsingRegisteredKeyItem
----------------------------- include/item_menu.h -----------------------------
index ce03cdacb..f65a12cd9 100644
@@ -93,9 +93,9 @@ void CB2_BagMenuFromBattle(void);
 void UpdatePocketListPosition(u8 pocketId);
 void CB2_ReturnToBagMenuPocket(void);
 void CB2_BagMenuFromStartMenu(void);
 u8 GetItemListPosition(u8 pocketId);
-bool8 UseRegisteredKeyItemOnField(void);
+bool8 UseRegisteredKeyItemOnField(bool8 isRegisterHold);
 void CB2_GoToSellMenu(void);
 void GoToBagMenu(u8 bagMenuType, u8 pocketId, void ( *exitCallback)());
 void DoWallyTutorialBagMenu(void);
 void ResetBagScrollPositions(void);

Set it to nothing on a new game.

-------------------------------- src/new_game.c --------------------------------
index aa0a1f240..4b9e5b18e 100644
@@ -189,8 +189,9 @@ void NewGameInitData(void)
     ResetPokemonStorageSystem();
     ClearRoamerData();
     ClearRoamerLocationData();
     gSaveBlock1Ptr->registeredItem = 0;
+    gSaveBlock1Ptr->registeredLongItem = 0;
     ClearBag();
     NewGameInitPCItems();
     ClearPokeblocks();
     ClearDecorationInventories();

Add in the event script that explains how to register with the new option if nothing is registered.

----------------------------- data/event_scripts.s -----------------------------
index 22c15371d..02db7c064 100644
@@ -861,8 +861,12 @@ gText_SandstormIsVicious::
 gText_SelectWithoutRegisteredItem::
 	.string "An item in the BAG can be\n"
 	.string "registered to SELECT for easy use.$"
 
+gText_SelectWithoutRegisteredLongItem::
+	.string "A second item in the BAG can be\n"
+	.string "registered to holding SELECT.$"
+
 gText_PokemonTrainerSchoolEmail::
 	.string "There's an e-mail from POKéMON TRAINER\n"
 	.string "SCHOOL.\p"
 	.string "… … … … … …\p"
@@ -968,8 +972,12 @@ gText_LegendaryFlewAway::
 EventScript_SelectWithoutRegisteredItem::
 	msgbox gText_SelectWithoutRegisteredItem, MSGBOX_SIGN
 	end
 
+EventScript_SelectWithoutRegisteredLongItem::
+	msgbox gText_SelectWithoutRegisteredLongItem, MSGBOX_SIGN
+	end
+
 	.include "data/scripts/field_poison.inc"
 
 Common_EventScript_NopReturn::
 	return
--------------------------- include/event_scripts.h ---------------------------
index a64b8ffc4..953f78fc7 100644
@@ -615,8 +615,9 @@ extern const u8 BerryTree_EventScript_ItemUsePlantBerry[];
 extern const u8 BerryTree_EventScript_ItemUseWailmerPail[];
 extern const u8 BattleFrontier_OutsideEast_EventScript_WaterSudowoodo[];
 
 extern const u8 EventScript_SelectWithoutRegisteredItem[];
+extern const u8 EventScript_SelectWithoutRegisteredLongItem[];
 
 // overworld
 extern const u8 EventScript_WhiteOut[];
 extern const u8 EventScript_ResetMrBriney[];

Modify SwapRegisteredBike for the new register option.

--------------------------------- src/item.c ---------------------------------
void SwapRegisteredBike(void)
{
    switch (gSaveBlock1Ptr->registeredItem)
    {
    case ITEM_MACH_BIKE:
        gSaveBlock1Ptr->registeredItem = ITEM_ACRO_BIKE;
        break;
    case ITEM_ACRO_BIKE:
        gSaveBlock1Ptr->registeredItem = ITEM_MACH_BIKE;
        break;
    }
+    switch (gSaveBlock1Ptr->registeredLongItem)
+    {
+    case ITEM_MACH_BIKE:
+        gSaveBlock1Ptr->registeredLongItem = ITEM_ACRO_BIKE;
+        break;
+    case ITEM_ACRO_BIKE:
+        gSaveBlock1Ptr->registeredLongItem = ITEM_MACH_BIKE;
+        break;
+    }
}
Clone this wiki locally