Skip to content

Commit

Permalink
Dynamic Multichoices (#3826)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexOn1ine authored Jan 3, 2024
2 parents 9f09309 + 34f51da commit cb89df8
Show file tree
Hide file tree
Showing 12 changed files with 574 additions and 9 deletions.
32 changes: 32 additions & 0 deletions asm/macros/event.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1735,6 +1735,38 @@
.2byte \quantity
.endm

.macro _dynmultichoice left:req, top:req, ignoreBPress:req, maxBeforeScroll:req, shouldSort:req, initialSelected:req, callbacks:req argv:vararg
.byte 0xe3
.2byte \left
.2byte \top
.byte \ignoreBPress
.byte \maxBeforeScroll
.byte \shouldSort
.2byte \initialSelected
.byte \callbacks
.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, 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
.2byte \id
.endm

.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


@ Supplementary

Expand Down
2 changes: 2 additions & 0 deletions data/script_cmd_table.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions include/constants/script_menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions include/field_specials.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

extern bool8 gBikeCyclingChallenge;
extern u8 gBikeCollisions;
extern u16 gScrollableMultichoice_ScrollOffset;

u8 GetLeadMonIndex(void);
u8 IsDestinationBoxFull(void);
Expand Down
2 changes: 2 additions & 0 deletions include/list_menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions include/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
26 changes: 26 additions & 0 deletions include/script_menu.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
#ifndef GUARD_SCRIPT_MENU_H
#define GUARD_SCRIPT_MENU_H

#include "list_menu.h"
#include "constants/script_menu.h"
#include "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);
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, 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);
void DrawMultichoiceMenuInternal(u8 left, u8 top, u8 multichoiceId, bool8 ignoreBPress, u8 cursorPos, const struct MenuAction *actions, int count);
Expand Down
9 changes: 5 additions & 4 deletions src/field_specials.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,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;
Expand All @@ -96,6 +96,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);
Expand Down Expand Up @@ -2561,7 +2562,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);
Expand Down Expand Up @@ -2635,7 +2636,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);
Expand Down Expand Up @@ -2756,7 +2757,7 @@ static void ScrollableMultichoice_UpdateScrollArrows(u8 taskId)
template.secondY = task->tHeight * 8 + 10;
template.fullyUpThreshold = 0;
template.fullyDownThreshold = task->tNumItems - task->tMaxItemsOnScreen;
task->tScrollArrowId = AddScrollIndicatorArrowPair(&template, &sScrollableMultichoice_ScrollOffset);
task->tScrollArrowId = AddScrollIndicatorArrowPair(&template, &gScrollableMultichoice_ScrollOffset);
}
}

Expand Down
16 changes: 11 additions & 5 deletions src/list_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand All @@ -867,15 +866,17 @@ 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:
case 3:
ListMenuErasePrintedCursor(list, oldSelectedRow);
ListMenuScroll(list, cursorCount, movingDown);
ListMenuDrawCursor(list);
ListMenuCallSelectionChangedCallback(list, FALSE);
if (callCallback)
ListMenuCallSelectionChangedCallback(list, FALSE);
CopyWindowToVram(list->template.windowId, COPYWIN_GFX);
break;
}
Expand All @@ -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)
{
return ListMenuChangeSelectionFull(list, updateCursorAndCallCallback, updateCursorAndCallCallback, count, movingDown);
}

static void ListMenuCallSelectionChangedCallback(struct ListMenu *list, u8 onInit)
{
if (list->template.moveCursorFunc != NULL)
Expand Down
98 changes: 98 additions & 0 deletions src/scrcmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -69,6 +71,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.
Expand Down Expand Up @@ -1351,6 +1354,101 @@ 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;
u32 left = VarGet(ScriptReadHalfword(ctx));
u32 top = VarGet(ScriptReadHalfword(ctx));
bool32 ignoreBPress = ScriptReadByte(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);
struct ListMenuItem *items;

if (argc == 0)
return FALSE;

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;
if (i == initialSelected)
initialRow = i;
}
}
else
{
argc = MultichoiceDynamic_StackSize();
items = AllocZeroed(sizeof(struct ListMenuItem) * argc);
for (i = 0; i < argc; ++i)
{
struct ListMenuItem *currentItem = MultichoiceDynamic_PeekElementAt(i);
items[i] = *currentItem;
if (currentItem->id == initialSelected)
initialRow = i;
}
if (shouldSort)
DynamicMultichoiceSortList(items, argc);
MultichoiceDynamic_DestroyStack();
}

if (ScriptMenu_MultichoiceDynamic(left, top, argc, items, ignoreBPress, maxBeforeScroll, initialRow, callbackSet))
{
ScriptContext_Stop();
return TRUE;
}
else
{
return FALSE;
}
}

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;
StringExpandPlaceholders(nameBuffer, name);
item.name = nameBuffer;
item.id = id;
MultichoiceDynamic_PushElement(item);
return FALSE;
}

bool8 ScrCmd_multichoice(struct ScriptContext *ctx)
{
u8 left = ScriptReadByte(ctx);
Expand Down
9 changes: 9 additions & 0 deletions src/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit cb89df8

Please sign in to comment.