code:
#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/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 (JOY_NEW(A_BUTTON) || JOY_NEW(B_BUTTON))
        SwitchTaskToFollowupFunc(taskId);
}

static void Task_ExecuteFuncAfterButtonPress(u8 taskId)
{
    if (JOY_NEW(A_BUTTON) || JOY_NEW(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);
}


code:
// Includes
#include "global.h"
#include "bard_music.h"
#include "constants/easy_chat.h"
#include "easy_chat.h"

#include "data/bard_music/bard_sounds.h"
#include "data/bard_music/word_pitch.h"
#include "data/bard_music/default_sound.h"
#include "data/bard_music/length_table.h"

s16 CalcWordPitch(int arg0, int songPos)
{
    return gBardSoundPitchTables[arg0][songPos];
}

const struct BardSound *GetWordSounds(u16 word)
{
    u32 category;
    u32 subword;
    const struct BardSound (*ptr)[6];

    if (ECWord_CheckIfOutsideOfValidRange(word))
    {
        return gBardSound_InvalidWord;
    }
    category = word >> 9;
    subword = word & 0x1ff;
    switch (category)
    {
        case EC_GROUP_POKEMON:
        case EC_GROUP_POKEMON_2:
            ptr = gBardSounds_Pokemon;
            break;
        case EC_GROUP_MOVE_1:
        case EC_GROUP_MOVE_2:
            ptr = gBardSounds_Moves;
            break;
        default:
            ptr = gBardSoundsTable[category];
            break;
    }
    ptr += subword;
    return *ptr;
}

void GetWordPhonemes(struct BardSong *song, u16 word)
{
    int i;
    const struct BardSound *sound;

    song->length = 0;
    for (i = 0; i < 6; i ++)
    {
        sound = &song->sound[i];
        if (sound->var00 != 0xFF)
        {
            song->phonemes[i].length = sound->var01 + gBardSoundLengthTable[sound->var00];
            song->phonemes[i].pitch = CalcWordPitch(word + 30, i);
            song->length += song->phonemes[i].length;
        }
    }
    song->currPhoneme = 0;
    song->voiceInflection = 0;
}

repo_name: Lunos/pokeemerald

license: mit