pokeemerald/src/apprentice.c
aaaaaa123456789 7dc95a0103 Undo PokeCodec's PRs
This commit undoes most of PokeCodec's PRs after the debate in chat. Some
harmless or completely superseded PRs have been left alone, as there is not
much benefit in attempting to undo them.

Reverts #1104, #1108, #1115, #1118, #1119, #1124, #1126, #1127, #1132, #1136,
#1137, #1139, #1140, #1144, #1148, #1149, #1150, #1153, #1155, #1177, #1179,
#1180, #1181, #1182 and #1183.
2020-09-13 06:30:55 -03:00

1327 lines
42 KiB
C

#include "global.h"
#include "apprentice.h"
#include "battle.h"
#include "battle_tower.h"
#include "data.h"
#include "event_data.h"
#include "event_object_movement.h"
#include "field_player_avatar.h"
#include "international_string_util.h"
#include "item.h"
#include "item_menu.h"
#include "main.h"
#include "malloc.h"
#include "menu.h"
#include "new_game.h"
#include "party_menu.h"
#include "random.h"
#include "script.h"
#include "script_menu.h"
#include "sound.h"
#include "string_util.h"
#include "strings.h"
#include "task.h"
#include "text.h"
#include "constants/battle_frontier.h"
#include "constants/easy_chat.h"
#include "constants/items.h"
#include "constants/pokemon.h"
#include "constants/songs.h"
#include "constants/species.h"
#include "constants/trainers.h"
#include "constants/moves.h"
/* Summary of Apprentice, because (as of writing at least) its not very well documented online
*
* ## Basic info
* In the Battle Tower lobby there is an NPC which asks to be taught by the player
* They can be any 1 of 16 NPC trainers, each with their own name, class, and set of possible party species
* They ask the player a series of questions once per day, and eventually depart the lobby to be replaced by a new Apprentice
*
* ## Initial Questions
* The first question they always ask is a request to be taught, which cannot be rejected
* The second question (which follows immediately after) is whether they should participate in Battle Tower Lv 50 or Open Lv
* After these opening questions they always ask the player to choose between 2 mons, which they repeat 3 times
*
* ## Random Questions
* After choosing 3 mons for them, the Apprentice will randomly ask between 1 and 8 questions of 4 different types, as follows
* - Asking which mon to lead with, which they will only ask at most once
* - Asking which move a mon should use, which they will ask at most 5 times
* - Asking what held item to give to a mon, which they will ask at most 3 times (once for each mon)
* - Asking what they should say when they win a battle, which will always be their final question before departing
*
* ## After departing
* After telling them what they should say when they win a battle they will leave the lobby for a final time
* They will then be replaced by a new random Apprentice (they can repeat)
* Up to 4 old Apprentices are saved and can be encountered (or partnered with) during challenges of the mode they were told to battle in
* They can also be record mixed to and from other Emerald games
* Old/record mixed Apprentices are stored in struct Apprentice apprentices of SaveBlock2
* and the current Apprentice is stored in struct PlayersApprentice playerApprentice of SaveBlock2
*/
#define PLAYER_APPRENTICE gSaveBlock2Ptr->playerApprentice
#define CURRENT_QUESTION_NUM PLAYER_APPRENTICE.questionsAnswered - NUM_WHICH_MON_QUESTIONS
struct ApprenticePartyMovesData
{
u8 moveCounter;
u16 moves[MULTI_PARTY_SIZE][NUM_WHICH_MOVE_QUESTIONS];
u8 moveSlots[MULTI_PARTY_SIZE][NUM_WHICH_MOVE_QUESTIONS];
};
struct ApprenticeQuestionData
{
u16 speciesId;
u16 altSpeciesId;
u16 moveId1;
u16 moveId2;
};
// IWRAM common
struct ApprenticePartyMovesData *gApprenticePartyMovesData;
struct ApprenticeQuestionData *gApprenticeQuestionData;
void (*gApprenticeFunc)(void);
// This file's functions.
static u16 GetRandomAlternateMove(u8 monId);
static bool8 TrySetMove(u8 monId, u16 moveId);
static void CreateChooseAnswerTask(bool8 noBButton, u8 itemsCount, u8 windowId);
static u8 CreateAndShowWindow(u8 left, u8 top, u8 width, u8 height);
static void RemoveAndHideWindow(u8 windowId);
static void ExecuteFuncAfterButtonPress(void (*func)(void));
static void Script_GivenApprenticeLvlMode(void);
static void Script_SetApprenticeLvlMode(void);
static void Script_SetApprenticeId(void);
static void ShuffleApprenticeSpecies(void);
static void Script_SetRandomQuestionData(void);
static void IncrementQuestionsAnswered(void);
static void IsFinalQuestion(void);
static void Script_CreateApprenticeMenu(void);
static void Script_PrintApprenticeMessage(void);
static void Script_ResetPlayerApprentice(void);
static void GetShouldCheckApprenticeGone(void);
static void ApprenticeGetQuestion(void);
static void GetNumApprenticePartyMonsAssigned(void);
static void SetApprenticePartyMon(void);
static void InitQuestionData(void);
static void FreeQuestionData(void);
static void ApprenticeBufferString(void);
static void SetApprenticeMonMove(void);
static void SetLeadApprenticeMon(void);
static void Script_ApprenticeOpenBagMenu(void);
static void TrySetApprenticeHeldItem(void);
static void SaveApprentice(void);
static void SetSavedApprenticeTrainerGfxId(void);
static void SetPlayerApprenticeTrainerGfxId(void);
static void GetShouldApprenticeLeave(void);
static void ShiftSavedApprentices(void);
#include "data/battle_frontier/apprentice.h"
void BufferApprenticeChallengeText(u8 saveApprenticeId)
{
u8 i, num;
const u8 *challengeText;
num = gSaveBlock2Ptr->apprentices[saveApprenticeId].number;
for (i = 0; num != 0 && i < APPRENTICE_COUNT; num /= 10, i++)
;
StringCopy7(gStringVar1, gSaveBlock2Ptr->apprentices[saveApprenticeId].playerName);
ConvertInternationalString(gStringVar1, gSaveBlock2Ptr->apprentices[saveApprenticeId].language);
ConvertIntToDecimalStringN(gStringVar2, gSaveBlock2Ptr->apprentices[saveApprenticeId].number, STR_CONV_MODE_RIGHT_ALIGN, i);
challengeText = sApprenticeChallengeTexts[gSaveBlock2Ptr->apprentices[saveApprenticeId].id];
StringExpandPlaceholders(gStringVar4, challengeText);
}
void Apprentice_EnableBothScriptContexts(void)
{
EnableBothScriptContexts();
}
void ResetApprenticeStruct(struct Apprentice *apprentice)
{
u8 i;
for (i = 0; i < ARRAY_COUNT(apprentice->speechWon); i++)
apprentice->speechWon[i] = 0xFFFF;
apprentice->playerName[0] = EOS;
apprentice->id = NUM_APPRENTICES;
}
void ResetAllApprenticeData(void)
{
u8 i, j;
PLAYER_APPRENTICE.saveId = 0;
for (i = 0; i < APPRENTICE_COUNT; i++)
{
for (j = 0; j < ARRAY_COUNT(gSaveBlock2Ptr->apprentices[i].speechWon); j++)
gSaveBlock2Ptr->apprentices[i].speechWon[j] = 0xFFFF;
gSaveBlock2Ptr->apprentices[i].id = NUM_APPRENTICES;
gSaveBlock2Ptr->apprentices[i].playerName[0] = EOS;
gSaveBlock2Ptr->apprentices[i].lvlMode = 0;
gSaveBlock2Ptr->apprentices[i].number = 0;
gSaveBlock2Ptr->apprentices[i].numQuestions = 0;
for (j = 0; j < TRAINER_ID_LENGTH; j++)
gSaveBlock2Ptr->apprentices[i].playerId[j] = 0;
gSaveBlock2Ptr->apprentices[i].language = gGameLanguage;
gSaveBlock2Ptr->apprentices[i].checksum = 0;
}
Script_ResetPlayerApprentice();
}
static bool8 GivenApprenticeLvlMode(void)
{
return (PLAYER_APPRENTICE.lvlMode != 0);
}
static void SetApprenticeId(void)
{
if (gSaveBlock2Ptr->apprentices[0].number == 0)
{
do
{
PLAYER_APPRENTICE.id = sInitialApprenticeIds[Random() % ARRAY_COUNT(sInitialApprenticeIds)];
} while (PLAYER_APPRENTICE.id == gSaveBlock2Ptr->apprentices[0].id);
}
else
{
do
{
PLAYER_APPRENTICE.id = Random() % (NUM_APPRENTICES);
} while (PLAYER_APPRENTICE.id == gSaveBlock2Ptr->apprentices[0].id);
}
}
static void SetPlayersApprenticeLvlMode(u8 mode)
{
PLAYER_APPRENTICE.lvlMode = mode;
}
static void ShuffleApprenticeSpecies(void)
{
u8 species[APPRENTICE_SPECIES_COUNT];
u8 i;
for (i = 0; i < ARRAY_COUNT(species); i++)
species[i] = i;
// Shuffle the possible species an arbitrary 50 times
for (i = 0; i < 50; i++)
{
u8 temp;
u8 rand1 = Random() % ARRAY_COUNT(species);
u8 rand2 = Random() % ARRAY_COUNT(species);
SWAP(species[rand1], species[rand2], temp);
}
for (i = 0; i < MULTI_PARTY_SIZE; i++)
PLAYER_APPRENTICE.speciesIds[i] = ((species[i * 2] & 0xF) << 4) | ((species[i * 2 + 1]) & 0xF);
}
// Pick one of the Apprentice's mons to ask the question about
// Picking a move chooses a random mon, picking a held item is sequential (so that none are repeated)
static u8 GetMonIdForQuestion(u8 questionId, u8 *party, u8 *partySlot)
{
u8 i, count;
u8 monId = 0;
if (questionId == QUESTION_ID_WHICH_MOVE)
{
do
{
monId = Random() % (MULTI_PARTY_SIZE);
for (count = 0, i = 0; i < NUM_WHICH_MOVE_QUESTIONS; i++)
{
if (gApprenticePartyMovesData->moves[monId][i] != MOVE_NONE)
count++;
}
} while (count > MULTI_PARTY_SIZE);
}
else if (questionId == QUESTION_ID_WHAT_ITEM)
{
monId = party[*partySlot];
(*partySlot)++;
}
return monId;
}
// Sets the random order and data for the remaining questions after the initial "choose mon" questions
static void SetRandomQuestionData(void)
{
u8 questionOrder[APPRENTICE_MAX_QUESTIONS + 1];
u8 partyOrder[MULTI_PARTY_SIZE];
u8 partySlot;
u8 i, j;
u8 rand1, rand2;
u8 id;
for (i = 0; i < ARRAY_COUNT(partyOrder); i++)
partyOrder[i] = i;
// Shuffle the party an arbitrary 10 times
for (i = 0; i < 10; i++)
{
u8 temp;
rand1 = Random() % ARRAY_COUNT(partyOrder);
rand2 = Random() % ARRAY_COUNT(partyOrder);
SWAP(partyOrder[rand1], partyOrder[rand2], temp);
}
for (i = 0; i < ARRAY_COUNT(questionOrder); i++)
questionOrder[i] = sQuestionPossibilities[i];
// Shuffle the questions an arbitrary 50 times
for (i = 0; i < 50; i++)
{
u8 temp;
rand1 = Random() % ARRAY_COUNT(questionOrder);
rand2 = Random() % ARRAY_COUNT(questionOrder);
SWAP(questionOrder[rand1], questionOrder[rand2], temp);
}
gApprenticePartyMovesData = AllocZeroed(sizeof(*gApprenticePartyMovesData));
gApprenticePartyMovesData->moveCounter = 0;
for (i = 0; i < NUM_WHICH_MOVE_QUESTIONS; i++)
{
for (j = 0; j < MULTI_PARTY_SIZE; j++)
gApprenticePartyMovesData->moveSlots[j][i] = MAX_MON_MOVES;
}
partySlot = 0;
for (i = 0; i < APPRENTICE_MAX_QUESTIONS; i++)
{
PLAYER_APPRENTICE.questions[i].questionId = questionOrder[i];
if (questionOrder[i] != QUESTION_ID_WHICH_FIRST)
{
PLAYER_APPRENTICE.questions[i].monId = GetMonIdForQuestion(questionOrder[i], partyOrder, &partySlot);
id = PLAYER_APPRENTICE.questions[i].monId;
if (questionOrder[i] == QUESTION_ID_WHICH_MOVE)
{
do
{
rand1 = Random() % MAX_MON_MOVES;
for (j = 0; j < gApprenticePartyMovesData->moveCounter + 1; j++)
{
if (gApprenticePartyMovesData->moveSlots[id][j] == rand1)
break;
}
} while (j != gApprenticePartyMovesData->moveCounter + 1);
gApprenticePartyMovesData->moveSlots[id][gApprenticePartyMovesData->moveCounter] = rand1;
PLAYER_APPRENTICE.questions[i].moveSlot = rand1;
PLAYER_APPRENTICE.questions[i].data = GetRandomAlternateMove(PLAYER_APPRENTICE.questions[i].monId);
}
}
}
FREE_AND_SET_NULL(gApprenticePartyMovesData);
}
// No idea why a do-while loop is needed, but it will not match without it.
#define APPRENTICE_SPECIES_ID(speciesArrId, monId) speciesArrId = (PLAYER_APPRENTICE.speciesIds[monId] >> \
(((PLAYER_APPRENTICE.party >> monId) & 1) << 2)) & 0xF; \
do {} while (0)
// Why the need to have two macros do the exact thing differently?
#define APPRENTICE_SPECIES_ID_2(speciesArrId, monId) { u8 a0 = ((PLAYER_APPRENTICE.party >> monId) & 1);\
speciesArrId = PLAYER_APPRENTICE.speciesIds[monId]; \
speciesArrId = ((speciesArrId) >> (a0 << 2)) & 0xF; \
}
// Get the second move choice for the "Which move" question
// Unlike the first move choice, this can be either a level up move or a TM/HM move
static u16 GetRandomAlternateMove(u8 monId)
{
u8 i, j;
u8 id;
u8 numLearnsetMoves;
u16 species;
const u16 *learnset;
bool32 needTMs = FALSE;
u16 moveId = MOVE_NONE;
bool32 shouldUseMove;
u8 level;
if (monId < MULTI_PARTY_SIZE)
{
APPRENTICE_SPECIES_ID(id, monId);
}
else
{
id = 0;
}
species = gApprentices[PLAYER_APPRENTICE.id].species[id];
learnset = gLevelUpLearnsets[species];
j = 0;
// Despite being open level, level up moves are only read up to level 60
if (PLAYER_APPRENTICE.lvlMode == APPRENTICE_LVL_MODE_50)
level = 50;
else // == APPRENTICE_LVL_MODE_OPEN
level = 60;
for (j = 0; learnset[j] != LEVEL_UP_END; j++)
{
if ((learnset[j] & LEVEL_UP_MOVE_LV) > (level << 9))
break;
}
numLearnsetMoves = j;
i = 0;
// i < 5 here is arbitrary, i isnt used and is only incremented when the selected move isnt in sValidApprenticeMoves
// This while loop contains 3 potential infinite loops, though none of them would occur in the base game
while (i < 5)
{
if (Random() % 2 == 0 || needTMs == TRUE)
{
// Get TM move
// NOTE: Below is an infinite loop if a species that only learns TMs for moves
// that are also in its level up learnset is assigned to an Apprentice
do
{
// NOTE: Below is an infinite loop if a species which cannot learn TMs is assigned to an Apprentice
do
{
id = Random() % (NUM_TECHNICAL_MACHINES + NUM_HIDDEN_MACHINES);
shouldUseMove = CanSpeciesLearnTMHM(species, id);
}
while (!shouldUseMove);
moveId = ItemIdToBattleMoveId(ITEM_TM01 + id);
shouldUseMove = TRUE;
if (numLearnsetMoves <= MAX_MON_MOVES)
j = 0;
else
j = numLearnsetMoves - MAX_MON_MOVES;
for (; j < numLearnsetMoves; j++)
{
// Keep looking for TMs until one not in the level up learnset is found
if ((learnset[j] & LEVEL_UP_MOVE_ID) == moveId)
{
shouldUseMove = FALSE;
break;
}
}
} while (shouldUseMove != TRUE);
}
else
{
if (numLearnsetMoves <= MAX_MON_MOVES)
{
needTMs = TRUE;
continue;
}
else
{
// Get level up move
// NOTE: Below is an infinite loop if a mon whose last 4 moves contain
// all the moves in the rest of its learnset is assigned to an Apprentice
do
{
// Get a random move excluding the 4 it would know at max level
u8 learnsetId = Random() % (numLearnsetMoves - MAX_MON_MOVES);
moveId = learnset[learnsetId] & LEVEL_UP_MOVE_ID;
shouldUseMove = TRUE;
for (j = numLearnsetMoves - MAX_MON_MOVES; j < numLearnsetMoves; j++)
{
// Keep looking for moves until one not in the last 4 is found
if ((learnset[j] & LEVEL_UP_MOVE_ID) == moveId)
{
shouldUseMove = FALSE;
break;
}
}
} while (shouldUseMove != TRUE);
}
}
if (TrySetMove(monId, moveId))
{
if (sValidApprenticeMoves[moveId])
break;
i++;
}
}
gApprenticePartyMovesData->moveCounter++;
return moveId;
}
static bool8 TrySetMove(u8 monId, u16 moveId)
{
u8 i;
for (i = 0; i < NUM_WHICH_MOVE_QUESTIONS; i++)
{
if (gApprenticePartyMovesData->moves[monId][i] == moveId)
return FALSE;
}
gApprenticePartyMovesData->moves[monId][gApprenticePartyMovesData->moveCounter] = moveId;
return TRUE;
}
static void GetLatestLearnedMoves(u16 species, u16 *moves)
{
u8 i, j;
u8 level, numLearnsetMoves;
const u16 *learnset;
if (PLAYER_APPRENTICE.lvlMode == APPRENTICE_LVL_MODE_50)
level = 50;
else // == APPRENTICE_LVL_MODE_OPEN
level = 60;
learnset = gLevelUpLearnsets[species];
for (i = 0; learnset[i] != LEVEL_UP_END; i++)
{
if ((learnset[i] & LEVEL_UP_MOVE_LV) > (level << 9))
break;
}
numLearnsetMoves = i;
if (numLearnsetMoves > MAX_MON_MOVES)
numLearnsetMoves = MAX_MON_MOVES;
for (j = 0; j < numLearnsetMoves; j++)
moves[j] = learnset[(i - 1) - j] & LEVEL_UP_MOVE_ID;
}
// Get the level up move or previously suggested move to be the first move choice
// Compare to GetRandomAlternateMove, which gets the move that will be the second choice
static u16 GetDefaultMove(u8 monId, u8 speciesArrayId, u8 moveSlot)
{
u16 moves[MAX_MON_MOVES];
u8 i, numQuestions;
if (PLAYER_APPRENTICE.questionsAnswered < NUM_WHICH_MON_QUESTIONS)
return MOVE_NONE;
numQuestions = 0;
for (i = 0; i < APPRENTICE_MAX_QUESTIONS && PLAYER_APPRENTICE.questions[i].questionId != QUESTION_ID_WIN_SPEECH; i++)
numQuestions++;
GetLatestLearnedMoves(gApprentices[PLAYER_APPRENTICE.id].species[speciesArrayId], moves);
for (i = 0; i < numQuestions && i < CURRENT_QUESTION_NUM; i++)
{
if (PLAYER_APPRENTICE.questions[i].questionId == QUESTION_ID_WHICH_MOVE
&& PLAYER_APPRENTICE.questions[i].monId == monId
&& PLAYER_APPRENTICE.questions[i].suggestedChange)
{
moves[PLAYER_APPRENTICE.questions[i].moveSlot] = PLAYER_APPRENTICE.questions[i].data;
}
}
return moves[moveSlot];
}
static void SaveApprenticeParty(u8 numQuestions)
{
struct ApprenticeMon *apprenticeMons[MULTI_PARTY_SIZE];
u8 i, j;
u32 speciesTableId;
for (i = 0; i < MULTI_PARTY_SIZE; i++)
{
gSaveBlock2Ptr->apprentices[0].party[i].species = SPECIES_NONE;
gSaveBlock2Ptr->apprentices[0].party[i].item = ITEM_NONE;
for (j = 0; j < MAX_MON_MOVES; j++)
gSaveBlock2Ptr->apprentices[0].party[i].moves[j] = MOVE_NONE;
}
// Save party order
j = PLAYER_APPRENTICE.leadMonId;
for (i = 0; i < MULTI_PARTY_SIZE; i++)
{
apprenticeMons[j] = &gSaveBlock2Ptr->apprentices[0].party[i];
j = (j + 1) % (MULTI_PARTY_SIZE);
}
// Save party species
for (i = 0; i < MULTI_PARTY_SIZE; i++)
{
APPRENTICE_SPECIES_ID(speciesTableId, i);
apprenticeMons[i]->species = gApprentices[PLAYER_APPRENTICE.id].species[speciesTableId];
GetLatestLearnedMoves(apprenticeMons[i]->species, apprenticeMons[i]->moves);
}
// Update party based on response to held item / move choice questions
for (i = 0; i < numQuestions; i++)
{
u8 questionId = PLAYER_APPRENTICE.questions[i].questionId;
u8 monId = PLAYER_APPRENTICE.questions[i].monId;
if (questionId == QUESTION_ID_WHAT_ITEM)
{
if (PLAYER_APPRENTICE.questions[i].suggestedChange)
apprenticeMons[monId]->item = PLAYER_APPRENTICE.questions[i].data;
}
else if (questionId == QUESTION_ID_WHICH_MOVE)
{
if (PLAYER_APPRENTICE.questions[i].suggestedChange)
{
u32 moveSlot = PLAYER_APPRENTICE.questions[i].moveSlot;
apprenticeMons[monId]->moves[moveSlot] = PLAYER_APPRENTICE.questions[i].data;
}
}
}
}
static void CreateApprenticeMenu(u8 menu)
{
u8 i;
u8 windowId;
const u8 *strings[3];
u8 count = 2;
u8 width;
u8 left;
u8 top;
s32 pixelWidth;
switch (menu)
{
case APPRENTICE_ASK_WHICH_LEVEL:
left = 18;
top = 8;
strings[0] = gText_Lv50;
strings[1] = gText_OpenLevel;
break;
case APPRENTICE_ASK_3SPECIES:
count = MULTI_PARTY_SIZE;
left = 18;
top = 6;
for (i = 0; i < MULTI_PARTY_SIZE; i++)
{
u16 species;
u32 speciesTableId;
APPRENTICE_SPECIES_ID(speciesTableId, i);
species = gApprentices[PLAYER_APPRENTICE.id].species[speciesTableId];
strings[i] = gSpeciesNames[species];
}
break;
case APPRENTICE_ASK_2SPECIES:
left = 18;
top = 8;
if (PLAYER_APPRENTICE.questionsAnswered >= NUM_WHICH_MON_QUESTIONS)
return;
strings[1] = gSpeciesNames[gApprenticeQuestionData->altSpeciesId];
strings[0] = gSpeciesNames[gApprenticeQuestionData->speciesId];
break;
case APPRENTICE_ASK_MOVES:
left = 17;
top = 8;
strings[0] = gMoveNames[gApprenticeQuestionData->moveId1];
strings[1] = gMoveNames[gApprenticeQuestionData->moveId2];
break;
case APPRENTICE_ASK_GIVE:
left = 18;
top = 8;
strings[0] = gText_Give;
strings[1] = gText_NoNeed;
break;
case APPRENTICE_ASK_YES_NO:
left = 20;
top = 8;
strings[0] = gText_Yes;
strings[1] = gText_No;
break;
default:
left = 0;
top = 0;
break;
}
pixelWidth = 0;
for (i = 0; i < count; i++)
{
s32 width = GetStringWidth(1, strings[i], 0);
if (width > pixelWidth)
pixelWidth = width;
}
width = ConvertPixelWidthToTileWidth(pixelWidth);
left = ScriptMenu_AdjustLeftCoordFromWidth(left, width);
windowId = CreateAndShowWindow(left, top, width, count * 2);
SetStandardWindowBorderStyle(windowId, 0);
for (i = 0; i < count; i++)
AddTextPrinterParameterized(windowId, 1, strings[i], 8, (i * 16) + 1, TEXT_SPEED_FF, NULL);
InitMenuInUpperLeftCornerPlaySoundWhenAPressed(windowId, count, 0);
CreateChooseAnswerTask(TRUE, count, windowId);
}
#define tNoBButton data[4]
#define tWrapAround data[5]
#define tWindowId data[6]
static void Task_ChooseAnswer(u8 taskId)
{
s8 input;
s16 *data = gTasks[taskId].data;
if (!tWrapAround)
input = Menu_ProcessInputNoWrap();
else
input = Menu_ProcessInput();
switch (input)
{
case MENU_NOTHING_CHOSEN:
return;
case MENU_B_PRESSED:
if (tNoBButton)
return;
PlaySE(SE_SELECT);
gSpecialVar_Result = 0x7F;
break;
default:
gSpecialVar_Result = input;
break;
}
RemoveAndHideWindow(tWindowId);
DestroyTask(taskId);
EnableBothScriptContexts();
}
static u8 CreateAndShowWindow(u8 left, u8 top, u8 width, u8 height)
{
u8 windowId;
struct WindowTemplate winTemplate = CreateWindowTemplate(0, left + 1, top + 1, width, height, 15, 100);
windowId = AddWindow(&winTemplate);
PutWindowTilemap(windowId);
CopyWindowToVram(windowId, 3);
return windowId;
}
static void RemoveAndHideWindow(u8 windowId)
{
ClearStdWindowAndFrameToTransparent(windowId, TRUE);
RemoveWindow(windowId);
}
static void CreateChooseAnswerTask(bool8 noBButton, u8 answers, u8 windowId)
{
u8 taskId = CreateTask(Task_ChooseAnswer, 80);
gTasks[taskId].tNoBButton = noBButton;
if (answers > 3)
gTasks[taskId].tWrapAround = TRUE;
else
gTasks[taskId].tWrapAround = FALSE;
gTasks[taskId].tWindowId = windowId;
}
#undef tNoBButton
#undef tWrapAround
#undef tWindowId
void CallApprenticeFunction(void)
{
sApprenticeFunctions[gSpecialVar_0x8004]();
}
static void Script_ResetPlayerApprentice(void)
{
u8 i;
SetApprenticeId();
PLAYER_APPRENTICE.lvlMode = 0;
PLAYER_APPRENTICE.questionsAnswered = 0;
PLAYER_APPRENTICE.leadMonId = 0;
PLAYER_APPRENTICE.party = 0;
for (i = 0; i < MULTI_PARTY_SIZE; i++)
PLAYER_APPRENTICE.speciesIds[i] = 0;
for (i = 0; i < APPRENTICE_MAX_QUESTIONS; i++)
{
PLAYER_APPRENTICE.questions[i].questionId = 0;
PLAYER_APPRENTICE.questions[i].monId = 0;
PLAYER_APPRENTICE.questions[i].moveSlot = 0;
PLAYER_APPRENTICE.questions[i].suggestedChange = 0;
PLAYER_APPRENTICE.questions[i].data = 0;
}
}
static void Script_GivenApprenticeLvlMode(void)
{
if (!GivenApprenticeLvlMode())
gSpecialVar_Result = FALSE;
else
gSpecialVar_Result = TRUE;
}
// VAR_0x8005 is 1 + the selection value from the multichoice APPRENTICE_ASK_WHICH_LEVEL
// i.e. APPRENTICE_LVL_MODE_50 or APPRENTICE_LVL_MODE_OPEN
static void Script_SetApprenticeLvlMode(void)
{
SetPlayersApprenticeLvlMode(gSpecialVar_0x8005);
}
// Never called, APPRENTICE_FUNC_SET_ID is unused
static void Script_SetApprenticeId(void)
{
SetApprenticeId();
}
static void Script_SetRandomQuestionData(void)
{
SetRandomQuestionData();
}
static void IncrementQuestionsAnswered(void)
{
PLAYER_APPRENTICE.questionsAnswered++;
}
// The first 3 questions answered after meeting the Apprentice are always selecting party mons
// after which this is never called
static void GetNumApprenticePartyMonsAssigned(void)
{
gSpecialVar_Result = PLAYER_APPRENTICE.questionsAnswered;
}
// Never called, APPRENTICE_FUNC_IS_FINAL_QUESTION is unused
static void IsFinalQuestion(void)
{
s32 questionNum = CURRENT_QUESTION_NUM;
if (questionNum < 0)
{
// Not finished asking initial questions
gSpecialVar_Result = FALSE;
}
else
{
if (questionNum > APPRENTICE_MAX_QUESTIONS - 1)
gSpecialVar_Result = TRUE;
if (PLAYER_APPRENTICE.questions[questionNum].questionId == QUESTION_ID_WIN_SPEECH)
gSpecialVar_Result = TRUE;
else
gSpecialVar_Result = FALSE;
}
}
static void Script_CreateApprenticeMenu(void)
{
CreateApprenticeMenu(gSpecialVar_0x8005);
}
static void Task_WaitForPrintingMessage(u8 taskId)
{
if (!RunTextPrintersAndIsPrinter0Active())
{
DestroyTask(taskId);
if (gSpecialVar_0x8005)
ExecuteFuncAfterButtonPress(EnableBothScriptContexts);
else
EnableBothScriptContexts();
}
}
static void PrintApprenticeMessage(void)
{
const u8 *string;
if (gSpecialVar_0x8006 == APPRENTICE_MSG_WHICH_MON)
{
string = sApprenticeWhichMonTexts[PLAYER_APPRENTICE.id][0];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_MON)
{
string = sApprenticeWhichMonTexts[PLAYER_APPRENTICE.id][1];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_WHICH_MOVE)
{
string = sApprenticeWhichMoveTexts[PLAYER_APPRENTICE.id][0];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_MOVE)
{
string = sApprenticeWhichMoveTexts[PLAYER_APPRENTICE.id][1];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_WHICH_MON_FIRST)
{
string = sApprenticeWhichMonFirstTexts[PLAYER_APPRENTICE.id][0];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_MON_FIRST)
{
string = sApprenticeWhichMonFirstTexts[PLAYER_APPRENTICE.id][1];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_WHAT_HELD_ITEM)
{
string = sApprenticeHeldItemTexts[PLAYER_APPRENTICE.id][0];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_PICK_WIN_SPEECH)
{
string = sApprenticePickWinSpeechTexts[PLAYER_APPRENTICE.id][0];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_HELD_ITEM)
{
string = sApprenticeHeldItemTexts[PLAYER_APPRENTICE.id][3];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_HOLD_NOTHING)
{
string = sApprenticeHeldItemTexts[PLAYER_APPRENTICE.id][1];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_ITEM_ALREADY_SUGGESTED)
{
string = sApprenticeHeldItemTexts[PLAYER_APPRENTICE.id][4];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_NO_HELD_ITEM)
{
string = sApprenticeHeldItemTexts[PLAYER_APPRENTICE.id][2];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_WIN_SPEECH)
{
string = sApprenticePickWinSpeechTexts[PLAYER_APPRENTICE.id][1];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_PLEASE_TEACH)
{
string = sApprenticeFirstMeetingTexts[PLAYER_APPRENTICE.id][0];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_REJECT)
{
string = sApprenticeFirstMeetingTexts[PLAYER_APPRENTICE.id][1];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_WHICH_LVL_MODE)
{
string = sApprenticeFirstMeetingTexts[PLAYER_APPRENTICE.id][2];
}
else if (gSpecialVar_0x8006 == APPRENTICE_MSG_THANKS_LVL_MODE)
{
string = sApprenticeFirstMeetingTexts[PLAYER_APPRENTICE.id][3];
}
else
{
EnableBothScriptContexts();
return;
}
StringExpandPlaceholders(gStringVar4, string);
AddTextPrinterForMessage(TRUE);
CreateTask(Task_WaitForPrintingMessage, 1);
}
static void Script_PrintApprenticeMessage(void)
{
ScriptContext2_Enable();
FreezeObjectEvents();
sub_808B864();
sub_808BCF4();
DrawDialogueFrame(0, 1);
PrintApprenticeMessage();
}
static void ApprenticeGetQuestion(void)
{
if (PLAYER_APPRENTICE.questionsAnswered < NUM_WHICH_MON_QUESTIONS)
{
gSpecialVar_Result = APPRENTICE_QUESTION_WHICH_MON;
}
else if (PLAYER_APPRENTICE.questionsAnswered > (APPRENTICE_MAX_QUESTIONS + NUM_WHICH_MON_QUESTIONS - 1))
{
gSpecialVar_Result = APPRENTICE_QUESTION_WIN_SPEECH;
}
else
{
s32 id = CURRENT_QUESTION_NUM;
switch (PLAYER_APPRENTICE.questions[id].questionId)
{
case QUESTION_ID_WHAT_ITEM:
gSpecialVar_Result = APPRENTICE_QUESTION_WHAT_ITEM;
break;
case QUESTION_ID_WHICH_MOVE:
gSpecialVar_Result = APPRENTICE_QUESTION_WHICH_MOVE;
break;
case QUESTION_ID_WHICH_FIRST:
gSpecialVar_Result = APPRENTICE_QUESTION_WHICH_FIRST;
break;
default:
//case QUESTION_ID_WIN_SPEECH:
gSpecialVar_Result = APPRENTICE_QUESTION_WIN_SPEECH;
break;
}
}
}
// gSpecialVar_0x8005 is 0 or 1 for the mon selection (0 is already on the team)
// gSpecialVar_0x8006 is 0-2 for the number of party mons selected so far
static void SetApprenticePartyMon(void)
{
if (gSpecialVar_0x8005)
{
u8 partySlot = gSpecialVar_0x8006;
PLAYER_APPRENTICE.party |= 1 << partySlot;
}
}
// gSpecialVar_0x8005 is 0 or 1 for the move selection
// Selection 0 is implicitly the default move assigned
static void SetApprenticeMonMove(void)
{
if (PLAYER_APPRENTICE.questionsAnswered >= NUM_WHICH_MON_QUESTIONS)
{
u8 id = CURRENT_QUESTION_NUM;
if (gSpecialVar_0x8005)
PLAYER_APPRENTICE.questions[id].suggestedChange = TRUE;
else
PLAYER_APPRENTICE.questions[id].suggestedChange = FALSE;
}
}
static void InitQuestionData(void)
{
u8 i;
u8 count = 0;
u8 id1, id2;
for (i = 0; i < APPRENTICE_MAX_QUESTIONS && (PLAYER_APPRENTICE.questions[i].questionId != QUESTION_ID_WIN_SPEECH); count++, i++)
;
gApprenticeQuestionData = AllocZeroed(sizeof(*gApprenticeQuestionData));
if (gSpecialVar_0x8005 == APPRENTICE_QUESTION_WHICH_MON)
{
if (PLAYER_APPRENTICE.questionsAnswered < NUM_WHICH_MON_QUESTIONS)
{
// For the first MULTI_PARTY_SIZE (3) questions, a mon is asked to be selected for the Apprentice's party
id1 = PLAYER_APPRENTICE.speciesIds[PLAYER_APPRENTICE.questionsAnswered] >> 4;
gApprenticeQuestionData->altSpeciesId = gApprentices[PLAYER_APPRENTICE.id].species[id1];
id2 = PLAYER_APPRENTICE.speciesIds[PLAYER_APPRENTICE.questionsAnswered] & 0xF;
gApprenticeQuestionData->speciesId = gApprentices[PLAYER_APPRENTICE.id].species[id2];
}
}
else if (gSpecialVar_0x8005 == APPRENTICE_QUESTION_WHICH_MOVE)
{
if (PLAYER_APPRENTICE.questionsAnswered >= NUM_WHICH_MON_QUESTIONS
&& PLAYER_APPRENTICE.questionsAnswered < count + NUM_WHICH_MON_QUESTIONS
&& PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].questionId == QUESTION_ID_WHICH_MOVE)
{
// count re-used as monId
count = PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].monId;
APPRENTICE_SPECIES_ID_2(id1, count);
gApprenticeQuestionData->speciesId = gApprentices[PLAYER_APPRENTICE.id].species[id1];
gApprenticeQuestionData->moveId1 = GetDefaultMove(count, id1, PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].moveSlot);
gApprenticeQuestionData->moveId2 = PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].data;
}
}
else if (gSpecialVar_0x8005 == APPRENTICE_QUESTION_WHAT_ITEM)
{
if (PLAYER_APPRENTICE.questionsAnswered >= NUM_WHICH_MON_QUESTIONS
&& PLAYER_APPRENTICE.questionsAnswered < count + NUM_WHICH_MON_QUESTIONS
&& PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].questionId == QUESTION_ID_WHAT_ITEM)
{
// count re-used as monId
count = PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].monId;
APPRENTICE_SPECIES_ID_2(id2, count);
gApprenticeQuestionData->speciesId = gApprentices[PLAYER_APPRENTICE.id].species[id2];
}
}
}
static void FreeQuestionData(void)
{
FREE_AND_SET_NULL(gApprenticeQuestionData);
}
static void ApprenticeBufferString(void)
{
u8 *stringDst;
u8 text[16];
u32 speciesArrayId;
switch (gSpecialVar_0x8005)
{
case 0:
stringDst = gStringVar1;
break;
case 1:
stringDst = gStringVar2;
break;
case 2:
stringDst = gStringVar3;
break;
default:
return;
}
switch (gSpecialVar_0x8006)
{
case APPRENTICE_BUFF_SPECIES1:
StringCopy(stringDst, gSpeciesNames[gApprenticeQuestionData->speciesId]);
break;
case APPRENTICE_BUFF_SPECIES2:
StringCopy(stringDst, gSpeciesNames[gApprenticeQuestionData->altSpeciesId]);
break;
case APPRENTICE_BUFF_SPECIES3:
StringCopy(stringDst, gSpeciesNames[gApprenticeQuestionData->speciesId]);
break;
case APPRENTICE_BUFF_MOVE1:
StringCopy(stringDst, gMoveNames[gApprenticeQuestionData->moveId1]);
break;
case APPRENTICE_BUFF_MOVE2:
StringCopy(stringDst, gMoveNames[gApprenticeQuestionData->moveId2]);
break;
case APPRENTICE_BUFF_ITEM:
StringCopy(stringDst, ItemId_GetName(PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].data));
break;
case APPRENTICE_BUFF_NAME:
TVShowConvertInternationalString(text, GetApprenticeNameInLanguage(PLAYER_APPRENTICE.id, LANGUAGE_ENGLISH), LANGUAGE_ENGLISH);
StringCopy(stringDst, text);
break;
case APPRENTICE_BUFF_LEVEL:
if (PLAYER_APPRENTICE.lvlMode == APPRENTICE_LVL_MODE_50)
StringCopy(stringDst, gText_Lv50);
else // == APPRENTICE_LVL_MODE_OPEN
StringCopy(stringDst, gText_OpenLevel);
break;
case APPRENTICE_BUFF_WIN_SPEECH:
FrontierSpeechToString(gSaveBlock2Ptr->apprentices[0].speechWon);
StringCopy(stringDst, gStringVar4);
break;
case APPRENTICE_BUFF_LEAD_MON_SPECIES:
if (PLAYER_APPRENTICE.leadMonId < MULTI_PARTY_SIZE)
{
APPRENTICE_SPECIES_ID(speciesArrayId, PLAYER_APPRENTICE.leadMonId);
}
else
{
speciesArrayId = 0;
}
StringCopy(stringDst, gSpeciesNames[gApprentices[PLAYER_APPRENTICE.id].species[speciesArrayId]]);
break;
}
}
static void SetLeadApprenticeMon(void)
{
PLAYER_APPRENTICE.leadMonId = gSpecialVar_0x8005;
}
static void Script_ApprenticeOpenBagMenu(void)
{
ApprenticeOpenBagMenu();
}
static void TrySetApprenticeHeldItem(void)
{
u8 i, j;
u8 count;
if (PLAYER_APPRENTICE.questionsAnswered < NUM_WHICH_MON_QUESTIONS)
return;
for (count = 0, j = 0; j < APPRENTICE_MAX_QUESTIONS && PLAYER_APPRENTICE.questions[j].questionId != QUESTION_ID_WIN_SPEECH; count++, j++)
;
// Make sure the item hasnt already been suggested in previous questions
for (i = 0; i < count && i < CURRENT_QUESTION_NUM; i++)
{
do {} while(0);
if (PLAYER_APPRENTICE.questions[i].questionId == QUESTION_ID_WHAT_ITEM
&& PLAYER_APPRENTICE.questions[i].suggestedChange
&& PLAYER_APPRENTICE.questions[i].data == gSpecialVar_0x8005)
{
PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].suggestedChange = FALSE;
PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].data = gSpecialVar_0x8005;
gSpecialVar_Result = FALSE;
return;
}
}
PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].suggestedChange = TRUE;
PLAYER_APPRENTICE.questions[CURRENT_QUESTION_NUM].data = gSpecialVar_0x8005;
gSpecialVar_Result = TRUE;
}
static void ShiftSavedApprentices(void)
{
s32 i;
s32 apprenticeNum;
s32 apprenticeIdx;
if (gSaveBlock2Ptr->apprentices[0].playerName[0] == EOS)
return;
for (i = 0; i < APPRENTICE_COUNT - 1; i++)
{
if (gSaveBlock2Ptr->apprentices[i + 1].playerName[0] == EOS)
{
gSaveBlock2Ptr->apprentices[i + 1] = gSaveBlock2Ptr->apprentices[0];
return;
}
}
apprenticeNum = 0xFFFF;
apprenticeIdx = -1;
for (i = 1; i < APPRENTICE_COUNT; i++)
{
if (GetTrainerId(gSaveBlock2Ptr->apprentices[i].playerId) == GetTrainerId(gSaveBlock2Ptr->playerTrainerId)
&& gSaveBlock2Ptr->apprentices[i].number < apprenticeNum)
{
apprenticeNum = gSaveBlock2Ptr->apprentices[i].number;
apprenticeIdx = i;
}
}
if (apprenticeIdx > 0)
gSaveBlock2Ptr->apprentices[apprenticeIdx] = gSaveBlock2Ptr->apprentices[0];
}
// Apprentice is always saved in the first slot. Pre-existing Apprentices are moved by ShiftSavedApprentices
static void SaveApprentice(void)
{
u8 i;
gSaveBlock2Ptr->apprentices[0].id = PLAYER_APPRENTICE.id;
gSaveBlock2Ptr->apprentices[0].lvlMode = PLAYER_APPRENTICE.lvlMode;
// Count questions asked until the final (win speech) question was reached
for (i = 0; i < APPRENTICE_MAX_QUESTIONS && (PLAYER_APPRENTICE.questions[i].questionId != QUESTION_ID_WIN_SPEECH); i++)
;
gSaveBlock2Ptr->apprentices[0].numQuestions = i;
if (gSaveBlock2Ptr->apprentices[0].number < 255)
gSaveBlock2Ptr->apprentices[0].number++;
SaveApprenticeParty(gSaveBlock2Ptr->apprentices[0].numQuestions);
for (i = 0; i < TRAINER_ID_LENGTH; i++)
gSaveBlock2Ptr->apprentices[0].playerId[i] = gSaveBlock2Ptr->playerTrainerId[i];
StringCopy(gSaveBlock2Ptr->apprentices[0].playerName, gSaveBlock2Ptr->playerName);
gSaveBlock2Ptr->apprentices[0].language = gGameLanguage;
CalcApprenticeChecksum(&gSaveBlock2Ptr->apprentices[0]);
}
// Never called, APPRENTICE_FUNC_SET_GFX_SAVED is unused
static void SetSavedApprenticeTrainerGfxId(void)
{
u8 i;
u8 objectEventGfxId;
u8 class = gApprentices[gSaveBlock2Ptr->apprentices[0].id].facilityClass;
for (i = 0; i < ARRAY_COUNT(gTowerMaleFacilityClasses) && gTowerMaleFacilityClasses[i] != class; i++)
;
if (i != ARRAY_COUNT(gTowerMaleFacilityClasses))
{
objectEventGfxId = gTowerMaleTrainerGfxIds[i];
VarSet(VAR_OBJ_GFX_ID_0, objectEventGfxId);
return;
}
for (i = 0; i < ARRAY_COUNT(gTowerFemaleFacilityClasses) && gTowerFemaleFacilityClasses[i] != class; i++)
;
if (i != ARRAY_COUNT(gTowerFemaleFacilityClasses))
{
objectEventGfxId = gTowerFemaleTrainerGfxIds[i];
VarSet(VAR_OBJ_GFX_ID_0, objectEventGfxId);
}
}
static void SetPlayerApprenticeTrainerGfxId(void)
{
u8 i;
u8 objectEventGfxId;
u8 class = gApprentices[PLAYER_APPRENTICE.id].facilityClass;
for (i = 0; i < ARRAY_COUNT(gTowerMaleFacilityClasses) && gTowerMaleFacilityClasses[i] != class; i++)
;
if (i != ARRAY_COUNT(gTowerMaleFacilityClasses))
{
objectEventGfxId = gTowerMaleTrainerGfxIds[i];
VarSet(VAR_OBJ_GFX_ID_0, objectEventGfxId);
return;
}
for (i = 0; i < ARRAY_COUNT(gTowerFemaleFacilityClasses) && gTowerFemaleFacilityClasses[i] != class; i++)
;
if (i != ARRAY_COUNT(gTowerFemaleFacilityClasses))
{
objectEventGfxId = gTowerFemaleTrainerGfxIds[i];
VarSet(VAR_OBJ_GFX_ID_0, objectEventGfxId);
}
}
// Both of the below functions may have been dummied / used for debug
// In all cases theres a conditional for VAR_0x8004 right after the call to these functions
static void GetShouldCheckApprenticeGone(void)
{
gSpecialVar_0x8004 = TRUE;
}
static void GetShouldApprenticeLeave(void)
{
gSpecialVar_0x8004 = TRUE;
}
const u8 *GetApprenticeNameInLanguage(u32 apprenticeId, s32 language)
{
const struct ApprenticeTrainer *apprentice = &gApprentices[apprenticeId];
switch (language)
{
case LANGUAGE_JAPANESE:
return apprentice->name[0];
case LANGUAGE_ENGLISH:
return apprentice->name[1];
case LANGUAGE_FRENCH:
return apprentice->name[2];
case LANGUAGE_ITALIAN:
return apprentice->name[3];
case LANGUAGE_GERMAN:
return apprentice->name[4];
case LANGUAGE_SPANISH:
default:
return apprentice->name[5];
}
}
// Functionally unused
static void Task_SwitchToFollowupFuncAfterButtonPress(u8 taskId)
{
if (gMain.newKeys & A_BUTTON || gMain.newKeys & B_BUTTON)
SwitchTaskToFollowupFunc(taskId);
}
static void Task_ExecuteFuncAfterButtonPress(u8 taskId)
{
if (gMain.newKeys & A_BUTTON || gMain.newKeys & B_BUTTON)
{
gApprenticeFunc = (void*)(u32)(((u16)gTasks[taskId].data[0] | (gTasks[taskId].data[1] << 16)));
gApprenticeFunc();
DestroyTask(taskId);
}
}
static void ExecuteFuncAfterButtonPress(void (*func)(void))
{
u8 taskId = CreateTask(Task_ExecuteFuncAfterButtonPress, 1);
gTasks[taskId].data[0] = (u32)(func);
gTasks[taskId].data[1] = (u32)(func) >> 16;
}
// Unused
static void ExecuteFollowupFuncAfterButtonPress(TaskFunc task)
{
u8 taskId = CreateTask(Task_SwitchToFollowupFuncAfterButtonPress, 1);
SetTaskFuncWithFollowupFunc(taskId, Task_SwitchToFollowupFuncAfterButtonPress, task);
}