From 569fa0a60a49fd7794af53c9a77a2fb1fb074200 Mon Sep 17 00:00:00 2001 From: sbird Date: Sun, 15 Jan 2023 13:41:10 +0100 Subject: [PATCH 1/9] [script-command] add dynmultichoice * supports variable length arguments * automatically scrolls * supports building list menus from a stack --- asm/macros/event.inc | 25 +++++ data/script_cmd_table.inc | 2 + include/field_specials.h | 1 + include/script.h | 1 + include/script_menu.h | 26 +++++ src/field_specials.c | 9 +- src/scrcmd.c | 87 ++++++++++++++ src/script.c | 9 ++ src/script_menu.c | 231 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 387 insertions(+), 4 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 6c0c3856b9..069477485a 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1729,6 +1729,31 @@ .2byte \quantity .endm + @ 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 argv:vararg + .byte 0xe3 + .byte \left + .byte \top + .byte \ignoreBPress + .byte \maxBeforeScroll + .byte (.Ldynmultichoice_\@_2 - .Ldynmultichoice_\@_1) / 4 +.Ldynmultichoice_\@_1: + .4byte \argv +.Ldynmultichoice_\@_2: + .endm + + .macro dynmultipush name:req, id:req + .byte 0xe4 + .4byte \name + .byte \id + .endm + + .macro dynmultistack left:req, top:req, ignoreBPress:req, maxBeforeScroll:req + dynmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, NULL + .endm + @ Supplementary diff --git a/data/script_cmd_table.inc b/data/script_cmd_table.inc index 51b7f966e4..537a5126c7 100644 --- a/data/script_cmd_table.inc +++ b/data/script_cmd_table.inc @@ -227,6 +227,8 @@ gScriptCmdTable:: .4byte ScrCmd_warpwhitefade @ 0xe0 .4byte ScrCmd_buffercontestname @ 0xe1 .4byte ScrCmd_bufferitemnameplural @ 0xe2 + .4byte ScrCmd_dynmultichoice @ 0xe3 + .4byte ScrCmd_dynmultipush @ 0xe4 gScriptCmdTableEnd:: .4byte ScrCmd_nop diff --git a/include/field_specials.h b/include/field_specials.h index faf71e9c08..9b28b1d26b 100644 --- a/include/field_specials.h +++ b/include/field_specials.h @@ -3,6 +3,7 @@ extern bool8 gBikeCyclingChallenge; extern u8 gBikeCollisions; +extern u16 gScrollableMultichoice_ScrollOffset; u8 GetLeadMonIndex(void); u8 IsDestinationBoxFull(void); diff --git a/include/script.h b/include/script.h index 7c180e961b..4dc30ca74c 100644 --- a/include/script.h +++ b/include/script.h @@ -31,6 +31,7 @@ void ScriptCall(struct ScriptContext *ctx, const u8 *ptr); void ScriptReturn(struct ScriptContext *ctx); u16 ScriptReadHalfword(struct ScriptContext *ctx); u32 ScriptReadWord(struct ScriptContext *ctx); +u32 ScriptPeekWord(struct ScriptContext *ctx); void LockPlayerFieldControls(void); void UnlockPlayerFieldControls(void); bool8 ArePlayerFieldControlsLocked(void); diff --git a/include/script_menu.h b/include/script_menu.h index 36b66bf987..e4e4836097 100644 --- a/include/script_menu.h +++ b/include/script_menu.h @@ -1,8 +1,34 @@ #ifndef GUARD_SCRIPT_MENU_H #define GUARD_SCRIPT_MENU_H +#include "list_menu.h" + +// The default size the stack for dynamic multichoice is initialized to +// If you try to push an element when the stack is full, it will be reallocated +// With increasing capacity of MULTICHOICE_DYNAMIC_STACK_INC + +#define MULTICHOICE_DYNAMIC_STACK_SIZE 5 +#define MULTICHOICE_DYNAMIC_STACK_INC 5 + extern const u8 *const gStdStrings[]; +struct DynamicMultichoiceStack +{ + s32 top; + u32 capacity; + struct ListMenuItem *elements; +}; + +void MultichoiceDynamic_InitStack(u32 capacity); +void MultichoiceDynamic_ReallocStack(u32 newCapacity); +bool32 MultichoiceDynamic_StackFull(void); +bool32 MultichoiceDynamic_StackEmpty(void); +u32 MultichoiceDynamic_StackSize(void); +void MultichoiceDynamic_PushElement(struct ListMenuItem item); +struct ListMenuItem *MultichoiceDynamic_PopElement(void); +struct ListMenuItem *MultichoiceDynamic_PeekElement(void); +void MultichoiceDynamic_DestroyStack(void); +bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll); 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/field_specials.c b/src/field_specials.c index 5c7d079163..2f09c0f0f2 100644 --- a/src/field_specials.c +++ b/src/field_specials.c @@ -75,7 +75,7 @@ static EWRAM_DATA u8 sTutorMoveAndElevatorWindowId = 0; static EWRAM_DATA u16 sLilycoveDeptStore_NeverRead = 0; static EWRAM_DATA u16 sLilycoveDeptStore_DefaultFloorChoice = 0; static EWRAM_DATA struct ListMenuItem *sScrollableMultichoice_ListMenuItem = NULL; -static EWRAM_DATA u16 sScrollableMultichoice_ScrollOffset = 0; + static EWRAM_DATA u16 sFrontierExchangeCorner_NeverRead = 0; static EWRAM_DATA u8 sScrollableMultichoice_ItemSpriteId = 0; static EWRAM_DATA u8 sBattlePointsWindowId = 0; @@ -84,6 +84,7 @@ static EWRAM_DATA u8 sPCBoxToSendMon = 0; static EWRAM_DATA u32 sBattleTowerMultiBattleTypeFlags = 0; struct ListMenuTemplate gScrollableMultichoice_ListMenuTemplate; +EWRAM_DATA u16 gScrollableMultichoice_ScrollOffset = 0; void TryLoseFansFromPlayTime(void); void SetPlayerGotFirstFans(void); @@ -2477,7 +2478,7 @@ static void Task_ShowScrollableMultichoice(u8 taskId) struct Task *task = &gTasks[taskId]; LockPlayerFieldControls(); - sScrollableMultichoice_ScrollOffset = 0; + gScrollableMultichoice_ScrollOffset = 0; sScrollableMultichoice_ItemSpriteId = MAX_SPRITES; FillFrontierExchangeCornerWindowAndItemIcon(task->tScrollMultiId, 0); ShowBattleFrontierTutorWindow(task->tScrollMultiId, 0); @@ -2551,7 +2552,7 @@ static void ScrollableMultichoice_MoveCursor(s32 itemIndex, bool8 onInit, struct u16 selection; struct Task *task = &gTasks[taskId]; ListMenuGetScrollAndRow(task->tListTaskId, &selection, NULL); - sScrollableMultichoice_ScrollOffset = selection; + gScrollableMultichoice_ScrollOffset = selection; ListMenuGetCurrentItemArrayId(task->tListTaskId, &selection); HideFrontierExchangeCornerItemIcon(task->tScrollMultiId, sFrontierExchangeCorner_NeverRead); FillFrontierExchangeCornerWindowAndItemIcon(task->tScrollMultiId, selection); @@ -2672,7 +2673,7 @@ static void ScrollableMultichoice_UpdateScrollArrows(u8 taskId) template.secondY = task->tHeight * 8 + 10; template.fullyUpThreshold = 0; template.fullyDownThreshold = task->data[1] - task->tMaxItemsOnScreen; - task->tScrollArrowId = AddScrollIndicatorArrowPair(&template, &sScrollableMultichoice_ScrollOffset); + task->tScrollArrowId = AddScrollIndicatorArrowPair(&template, &gScrollableMultichoice_ScrollOffset); } } diff --git a/src/scrcmd.c b/src/scrcmd.c index 01e1a8f09a..177c27277b 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -48,6 +48,8 @@ #include "trainer_see.h" #include "tv.h" #include "window.h" +#include "list_menu.h" +#include "malloc.h" #include "constants/event_objects.h" typedef u16 (*SpecialFunc)(void); @@ -68,6 +70,7 @@ extern const u8 *gStdScripts[]; extern const u8 *gStdScripts_End[]; static void CloseBrailleWindow(void); +static void DynamicMultichoiceSortList(struct ListMenuItem *items, u32 count); // This is defined in here so the optimizer can't see its value when compiling // script.c. @@ -1350,6 +1353,90 @@ bool8 ScrCmd_yesnobox(struct ScriptContext *ctx) } } +static void DynamicMultichoiceSortList(struct ListMenuItem *items, u32 count) +{ + u32 i,j; + struct ListMenuItem tmp; + for (i = 0; i < count - 1; ++i) + { + for (j = 0; j < count - i - 1; ++j) + { + if (items[j].id > items[j+1].id) + { + tmp = items[j]; + items[j] = items[j+1]; + items[j+1] = tmp; + } + } + } +} + +#define DYN_MULTICHOICE_DEFAULT_MAX_BEFORE_SCROLL 6 + +bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) +{ + u32 i; + u8 left = ScriptReadByte(ctx); + u8 top = ScriptReadByte(ctx); + bool8 ignoreBPress = ScriptReadByte(ctx); + u8 maxBeforeScroll = ScriptReadByte(ctx); + // Read vararg + u8 argc = ScriptReadByte(ctx); + struct ListMenuItem *items; + + if (argc == 0) + return; + + if (maxBeforeScroll == 0xFF) + maxBeforeScroll = DYN_MULTICHOICE_DEFAULT_MAX_BEFORE_SCROLL; + + if ((const u8*) ScriptPeekWord(ctx) != NULL) + { + items = AllocZeroed(sizeof(struct ListMenuItem) * argc); + for (i = 0; i < argc; ++i) + { + u8 *nameBuffer = Alloc(100); + const u8 *arg = (const u8 *) ScriptReadWord(ctx); + StringExpandPlaceholders(nameBuffer, arg); + items[i].name = nameBuffer; + items[i].id = i; + } + } + else + { + argc = MultichoiceDynamic_StackSize(); + items = AllocZeroed(sizeof(struct ListMenuItem) * argc); + for (i = 0; i < argc; ++i) + { + u8 *nameBuffer = Alloc(100); + struct ListMenuItem *currentItem = MultichoiceDynamic_PopElement(); + StringExpandPlaceholders(nameBuffer, currentItem->name); + items[i].name = nameBuffer; + items[i].id = currentItem->id; + } + DynamicMultichoiceSortList(items, argc); + MultichoiceDynamic_DestroyStack(); + } + + if (ScriptMenu_MultichoiceDynamic(left, top, argc, items, ignoreBPress, maxBeforeScroll)) + { + ScriptContext_Stop(); + return TRUE; + } + else + { + return FALSE; + } +} + +bool8 ScrCmd_dynmultipush(struct ScriptContext *ctx) +{ + const u8 *name = (const u8*) ScriptReadWord(ctx); + u32 id = ScriptReadByte(ctx); + struct ListMenuItem item = {.name = name, .id = id}; + MultichoiceDynamic_PushElement(item); +} + bool8 ScrCmd_multichoice(struct ScriptContext *ctx) { u8 left = ScriptReadByte(ctx); diff --git a/src/script.c b/src/script.c index c252c95f04..b14d33d4b3 100644 --- a/src/script.c +++ b/src/script.c @@ -179,6 +179,15 @@ u32 ScriptReadWord(struct ScriptContext *ctx) return (((((value3 << 8) + value2) << 8) + value1) << 8) + value0; } +u32 ScriptPeekWord(struct ScriptContext *ctx) +{ + u32 value0 = *(ctx->scriptPtr); + u32 value1 = *(ctx->scriptPtr + 1); + u32 value2 = *(ctx->scriptPtr + 2); + u32 value3 = *(ctx->scriptPtr + 3); + return (((((value3 << 8) + value2) << 8) + value1) << 8) + value0; +} + void LockPlayerFieldControls(void) { sLockFieldControls = TRUE; diff --git a/src/script_menu.c b/src/script_menu.c index 6633332f3f..ce0b378193 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -13,6 +13,9 @@ #include "strings.h" #include "task.h" #include "text.h" +#include "list_menu.h" +#include "malloc.h" +#include "util.h" #include "constants/field_specials.h" #include "constants/items.h" #include "constants/script_menu.h" @@ -21,12 +24,16 @@ #include "data/script_menu.h" static EWRAM_DATA u8 sProcessInputDelay = 0; +static EWRAM_DATA struct DynamicMultichoiceStack *sDynamicMultiChoiceStack = NULL; static u8 sLilycoveSSTidalSelections[SSTIDAL_SELECTION_COUNT]; +static void FreeListMenuItems(struct ListMenuItem *items, u32 count); +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, u8 cursorPos, u8 maxBeforeScroll); 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); @@ -36,6 +43,33 @@ static bool8 IsPicboxClosed(void); static void CreateStartMenuForPokenavTutorial(void); static void InitMultichoiceNoWrap(bool8 ignoreBPress, u8 unusedCount, u8 windowId, u8 multichoiceId); +static const struct ListMenuTemplate sScriptableListMenuTemplate = +{ + .item_X = 8, + .upText_Y = 1, + .cursorPal = 2, + .fillValue = 1, + .cursorShadowPal = 3, + .lettersSpacing = 1, + .scrollMultiple = LIST_NO_MULTIPLE_SCROLL, + .fontId = FONT_NORMAL, +}; + +bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll) +{ + if (FuncIsActiveTask(Task_HandleMultichoiceInput) == TRUE) + { + FreeListMenuItems(items, argc); + return FALSE; + } + else + { + gSpecialVar_Result = 0xFF; + DrawMultichoiceMenuDynamic(left, top, argc, items, ignoreBPress, 0, maxBeforeScroll); + return TRUE; + } +} + bool8 ScriptMenu_Multichoice(u8 left, u8 top, u8 multichoiceId, bool8 ignoreBPress) { if (FuncIsActiveTask(Task_HandleMultichoiceInput) == TRUE) @@ -64,6 +98,17 @@ bool8 ScriptMenu_MultichoiceWithDefault(u8 left, u8 top, u8 multichoiceId, bool8 } } +static void FreeListMenuItems(struct ListMenuItem *items, u32 count) +{ + u32 i; + for (i = 0; i < count; ++i) + { + // All items were dynamically allocated, so items[i].name is not actually constant. + Free((void *)items[i].name); + } + Free(items); +} + // Unused static u16 GetLengthWithExpandedPlayerName(const u8 *str) { @@ -90,6 +135,148 @@ static u16 GetLengthWithExpandedPlayerName(const u8 *str) return length; } +void MultichoiceDynamic_InitStack(u32 capacity) +{ + AGB_ASSERT(sDynamicMultiChoiceStack == NULL); + sDynamicMultiChoiceStack = AllocZeroed(sizeof(*sDynamicMultiChoiceStack)); + AGB_ASSERT(sDynamicMultiChoiceStack != NULL); + sDynamicMultiChoiceStack->capacity = capacity; + sDynamicMultiChoiceStack->top = -1; + sDynamicMultiChoiceStack->elements = AllocZeroed(capacity * sizeof(struct ListMenuItem)); +} + +void MultichoiceDynamic_ReallocStack(u32 newCapacity) +{ + struct ListMenuItem *newElements; + AGB_ASSERT(sDynamicMultiChoiceStack != NULL); + AGB_ASSERT(sDynamicMultiChoiceStack->capacity < newCapacity); + newElements = AllocZeroed(newCapacity * sizeof(struct ListMenuItem)); + AGB_ASSERT(newElements != NULL); + memcpy(newElements, sDynamicMultiChoiceStack->elements, sDynamicMultiChoiceStack->capacity * sizeof(struct ListMenuItem)); + Free(sDynamicMultiChoiceStack->elements); + sDynamicMultiChoiceStack->elements = newElements; + sDynamicMultiChoiceStack->capacity = newCapacity; +} + +bool32 MultichoiceDynamic_StackFull(void) +{ + AGB_ASSERT(sDynamicMultiChoiceStack != NULL); + return sDynamicMultiChoiceStack->top == sDynamicMultiChoiceStack->capacity - 1; +} + +bool32 MultichoiceDynamic_StackEmpty(void) +{ + AGB_ASSERT(sDynamicMultiChoiceStack != NULL); + return sDynamicMultiChoiceStack->top == -1; +} + +u32 MultichoiceDynamic_StackSize(void) +{ + AGB_ASSERT(sDynamicMultiChoiceStack != NULL); + return sDynamicMultiChoiceStack->top + 1; +} + +void MultichoiceDynamic_PushElement(struct ListMenuItem item) +{ + if (sDynamicMultiChoiceStack == NULL) + MultichoiceDynamic_InitStack(MULTICHOICE_DYNAMIC_STACK_SIZE); + if (MultichoiceDynamic_StackFull()) + MultichoiceDynamic_ReallocStack(sDynamicMultiChoiceStack->capacity + MULTICHOICE_DYNAMIC_STACK_INC); + sDynamicMultiChoiceStack->elements[++sDynamicMultiChoiceStack->top] = item; +} + +struct ListMenuItem *MultichoiceDynamic_PopElement(void) +{ + if (sDynamicMultiChoiceStack == NULL) + return NULL; + if (MultichoiceDynamic_StackEmpty()) + return NULL; + return &sDynamicMultiChoiceStack->elements[sDynamicMultiChoiceStack->top--]; +} + +struct ListMenuItem *MultichoiceDynamic_PeekElement(void) +{ + if (sDynamicMultiChoiceStack == NULL) + return NULL; + if (MultichoiceDynamic_StackEmpty()) + return NULL; + return &sDynamicMultiChoiceStack->elements[sDynamicMultiChoiceStack->top]; +} + +void MultichoiceDynamic_DestroyStack(void) +{ + TRY_FREE_AND_SET_NULL(sDynamicMultiChoiceStack->elements); + TRY_FREE_AND_SET_NULL(sDynamicMultiChoiceStack); +} + +static void MultichoiceDynamic_MoveCursor(s32 itemIndex, bool8 onInit, struct ListMenu *list) +{ + u8 taskId; + PlaySE(SE_SELECT); + taskId = FindTaskIdByFunc(Task_HandleScrollingMultichoiceInput); + if (taskId != TASK_NONE) + { + u16 scrollOffset; + ListMenuGetScrollAndRow(gTasks[taskId].data[0], &scrollOffset, NULL); + gScrollableMultichoice_ScrollOffset = scrollOffset; + } +} + +static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 cursorPos, u8 maxBeforeScroll) +{ + u32 i; + u8 windowId; + s32 width = 0; + u8 newWidth; + u8 taskId; + u32 windowHeight; + + for (i = 0; i < argc; ++i) + { + width = DisplayTextAndGetWidth(items[i].name, width); + } + windowHeight = (argc < maxBeforeScroll) ? argc * 2 : maxBeforeScroll * 2; + newWidth = ConvertPixelWidthToTileWidth(width); + left = ScriptMenu_AdjustLeftCoordFromWidth(left, newWidth); + windowId = CreateWindowFromRect(left, top, newWidth, windowHeight); + SetStandardWindowBorderStyle(windowId, FALSE); + CopyWindowToVram(windowId, COPYWIN_FULL); + + gMultiuseListMenuTemplate = sScriptableListMenuTemplate; + gMultiuseListMenuTemplate.windowId = windowId; + gMultiuseListMenuTemplate.items = items; + gMultiuseListMenuTemplate.totalItems = argc; + gMultiuseListMenuTemplate.maxShowed = maxBeforeScroll; + gMultiuseListMenuTemplate.moveCursorFunc = MultichoiceDynamic_MoveCursor; + + taskId = CreateTask(Task_HandleScrollingMultichoiceInput, 80); + gTasks[taskId].data[0] = ListMenuInit(&gMultiuseListMenuTemplate, 0, 0); + gTasks[taskId].data[1] = ignoreBPress; + gTasks[taskId].data[2] = windowId; + gTasks[taskId].data[5] = argc; + gTasks[taskId].data[7] = maxBeforeScroll; + StoreWordInTwoHalfwords(&gTasks[taskId].data[3], (u32) items); + + if (argc > maxBeforeScroll) + { + // Create Scrolling Arrows + struct ScrollArrowsTemplate template; + template.firstX = (newWidth / 2) * 8 + 12 + (left) * 8; + template.firstY = top * 8 + 5; + template.secondX = template.firstX; + template.secondY = windowHeight * 8 + 12; + template.fullyUpThreshold = 0; + template.fullyDownThreshold = argc - maxBeforeScroll; + template.firstArrowType = SCROLL_ARROW_UP; + template.secondArrowType = SCROLL_ARROW_DOWN; + template.tileTag = 2000; + template.palTag = 100, + template.palNum = 0; + + gTasks[taskId].data[6] = AddScrollIndicatorArrowPair(&template, &gScrollableMultichoice_ScrollOffset); + } +} + static void DrawMultichoiceMenu(u8 left, u8 top, u8 multichoiceId, bool8 ignoreBPress, u8 cursorPos) { int i; @@ -152,6 +339,50 @@ static void InitMultichoiceCheckWrap(bool8 ignoreBPress, u8 count, u8 windowId, DrawLinkServicesMultichoiceMenu(multichoiceId); } +static void Task_HandleScrollingMultichoiceInput(u8 taskId) +{ + bool32 done = FALSE; + s32 input = ListMenu_ProcessInput(gTasks[taskId].data[0]); + + switch (input) + { + case LIST_HEADER: + case LIST_NOTHING_CHOSEN: + break; + case LIST_CANCEL: + if (gTasks[taskId].data[1]) + { + gSpecialVar_Result = 0x7F; + done = TRUE; + } + break; + default: + gSpecialVar_Result = input; + done = TRUE; + break; + } + + if (done) + { + struct ListMenuItem *items; + + PlaySE(SE_SELECT); + if (gTasks[taskId].data[5] > gTasks[taskId].data[7]) + { + RemoveScrollIndicatorArrowPair(gTasks[taskId].data[6]); + } + + LoadWordFromTwoHalfwords(&gTasks[taskId].data[3], (u32* )(&items)); + FreeListMenuItems(items, gTasks[taskId].data[5]); + + DestroyListMenuTask(gTasks[taskId].data[0], NULL, NULL); + ClearStdWindowAndFrame(gTasks[taskId].data[2], TRUE); + RemoveWindow(gTasks[taskId].data[2]); + ScriptContext_Enable(); + DestroyTask(taskId); + } +} + static void Task_HandleMultichoiceInput(u8 taskId) { s8 selection; From 276ce62d95789b21fe8d2d5f078c104af4c2e8b0 Mon Sep 17 00:00:00 2001 From: sbird Date: Mon, 16 Jan 2023 22:40:36 +0100 Subject: [PATCH 2/9] [script-command, dynmultichoice] add shouldSort, initialSelected arguments. read pushed arguments front to back --- asm/macros/event.inc | 18 ++++++++++++------ include/list_menu.h | 2 ++ include/script_menu.h | 3 ++- src/list_menu.c | 16 +++++++++++----- src/scrcmd.c | 24 ++++++++++++++++-------- src/script_menu.c | 26 ++++++++++++++++++-------- 6 files changed, 61 insertions(+), 28 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index 069477485a..4e9ab1855b 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1729,29 +1729,35 @@ .2byte \quantity .endm - @ 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 argv:vararg + .macro _dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req argv:vararg .byte 0xe3 .byte \left .byte \top .byte \ignoreBPress .byte \maxBeforeScroll + .byte \shouldSort + .2byte \initialSelected .byte (.Ldynmultichoice_\@_2 - .Ldynmultichoice_\@_1) / 4 .Ldynmultichoice_\@_1: .4byte \argv .Ldynmultichoice_\@_2: .endm + @ 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 + .endm + .macro dynmultipush name:req, id:req .byte 0xe4 .4byte \name .byte \id .endm - .macro dynmultistack left:req, top:req, ignoreBPress:req, maxBeforeScroll:req - dynmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, NULL + .macro dynmultistack left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req + _dynmultichoice \left, \top, \ignoreBPress, \maxBeforeScroll, \shouldSort, \initialSelected, NULL .endm diff --git a/include/list_menu.h b/include/list_menu.h index 9299ede6c8..23caaf4b4c 100644 --- a/include/list_menu.h +++ b/include/list_menu.h @@ -126,5 +126,7 @@ u8 AddScrollIndicatorArrowPair(const struct ScrollArrowsTemplate *arrowInfo, u16 u8 AddScrollIndicatorArrowPairParameterized(u32 arrowType, s32 commonPos, s32 firstPos, s32 secondPos, s32 fullyDownThreshold, s32 tileTag, s32 palTag, u16 *currItemPtr); void RemoveScrollIndicatorArrowPair(u8 taskId); void Task_ScrollIndicatorArrowPairOnMainMenu(u8 taskId); +bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAndCallCallback, u8 count, bool8 movingDown); +bool8 ListMenuChangeSelectionFull(struct ListMenu *list, bool32 updateCursor, bool32 callCallback, u8 count, bool8 movingDown); #endif //GUARD_LIST_MENU_H diff --git a/include/script_menu.h b/include/script_menu.h index e4e4836097..c37b3603dd 100644 --- a/include/script_menu.h +++ b/include/script_menu.h @@ -27,8 +27,9 @@ u32 MultichoiceDynamic_StackSize(void); void MultichoiceDynamic_PushElement(struct ListMenuItem item); 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); +bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll, u32 initialRow); 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/list_menu.c b/src/list_menu.c index c240564c65..4f631188bb 100644 --- a/src/list_menu.c +++ b/src/list_menu.c @@ -70,7 +70,6 @@ struct RedArrowCursor // this file's functions static u8 ListMenuInitInternal(struct ListMenuTemplate *listMenuTemplate, u16 scrollOffset, u16 selectedRow); -static bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAndCallCallback, u8 count, bool8 movingDown); static void ListMenuPrintEntries(struct ListMenu *list, u16 startIndex, u16 yOffset, u16 count); static void ListMenuDrawCursor(struct ListMenu *list); static void ListMenuCallSelectionChangedCallback(struct ListMenu *list, u8 onInit); @@ -837,7 +836,7 @@ static void ListMenuScroll(struct ListMenu *list, u8 count, bool8 movingDown) } } -static bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAndCallCallback, u8 count, bool8 movingDown) +bool8 ListMenuChangeSelectionFull(struct ListMenu *list, bool32 updateCursor, bool32 callCallback, u8 count, bool8 movingDown) { u16 oldSelectedRow; u8 selectionChange, i, cursorCount; @@ -857,7 +856,7 @@ static bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAn } while (list->template.items[list->scrollOffset + list->selectedRow].id == LIST_HEADER); } - if (updateCursorAndCallCallback) + if (updateCursor) { switch (selectionChange) { @@ -867,7 +866,8 @@ static bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAn case 1: ListMenuErasePrintedCursor(list, oldSelectedRow); ListMenuDrawCursor(list); - ListMenuCallSelectionChangedCallback(list, FALSE); + if (callCallback) + ListMenuCallSelectionChangedCallback(list, FALSE); CopyWindowToVram(list->template.windowId, COPYWIN_GFX); break; case 2: @@ -875,7 +875,8 @@ static bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAn ListMenuErasePrintedCursor(list, oldSelectedRow); ListMenuScroll(list, cursorCount, movingDown); ListMenuDrawCursor(list); - ListMenuCallSelectionChangedCallback(list, FALSE); + if (callCallback) + ListMenuCallSelectionChangedCallback(list, FALSE); CopyWindowToVram(list->template.windowId, COPYWIN_GFX); break; } @@ -884,6 +885,11 @@ static bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAn return FALSE; } +bool8 ListMenuChangeSelection(struct ListMenu *list, bool8 updateCursorAndCallCallback, u8 count, bool8 movingDown) +{ + ListMenuChangeSelectionFull(list, updateCursorAndCallCallback, updateCursorAndCallCallback, count, movingDown); +} + static void ListMenuCallSelectionChangedCallback(struct ListMenu *list, u8 onInit) { if (list->template.moveCursorFunc != NULL) diff --git a/src/scrcmd.c b/src/scrcmd.c index 177c27277b..e11528ccca 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1376,12 +1376,15 @@ static void DynamicMultichoiceSortList(struct ListMenuItem *items, u32 count) bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) { u32 i; - u8 left = ScriptReadByte(ctx); - u8 top = ScriptReadByte(ctx); - bool8 ignoreBPress = ScriptReadByte(ctx); - u8 maxBeforeScroll = ScriptReadByte(ctx); + u32 left = ScriptReadByte(ctx); + u32 top = ScriptReadByte(ctx); + bool32 ignoreBPress = ScriptReadByte(ctx); + u32 maxBeforeScroll = ScriptReadByte(ctx); + bool32 shouldSort = ScriptReadByte(ctx); + u32 initialSelected = VarGet(ScriptReadHalfword(ctx)); + u32 initialRow = 0; // Read vararg - u8 argc = ScriptReadByte(ctx); + u32 argc = ScriptReadByte(ctx); struct ListMenuItem *items; if (argc == 0) @@ -1400,6 +1403,8 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) StringExpandPlaceholders(nameBuffer, arg); items[i].name = nameBuffer; items[i].id = i; + if (i == initialSelected) + initialRow = i; } } else @@ -1409,16 +1414,19 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) for (i = 0; i < argc; ++i) { u8 *nameBuffer = Alloc(100); - struct ListMenuItem *currentItem = MultichoiceDynamic_PopElement(); + struct ListMenuItem *currentItem = MultichoiceDynamic_PeekElementAt(i); StringExpandPlaceholders(nameBuffer, currentItem->name); items[i].name = nameBuffer; items[i].id = currentItem->id; + if (currentItem->id == initialSelected) + initialRow = i; } - DynamicMultichoiceSortList(items, argc); + if (shouldSort) + DynamicMultichoiceSortList(items, argc); MultichoiceDynamic_DestroyStack(); } - if (ScriptMenu_MultichoiceDynamic(left, top, argc, items, ignoreBPress, maxBeforeScroll)) + if (ScriptMenu_MultichoiceDynamic(left, top, argc, items, ignoreBPress, maxBeforeScroll, initialRow)) { ScriptContext_Stop(); return TRUE; diff --git a/src/script_menu.c b/src/script_menu.c index ce0b378193..e4d6162074 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -33,7 +33,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, u8 cursorPos, u8 maxBeforeScroll); +static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u32 initialRow, u8 maxBeforeScroll); 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); @@ -55,7 +55,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) +bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 maxBeforeScroll, u32 initialRow) { if (FuncIsActiveTask(Task_HandleMultichoiceInput) == TRUE) { @@ -65,7 +65,7 @@ bool8 ScriptMenu_MultichoiceDynamic(u8 left, u8 top, u8 argc, struct ListMenuIte else { gSpecialVar_Result = 0xFF; - DrawMultichoiceMenuDynamic(left, top, argc, items, ignoreBPress, 0, maxBeforeScroll); + DrawMultichoiceMenuDynamic(left, top, argc, items, ignoreBPress, initialRow, maxBeforeScroll); return TRUE; } } @@ -203,6 +203,15 @@ struct ListMenuItem *MultichoiceDynamic_PeekElement(void) return &sDynamicMultiChoiceStack->elements[sDynamicMultiChoiceStack->top]; } +struct ListMenuItem *MultichoiceDynamic_PeekElementAt(u32 index) +{ + if (sDynamicMultiChoiceStack == NULL) + return NULL; + if (sDynamicMultiChoiceStack->top < index) + return NULL; + return &sDynamicMultiChoiceStack->elements[index]; +} + void MultichoiceDynamic_DestroyStack(void) { TRY_FREE_AND_SET_NULL(sDynamicMultiChoiceStack->elements); @@ -216,13 +225,11 @@ static void MultichoiceDynamic_MoveCursor(s32 itemIndex, bool8 onInit, struct Li taskId = FindTaskIdByFunc(Task_HandleScrollingMultichoiceInput); if (taskId != TASK_NONE) { - u16 scrollOffset; - ListMenuGetScrollAndRow(gTasks[taskId].data[0], &scrollOffset, NULL); - gScrollableMultichoice_ScrollOffset = scrollOffset; + ListMenuGetScrollAndRow(gTasks[taskId].data[0], &gScrollableMultichoice_ScrollOffset, NULL); } } -static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u8 cursorPos, u8 maxBeforeScroll) +static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenuItem *items, bool8 ignoreBPress, u32 initialRow, u8 maxBeforeScroll) { u32 i; u8 windowId; @@ -230,6 +237,7 @@ static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenu u8 newWidth; u8 taskId; u32 windowHeight; + struct ListMenu *list; for (i = 0; i < argc; ++i) { @@ -256,7 +264,9 @@ static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenu gTasks[taskId].data[5] = argc; gTasks[taskId].data[7] = maxBeforeScroll; StoreWordInTwoHalfwords(&gTasks[taskId].data[3], (u32) items); - + list = (void *) gTasks[gTasks[taskId].data[0]].data; + ListMenuChangeSelectionFull(list, TRUE, FALSE, initialRow, TRUE); + ListMenuGetScrollAndRow(gTasks[taskId].data[0], &gScrollableMultichoice_ScrollOffset, NULL); if (argc > maxBeforeScroll) { // Create Scrolling Arrows From a7cd4ca592ff6f85186174002fca6b11e4c61eb1 Mon Sep 17 00:00:00 2001 From: sbird Date: Tue, 17 Jan 2023 21:21:07 +0100 Subject: [PATCH 3/9] [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]); From 4fad6b3e125a184d421d1a005380956974cc14fd Mon Sep 17 00:00:00 2001 From: sbird Date: Wed, 18 Jan 2023 23:25:19 +0100 Subject: [PATCH 4/9] [script-command, dynmultichoice] load message box and border gfx --- src/script_menu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script_menu.c b/src/script_menu.c index 43c8d7a621..8e9cd5296c 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -357,6 +357,7 @@ static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenu { width = DisplayTextAndGetWidth(items[i].name, width); } + LoadMessageBoxAndBorderGfx(); windowHeight = (argc < maxBeforeScroll) ? argc * 2 : maxBeforeScroll * 2; newWidth = ConvertPixelWidthToTileWidth(width); left = ScriptMenu_AdjustLeftCoordFromWidth(left, newWidth); From bfc6619c03b460f7c2d5d96f39d17c2a4428f360 Mon Sep 17 00:00:00 2001 From: sbird Date: Wed, 18 Jan 2023 23:34:27 +0100 Subject: [PATCH 5/9] [script-command, dynmultichoice] early expand placeholders --- src/scrcmd.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/scrcmd.c b/src/scrcmd.c index bb253a20c5..4599bc8bb4 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1414,11 +1414,8 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) items = AllocZeroed(sizeof(struct ListMenuItem) * argc); for (i = 0; i < argc; ++i) { - u8 *nameBuffer = Alloc(100); struct ListMenuItem *currentItem = MultichoiceDynamic_PeekElementAt(i); - StringExpandPlaceholders(nameBuffer, currentItem->name); - items[i].name = nameBuffer; - items[i].id = currentItem->id; + items[i] = *currentItem; if (currentItem->id == initialSelected) initialRow = i; } @@ -1440,9 +1437,13 @@ bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) bool8 ScrCmd_dynmultipush(struct ScriptContext *ctx) { + u8 *nameBuffer = Alloc(100); const u8 *name = (const u8*) ScriptReadWord(ctx); u32 id = VarGet(ScriptReadHalfword(ctx)); - struct ListMenuItem item = {.name = name, .id = id}; + struct ListMenuItem item; + StringExpandPlaceholders(nameBuffer, name); + item.name = nameBuffer; + item.id = id; MultichoiceDynamic_PushElement(item); } From a2dfc7c88700c37ccca188bd12fade9646e4975d Mon Sep 17 00:00:00 2001 From: sbird Date: Mon, 24 Apr 2023 19:13:39 +0200 Subject: [PATCH 6/9] [dynmulti] fix second scroll arrow y offset --- src/script_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script_menu.c b/src/script_menu.c index 8e9cd5296c..e639c68378 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -405,7 +405,7 @@ static void DrawMultichoiceMenuDynamic(u8 left, u8 top, u8 argc, struct ListMenu template.firstX = (newWidth / 2) * 8 + 12 + (left) * 8; template.firstY = top * 8 + 5; template.secondX = template.firstX; - template.secondY = windowHeight * 8 + 12; + template.secondY = top * 8 + windowHeight * 8 + 12; template.fullyUpThreshold = 0; template.fullyDownThreshold = argc - maxBeforeScroll; template.firstArrowType = SCROLL_ARROW_UP; From 47f71a1b62dd054b1318f9f8d1fc8da4fc9d6aa7 Mon Sep 17 00:00:00 2001 From: sbird Date: Wed, 26 Apr 2023 03:53:52 +0200 Subject: [PATCH 7/9] [dynmulti] fix ignoreBPress semantics --- src/script_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script_menu.c b/src/script_menu.c index e639c68378..01c7a4aaf9 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -491,7 +491,7 @@ static void Task_HandleScrollingMultichoiceInput(u8 taskId) case LIST_NOTHING_CHOSEN: break; case LIST_CANCEL: - if (gTasks[taskId].data[1]) + if (!gTasks[taskId].data[1]) { gSpecialVar_Result = 0x7F; done = TRUE; From 693f7509bb29427f68bfbe46cce0b154fc0cd056 Mon Sep 17 00:00:00 2001 From: Jaizu Date: Wed, 16 Aug 2023 14:49:44 +0200 Subject: [PATCH 8/9] Make use of MULTI_B_PRESSED constant. (#4) --- src/script_menu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script_menu.c b/src/script_menu.c index 01c7a4aaf9..53e31be9a0 100644 --- a/src/script_menu.c +++ b/src/script_menu.c @@ -493,7 +493,7 @@ static void Task_HandleScrollingMultichoiceInput(u8 taskId) case LIST_CANCEL: if (!gTasks[taskId].data[1]) { - gSpecialVar_Result = 0x7F; + gSpecialVar_Result = MULTI_B_PRESSED; done = TRUE; } break; From cefb05bdec70f424c840e022e4cd4540ae4dcc41 Mon Sep 17 00:00:00 2001 From: sbird Date: Wed, 29 Nov 2023 13:19:37 +0100 Subject: [PATCH 9/9] [dynmulti] left/top read from variables --- asm/macros/event.inc | 4 ++-- src/scrcmd.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/asm/macros/event.inc b/asm/macros/event.inc index bc29334894..c78c3140aa 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -1731,8 +1731,8 @@ .macro _dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req, callbacks:req argv:vararg .byte 0xe3 - .byte \left - .byte \top + .2byte \left + .2byte \top .byte \ignoreBPress .byte \maxBeforeScroll .byte \shouldSort diff --git a/src/scrcmd.c b/src/scrcmd.c index 7a094c7bee..47d7d1b7eb 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1376,8 +1376,8 @@ static void DynamicMultichoiceSortList(struct ListMenuItem *items, u32 count) bool8 ScrCmd_dynmultichoice(struct ScriptContext *ctx) { u32 i; - u32 left = ScriptReadByte(ctx); - u32 top = ScriptReadByte(ctx); + u32 left = VarGet(ScriptReadHalfword(ctx)); + u32 top = VarGet(ScriptReadHalfword(ctx)); bool32 ignoreBPress = ScriptReadByte(ctx); u32 maxBeforeScroll = ScriptReadByte(ctx); bool32 shouldSort = ScriptReadByte(ctx);