#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 struct LevelUpMove *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].move != LEVEL_UP_END; j++) { if (learnset[j].level > level) 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].move) == 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].move; 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].move) == 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 struct LevelUpMove *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].move != LEVEL_UP_END; i++) { if (learnset[i].level > level) break; } numLearnsetMoves = i; if (numLearnsetMoves > MAX_MON_MOVES) numLearnsetMoves = MAX_MON_MOVES; for (j = 0; j < numLearnsetMoves; j++) moves[j] = learnset[(i - 1) - j].move; } // 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); }