From a7cd4ca592ff6f85186174002fca6b11e4c61eb1 Mon Sep 17 00:00:00 2001 From: sbird Date: Tue, 17 Jan 2023 21:21:07 +0100 Subject: [PATCH] [script-command, dynmultichoice] implement event handler --- asm/macros/event.inc | 13 +-- include/constants/script_menu.h | 6 ++ include/script_menu.h | 2 +- src/scrcmd.c | 5 +- src/script_menu.c | 149 ++++++++++++++++++++++++++++++-- 5 files changed, 161 insertions(+), 14 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 4e9ab1855b..d4a3159233 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1729,7 +1729,7 @@ .2byte \quantity .endm - .macro _dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req argv:vararg + .macro _dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req, callbacks:req argv:vararg .byte 0xe3 .byte \left .byte \top @@ -1737,6 +1737,7 @@ .byte \maxBeforeScroll .byte \shouldSort .2byte \initialSelected + .byte \callbacks .byte (.Ldynmultichoice_\@_2 - .Ldynmultichoice_\@_1) / 4 .Ldynmultichoice_\@_1: .4byte \argv @@ -1746,18 +1747,18 @@ @ Displays a multichoice box from which the user can choose a selection, and blocks script execution until a selection is made. @ Lists of options are provided in argv. @ If ignoreBPress is set to a non-zero value, then the user will not be allowed to back out of the multichoice with the B button. - .macro dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req argv:vararg - _dynamicmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, FALSE, \initialSelected, \argv + .macro dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, initialSelected:req, callbacks:req argv:vararg + _dynmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, FALSE, \initialSelected, \callbacks, \argv .endm .macro dynmultipush name:req, id:req .byte 0xe4 .4byte \name - .byte \id + .2byte \id .endm - .macro dynmultistack left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req - _dynmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, \shouldSort, \initialSelected, NULL + .macro dynmultistack left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req, callbacks:req + _dynmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, \shouldSort, \initialSelected, \callbacks, NULL .endm diff --git a/include/constants/script_menu.h b/include/constants/script_menu.h index c58df7335f..e9dc39d058 100644 --- a/include/constants/script_menu.h +++ b/include/constants/script_menu.h @@ -165,4 +165,10 @@ #define STDSTRING_BATTLE_PIKE 28 #define STDSTRING_BATTLE_PYRAMID 29 +// Dynamic Multichoice Callbacks + +#define DYN_MULTICHOICE_CB_DEBUG 0 +#define DYN_MULTICHOICE_CB_SHOW_ITEM 1 +#define DYN_MULTICHOICE_CB_NONE 255 + #endif //GUARD_SCRIPT_MENU_CONSTANTS_H diff --git a/include/script_menu.h b/include/script_menu.h index c37b3603dd..a138548c2e 100644 --- a/include/script_menu.h +++ b/include/script_menu.h @@ -29,7 +29,7 @@ struct ListMenuItem *MultichoiceDynamic_PopElement(void); struct ListMenuItem *MultichoiceDynamic_PeekElement(void); struct ListMenuItem *MultichoiceDynamic_PeekElementAt(u32 index); void MultichoiceDynamic_DestroyStack(void); -bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll, u32 initialRow); +bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll, u32 initialRow, u32 callbackSet); bool8 ScriptMenu_Multichoice(u8 left, u8 top, u8 multichoiceId, bool8 ignoreBPress); bool8 ScriptMenu_MultichoiceWithDefault(u8 left, u8 top, u8 multichoiceId, bool8 ignoreBPress, u8 defaultChoice); bool8 ScriptMenu_YesNo(u8 left, u8 top); diff --git a/src/scrcmd.c b/src/scrcmd.c index e11528ccca..bb253a20c5 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1382,6 +1382,7 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) u32 maxBeforeScroll = ScriptReadByte(ctx); bool32 shouldSort = ScriptReadByte(ctx); u32 initialSelected = VarGet(ScriptReadHalfword(ctx)); + u32 callbackSet = ScriptReadByte(ctx); u32 initialRow = 0; // Read vararg u32 argc = ScriptReadByte(ctx); @@ -1426,7 +1427,7 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) MultichoiceDynamic_DestroyStack(); } - if (ScriptMenu_MultichoiceDynamic(left, top, argc, items, ignoreBPress, maxBeforeScroll, initialRow)) + if (ScriptMenu_MultichoiceDynamic(left, top, argc, items, ignoreBPress, maxBeforeScroll, initialRow, callbackSet)) { ScriptContext_Stop(); return TRUE; @@ -1440,7 +1441,7 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) bool8 ScrCmd_dynmultipush(struct ScriptContext *ctx) { const u8 *name = (const u8*) ScriptReadWord(ctx); - u32 id = ScriptReadByte(ctx); + u32 id = VarGet(ScriptReadHalfword(ctx)); struct ListMenuItem item = {.name = name, .id = id}; MultichoiceDynamic_PushElement(item); } diff --git a/src/script_menu.c b/src/script_menu.c index e4d6162074..43c8d7a621 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -16,6 +16,7 @@ #include "list_menu.h" #include "malloc.h" #include "util.h" +#include "item_icon.h" #include "constants/field_specials.h" #include "constants/items.h" #include "constants/script_menu.h" @@ -23,8 +24,26 @@ #include "data/script_menu.h" +struct DynamicListMenuEventArgs +{ + struct ListMenuTemplate *list; + u16 selectedItem; + u8 windowId; +}; + +typedef void (*DynamicListCallback)(struct DynamicListMenuEventArgs *eventArgs); + +struct DynamicListMenuEventCollection +{ + DynamicListCallback OnInit; + DynamicListCallback OnSelectionChanged; + DynamicListCallback OnDestroy; +}; + static EWRAM_DATA u8 sProcessInputDelay = 0; +static EWRAM_DATA u8 sDynamicMenuEventId = 0; static EWRAM_DATA struct DynamicMultichoiceStack *sDynamicMultiChoiceStack = NULL; +static EWRAM_DATA u16 *sDynamicMenuEventScratchPad = NULL; static u8 sLilycoveSSTidalSelections[SSTIDAL_SELECTION_COUNT]; @@ -33,7 +52,7 @@ static void Task_HandleScrollingMultichoiceInput(u8 taskId); static void Task_HandleMultichoiceInput(u8 taskId); static void Task_HandleYesNoInput(u8 taskId); static void Task_HandleMultichoiceGridInput(u8 taskId); -static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u32 initialRow, u8 maxBeforeScroll); +static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u32 initialRow, u8 maxBeforeScroll, u32 callbackSet); static void DrawMultichoiceMenu(u8 left, u8 top, u8 multichoiceId, bool8 ignoreBPress, u8 cursorPos); static void InitMultichoiceCheckWrap(bool8 ignoreBPress, u8 count, u8 windowId, u8 multichoiceId); static void DrawLinkServicesMultichoiceMenu(u8 multichoiceId); @@ -42,6 +61,28 @@ static void CreateLilycoveSSTidalMultichoice(void); static bool8 IsPicboxClosed(void); static void CreateStartMenuForPokenavTutorial(void); static void InitMultichoiceNoWrap(bool8 ignoreBPress, u8 unusedCount, u8 windowId, u8 multichoiceId); +static void MultichoiceDynamicEventDebug_OnInit(struct DynamicListMenuEventArgs *eventArgs); +static void MultichoiceDynamicEventDebug_OnSelectionChanged(struct DynamicListMenuEventArgs *eventArgs); +static void MultichoiceDynamicEventDebug_OnDestroy(struct DynamicListMenuEventArgs *eventArgs); +static void MultichoiceDynamicEventShowItem_OnInit(struct DynamicListMenuEventArgs *eventArgs); +static void MultichoiceDynamicEventShowItem_OnSelectionChanged(struct DynamicListMenuEventArgs *eventArgs); +static void MultichoiceDynamicEventShowItem_OnDestroy(struct DynamicListMenuEventArgs *eventArgs); + +static const struct DynamicListMenuEventCollection sDynamicListMenuEventCollections[] = +{ + [DYN_MULTICHOICE_CB_DEBUG] = + { + .OnInit = MultichoiceDynamicEventDebug_OnInit, + .OnSelectionChanged = MultichoiceDynamicEventDebug_OnSelectionChanged, + .OnDestroy = MultichoiceDynamicEventDebug_OnDestroy + }, + [DYN_MULTICHOICE_CB_SHOW_ITEM] = + { + .OnInit = MultichoiceDynamicEventShowItem_OnInit, + .OnSelectionChanged = MultichoiceDynamicEventShowItem_OnSelectionChanged, + .OnDestroy = MultichoiceDynamicEventShowItem_OnDestroy + } +}; static const struct ListMenuTemplate sScriptableListMenuTemplate = { @@ -55,7 +96,7 @@ static const struct ListMenuTemplate sScriptableListMenuTemplate = .fontId = FONT_NORMAL, }; -bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll, u32 initialRow) +bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll, u32 initialRow, u32 callbackSet) { if (FuncIsActiveTask(Task_HandleMultichoiceInput) == TRUE) { @@ -65,7 +106,7 @@ bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuIte else { gSpecialVar_Result = 0xFF; - DrawMultichoiceMenuDynamic(left, top, argc, items, ignoreBPress, initialRow, maxBeforeScroll); + DrawMultichoiceMenuDynamic(left, top, argc, items, ignoreBPress, initialRow, maxBeforeScroll, callbackSet); return TRUE; } } @@ -98,6 +139,74 @@ bool8 ScriptMenu_MultichoiceWithDefault(u8 left, u8 top, u8 multichoiceId, bool8 } } +static void MultichoiceDynamicEventDebug_OnInit(struct DynamicListMenuEventArgs *eventArgs) +{ + DebugPrintf("OnInit: %d", eventArgs->windowId); +} + +static void MultichoiceDynamicEventDebug_OnSelectionChanged(struct DynamicListMenuEventArgs *eventArgs) +{ + DebugPrintf("OnSelectionChanged: %d", eventArgs->selectedItem); +} + +static void MultichoiceDynamicEventDebug_OnDestroy(struct DynamicListMenuEventArgs *eventArgs) +{ + DebugPrintf("OnDestroy: %d", eventArgs->windowId); +} + +#define sAuxWindowId sDynamicMenuEventScratchPad[0] +#define sItemSpriteId sDynamicMenuEventScratchPad[1] +#define TAG_CB_ITEM_ICON 3000 + +static void MultichoiceDynamicEventShowItem_OnInit(struct DynamicListMenuEventArgs *eventArgs) +{ + struct WindowTemplate *template = &gWindows[eventArgs->windowId].window; + u32 baseBlock = template->baseBlock + template->width * template->height; + struct WindowTemplate auxTemplate = CreateWindowTemplate(0, template->tilemapLeft + template->width + 2, template->tilemapTop, 4, 4, 15, baseBlock); + u32 auxWindowId = AddWindow(&auxTemplate); + SetStandardWindowBorderStyle(auxWindowId, FALSE); + FillWindowPixelBuffer(auxWindowId, 0x11); + CopyWindowToVram(auxWindowId, COPYWIN_FULL); + sAuxWindowId = auxWindowId; + sItemSpriteId = MAX_SPRITES; +} + +static void MultichoiceDynamicEventShowItem_OnSelectionChanged(struct DynamicListMenuEventArgs *eventArgs) +{ + struct WindowTemplate *template = &gWindows[eventArgs->windowId].window; + u32 x = template->tilemapLeft * 8 + template->width * 8 + 36; + u32 y = template->tilemapTop * 8 + 20; + + if (sItemSpriteId != MAX_SPRITES) + { + FreeSpriteTilesByTag(TAG_CB_ITEM_ICON); + FreeSpritePaletteByTag(TAG_CB_ITEM_ICON); + DestroySprite(&gSprites[sItemSpriteId]); + } + + sItemSpriteId = AddItemIconSprite(TAG_CB_ITEM_ICON, TAG_CB_ITEM_ICON, eventArgs->selectedItem); + gSprites[sItemSpriteId].oam.priority = 0; + gSprites[sItemSpriteId].x = x; + gSprites[sItemSpriteId].y = y; +} + +static void MultichoiceDynamicEventShowItem_OnDestroy(struct DynamicListMenuEventArgs *eventArgs) +{ + ClearStdWindowAndFrame(sAuxWindowId, TRUE); + RemoveWindow(sAuxWindowId); + + if (sItemSpriteId != MAX_SPRITES) + { + FreeSpriteTilesByTag(TAG_CB_ITEM_ICON); + FreeSpritePaletteByTag(TAG_CB_ITEM_ICON); + DestroySprite(&gSprites[sItemSpriteId]); + } +} + +#undef sAuxWindowId +#undef sItemSpriteId +#undef TAG_CB_ITEM_ICON + static void FreeListMenuItems(struct ListMenuItem *items, u32 count) { u32 i; @@ -226,10 +335,15 @@ static void MultichoiceDynamic_MoveCursor(s32 itemIndex, bool8 onInit, struct Li if (taskId != TASK_NONE) { ListMenuGetScrollAndRow(gTasks[taskId].data[0], &gScrollableMultichoice_ScrollOffset, NULL); + if (sDynamicMenuEventId != DYN_MULTICHOICE_CB_NONE && sDynamicListMenuEventCollections[sDynamicMenuEventId].OnSelectionChanged && !onInit) + { + struct DynamicListMenuEventArgs eventArgs = {.selectedItem = itemIndex, .windowId = list->template.windowId, .list = &list->template}; + sDynamicListMenuEventCollections[sDynamicMenuEventId].OnSelectionChanged(&eventArgs); + } } } -static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u32 initialRow, u8 maxBeforeScroll) +static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u32 initialRow, u8 maxBeforeScroll, u32 callbackSet) { u32 i; u8 windowId; @@ -250,6 +364,16 @@ static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenu SetStandardWindowBorderStyle(windowId, FALSE); CopyWindowToVram(windowId, COPYWIN_FULL); + // I don't like this being global either, but I could not come up with another solution that + // does not invade the whole ListMenu infrastructure. + sDynamicMenuEventId = callbackSet; + sDynamicMenuEventScratchPad = AllocZeroed(100 * sizeof(u16)); + if (sDynamicMenuEventId != DYN_MULTICHOICE_CB_NONE && sDynamicListMenuEventCollections[sDynamicMenuEventId].OnInit) + { + struct DynamicListMenuEventArgs eventArgs = {.selectedItem = initialRow, .windowId = windowId, .list = NULL}; + sDynamicListMenuEventCollections[sDynamicMenuEventId].OnInit(&eventArgs); + } + gMultiuseListMenuTemplate = sScriptableListMenuTemplate; gMultiuseListMenuTemplate.windowId = windowId; gMultiuseListMenuTemplate.items = items; @@ -266,6 +390,12 @@ static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenu StoreWordInTwoHalfwords(&gTasks[taskId].data[3], (u32) items); list = (void *) gTasks[gTasks[taskId].data[0]].data; ListMenuChangeSelectionFull(list, TRUE, FALSE, initialRow, TRUE); + + if (sDynamicMenuEventId != DYN_MULTICHOICE_CB_NONE && sDynamicListMenuEventCollections[sDynamicMenuEventId].OnSelectionChanged) + { + struct DynamicListMenuEventArgs eventArgs = {.selectedItem = items[initialRow].id, .windowId = windowId, .list = &gMultiuseListMenuTemplate}; + sDynamicListMenuEventCollections[sDynamicMenuEventId].OnSelectionChanged(&eventArgs); + } ListMenuGetScrollAndRow(gTasks[taskId].data[0], &gScrollableMultichoice_ScrollOffset, NULL); if (argc > maxBeforeScroll) { @@ -377,6 +507,15 @@ static void Task_HandleScrollingMultichoiceInput(u8 taskId) struct ListMenuItem *items; PlaySE(SE_SELECT); + + if (sDynamicMenuEventId != DYN_MULTICHOICE_CB_NONE && sDynamicListMenuEventCollections[sDynamicMenuEventId].OnDestroy) + { + struct DynamicListMenuEventArgs eventArgs = {.selectedItem = input, .windowId = gTasks[taskId].data[2], .list = NULL}; + sDynamicListMenuEventCollections[sDynamicMenuEventId].OnDestroy(&eventArgs); + } + + sDynamicMenuEventId = DYN_MULTICHOICE_CB_NONE; + if (gTasks[taskId].data[5] > gTasks[taskId].data[7]) { RemoveScrollIndicatorArrowPair(gTasks[taskId].data[6]); @@ -384,7 +523,7 @@ static void Task_HandleScrollingMultichoiceInput(u8 taskId) LoadWordFromTwoHalfwords(&gTasks[taskId].data[3], (u32* )(&items)); FreeListMenuItems(items, gTasks[taskId].data[5]); - + TRY_FREE_AND_SET_NULL(sDynamicMenuEventScratchPad); DestroyListMenuTask(gTasks[taskId].data[0], NULL, NULL); ClearStdWindowAndFrame(gTasks[taskId].data[2], TRUE); RemoveWindow(gTasks[taskId].data[2]);