#include "global.h"
#include "constants/items.h"
#include "constants/item_effects.h"
#include "constants/moves.h"
#include "constants/songs.h"
#include "constants/species.h"
#include "party_menu.h"
#include "player_pc.h"
#include "battle.h"
#include "battle_interface.h"
#include "battle_party_menu.h"
#include "choose_party.h"
#include "data2.h"
#include "decompress.h"
#include "event_data.h"
#include "evolution_scene.h"
#include "item.h"
#include "item_use.h"
#include "item_menu.h"
#include "link.h"
#include "mail_data.h"
#include "main.h"
#include "menu.h"
#include "menu_helpers.h"
#include "palette.h"
#include "pokemon.h"
#include "pokemon_icon.h"
#include "pokemon_item_effect.h"
#include "pokemon_menu.h"
#include "pokemon_summary_screen.h"
#include "rom_8077ABC.h"
#include "rom_8094928.h"
#include "sound.h"
#include "sprite.h"
#include "string_util.h"
#include "strings.h"
#include "task.h"
#include "scanline_effect.h"
#include "util.h"
#include "script_pokemon_80F9.h"
#include "ewram.h"

struct Coords8
{
    u8 x;
    u8 y;
};

struct PartyMenuWindowCoords
{
    u8 left;
    u8 top;
    u8 right;
    u8 bottom;
};

struct PartyMonTextSettingsStruct
{
    u8 xOffset;
    u8 yOffset;
    const u16 *oamSettings;
};

struct PartyMenuHandlersStruct
{
    /*0x0*/TaskFunc menuHandler;
    /*0x4*/bool8 (*menuSetup)(void);
    /*0x8*/u8 initialPromptTextId; // element in PartyMenuPromptTexts
};

static void nullsub_12(u8 monIndex, struct Pokemon *pokemon);
static void TryPrintPartyMenuMonNickname(u8 monIndex, struct Pokemon *pokemon);
static void PartyMenuTryPrintHP(u8 monIndex, struct Pokemon *pokemon);
static void sub_806D05C(u8 taskId);
static void sub_806D15C(u8 taskId);
static void sub_806D198(u8 taskId);
static void Task_PartyMenuPrintRun(u8 taskId);
static void sub_8070D90(u8 taskId);
static void sub_806D5B8(u8 taskId);
static void sub_806D014(u8 taskId);
static void sub_806D118(u8 taskId);
static void CB2_InitPartyMenu(void);
static void ReDrawPartyMonBackgrounds(void);
static void sub_806BA94(s16 a, u16 b, u8 c, u8 d);
static void sub_806B9A4(s16 a, u16 b, u8 c);
static void sub_806CA18(u8 taskId, u8 b);
static void ChangeDoubleBattlePartyMenuSelection(u8 spriteId, u8 menuIndex, s8 directionPressed);
static void ChangeDefaultPartyMenuSelection(u8 spriteId, u8 menuIndex, s8 directionPressed);
void ChangeLinkDoubleBattlePartyMenuSelection(u8 spriteId, u8 menuIndex, s8 directionPressed);
static void UpdateMonIconFrame_806DA0C(struct Sprite *sprite);
static void UpdateMonIconFrame_806DA38(struct Sprite *sprite);
static void UpdateMonIconFrame_806DA44(u8 taskId, u8 monIndex, u8 c);
static u8 sub_806CA00(u8 taskId);
static void SpriteCB_sub_806D37C(struct Sprite *sprite);
static u8 GetMonIconSpriteId(u8 taskId, u8 monIndex);
void SpriteCB_UpdateHeldItemIconPosition(struct Sprite *sprite);
static void ItemUseMoveMenu_HandleMoveSelection(u8 taskId);
static void ItemUseMoveMenu_HandleCancel(u8 taskId);
static bool8 SetupDefaultPartyMenu(void);
/*static*/ void sub_806B4A8(void);
/*static*/ void VBlankCB_PartyMenu(void);
/*static*/ bool8 LoadPartyMenuGraphics(u8 a);
static void sub_806BF24(const u8 *a, u8 monIndex, u8 c, u8 d);
static void sub_806BB9C(u8 a);
static void sub_806BBEC(u8 a);

void (*gPokemonItemUseCallback)(u8 taskId, u16 itemId, TaskFunc taskFunc);

extern u16 Random();

EWRAM_DATA u8 gUnknown_0202E8F4 = 0;
EWRAM_DATA u8 gUnknown_0202E8F5 = 0;
EWRAM_DATA u8 gPartyMenuMessage_IsPrinting = 0;
EWRAM_DATA u16 gUnknown_0202E8F8 = 0;
EWRAM_DATA u8 gPartyMenuType = 0;

const u16 TMHMMoves[] =
{
    MOVE_FOCUS_PUNCH,
    MOVE_DRAGON_CLAW,
    MOVE_WATER_PULSE,
    MOVE_CALM_MIND,
    MOVE_ROAR,
    MOVE_TOXIC,
    MOVE_HAIL,
    MOVE_BULK_UP,
    MOVE_BULLET_SEED,
    MOVE_HIDDEN_POWER,
    MOVE_SUNNY_DAY,
    MOVE_TAUNT,
    MOVE_ICE_BEAM,
    MOVE_BLIZZARD,
    MOVE_HYPER_BEAM,
    MOVE_LIGHT_SCREEN,
    MOVE_PROTECT,
    MOVE_RAIN_DANCE,
    MOVE_GIGA_DRAIN,
    MOVE_SAFEGUARD,
    MOVE_FRUSTRATION,
    MOVE_SOLAR_BEAM,
    MOVE_IRON_TAIL,
    MOVE_THUNDERBOLT,
    MOVE_THUNDER,
    MOVE_EARTHQUAKE,
    MOVE_RETURN,
    MOVE_DIG,
    MOVE_PSYCHIC,
    MOVE_SHADOW_BALL,
    MOVE_BRICK_BREAK,
    MOVE_DOUBLE_TEAM,
    MOVE_REFLECT,
    MOVE_SHOCK_WAVE,
    MOVE_FLAMETHROWER,
    MOVE_SLUDGE_BOMB,
    MOVE_SANDSTORM,
    MOVE_FIRE_BLAST,
    MOVE_ROCK_TOMB,
    MOVE_AERIAL_ACE,
    MOVE_TORMENT,
    MOVE_FACADE,
    MOVE_SECRET_POWER,
    MOVE_REST,
    MOVE_ATTRACT,
    MOVE_THIEF,
    MOVE_STEEL_WING,
    MOVE_SKILL_SWAP,
    MOVE_SNATCH,
    MOVE_OVERHEAT,
    MOVE_CUT,
    MOVE_FLY,
    MOVE_SURF,
    MOVE_STRENGTH,
    MOVE_FLASH,
    MOVE_ROCK_SMASH,
    MOVE_WATERFALL,
    MOVE_DIVE,
};

//FIXME

u8 *const gTileBufferPtr = gTileBuffer;

static const u8 MenuGfx_HoldIcons[] = INCBIN_U8("graphics/interface/hold_icons.4bpp");
static const u16 MenuPal_HoldIcons[] = INCBIN_U16("graphics/interface/hold_icons.gbapal");

static const struct SpriteSheet HeldItemsSpriteSheet = {
    MenuGfx_HoldIcons,
    sizeof MenuGfx_HoldIcons,
    0xd750
};

static const struct SpritePalette HeldItemsPalette = {
    MenuPal_HoldIcons,
    0xd750
};

static const struct OamData gOamData_83765EC =
{
    .y = 0,
    .affineMode = 0,
    .objMode = 0,
    .mosaic = 0,
    .bpp = 0,
    .shape = 0,
    .x = 0,
    .matrixNum = 0,
    .size = 0,
    .tileNum = 0,
    .priority = 1,
    .paletteNum = 0,
    .affineParam = 0,
};

static const union AnimCmd gSpriteAnim_83765F4[] = {
    ANIMCMD_FRAME(0, 1),
    ANIMCMD_END
};

static const union AnimCmd gSpriteAnim_83765FC[] = {
    ANIMCMD_FRAME(1, 1),
    ANIMCMD_END
};

static const union AnimCmd *const gSpriteAnimTable_8376604[] = {
    gSpriteAnim_83765F4,
    gSpriteAnim_83765FC,
};

static const struct SpriteTemplate gSpriteTemplate_837660C = {
    55120,
    55120,
    &gOamData_83765EC,
    gSpriteAnimTable_8376604,
    NULL,
    gDummySpriteAffineAnimTable,
    SpriteCB_UpdateHeldItemIconPosition
};

// Texts that can be displayed in the bottom of the party menu.
static const u8 *const PartyMenuPromptTexts[] = {
    OtherText_ChoosePoke,
    OtherText_MovePokeTo,
    OtherText_TeachWhat,
    OtherText_UseWhat,
    OtherText_GiveWhat,
    OtherText_DoWhat,
    OtherText_NothingToCut,
    OtherText_CantSurf,
    OtherText_AlreadySurfing,
    OtherText_CantUseThatHere,
    OtherText_RestoreWhatMove,
    OtherText_BoostPP,
    gOtherText_CancelWithTerminator,
    OtherText_DoWhatWithItem,
    OtherText_NoPokeForBattle,
    OtherText_ChoosePoke2,
    OtherText_NotEnoughHP,
    OtherText_ThreePokeNeeded,
    OtherText_PokeCantBeSame,
    OtherText_NoIdenticalHoldItems,
    OtherText_TeachWhichPoke,
};

static const struct Coords8 gUnknown_08376678[8][6] = {
    {{16, 40}, {104, 18}, {104, 42}, {104, 66}, {104, 90}, {104, 114}}, // PARTY_MENU_TYPE_STANDARD
    {{16, 24}, { 16, 80}, {104, 18}, {104, 50}, {104, 82}, {104, 114}}, // PARTY_MENU_TYPE_BATTLE
    {{16, 24}, { 16, 80}, {104, 26}, {104, 50}, {104, 82}, {104, 106}}, // PARTY_MENU_TYPE_CONTEST
    {{16, 24}, {104, 26}, {104, 50}, { 16, 80}, {104, 82}, {104, 106}}, // PARTY_MENU_TYPE_IN_GAME_TRADE
    {{ 5,  4}, { 16,  1}, { 16,  4}, { 16,  7}, { 16, 10}, { 16,  13}}, // PARTY_MENU_TYPE_BATTLE_TOWER
    {{ 5,  2}, {  5,  9}, { 16,  1}, { 16,  5}, { 16,  9}, { 16,  13}}, // PARTY_MENU_TYPE_LINK_MULTI_BATTLE
    {{ 5,  2}, {  5,  9}, { 16,  2}, { 16,  5}, { 16,  9}, { 16,  12}}, // PARTY_MENU_TYPE_DAYCARE
    {{ 5,  2}, { 16,  2}, { 16,  5}, {  5,  9}, { 16,  9}, { 16,  12}}, // PARTY_MENU_TYPE_MOVE_TUTOR
};

static const struct Coords8 gUnknown_08376738[12][6] = {
    {{6,  5}, {17,  2}, {17,  5}, {17,  8}, {17, 11}, {17, 14}},
    {{6,  3}, { 6, 10}, {17,  2}, {17,  6}, {17, 10}, {17, 14}},
    {{6,  3}, { 6, 10}, {17,  3}, {17,  6}, {17, 10}, {17, 13}},
    {{6,  3}, {17,  3}, {17,  6}, { 6, 10}, {17, 10}, {17, 13}},
    {{3,  7}, {22,  2}, {22,  5}, {22,  8}, {22, 11}, {22, 14}},
    {{3,  5}, { 3, 12}, {22,  2}, {22,  6}, {22, 10}, {22, 14}},
    {{3,  5}, { 3, 12}, {22,  3}, {22,  6}, {22, 10}, {22, 13}},
    {{3,  5}, {22,  3}, {22,  6}, { 3, 12}, {22, 10}, {22, 13}},
    {{7,  7}, {26,  2}, {26,  5}, {26,  8}, {26, 11}, {26, 14}},
    {{7,  5}, { 7, 12}, {26,  2}, {26,  6}, {26, 10}, {26, 14}},
    {{7,  5}, { 7, 12}, {26,  3}, {26,  6}, {26, 10}, {26, 13}},
    {{7,  5}, {26,  3}, {26,  6}, { 7, 12}, {26, 10}, {26, 13}},
};

static u16 *const gUnknown_08376858[4][6] = {
    {(u16*)(BG_VRAM + 0xF1C8), (u16*)(BG_VRAM + 0xF0AE), (u16*)(BG_VRAM + 0xF16E), (u16*)(BG_VRAM + 0xF22E), (u16*)(BG_VRAM + 0xF2EE), (u16*)(BG_VRAM + 0xF3AE)}, // PARTY_MENU_STANDARD
    {(u16*)(BG_VRAM + 0xF148), (u16*)(BG_VRAM + 0xF308), (u16*)(BG_VRAM + 0xF0AE), (u16*)(BG_VRAM + 0xF1AE), (u16*)(BG_VRAM + 0xF2AE), (u16*)(BG_VRAM + 0xF3AE)}, // PARTY_MENU_LAYOUT_DOUBLE_BATTLE
    {(u16*)(BG_VRAM + 0xF148), (u16*)(BG_VRAM + 0xF308), (u16*)(BG_VRAM + 0xF0EE), (u16*)(BG_VRAM + 0xF1AE), (u16*)(BG_VRAM + 0xF2AE), (u16*)(BG_VRAM + 0xF36E)}, // PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE
    {(u16*)(BG_VRAM + 0xF148), (u16*)(BG_VRAM + 0xF0EE), (u16*)(BG_VRAM + 0xF1AE), (u16*)(BG_VRAM + 0xF308), (u16*)(BG_VRAM + 0xF2AE), (u16*)(BG_VRAM + 0xF36E)}, // PARTY_MENU_LAYOUT_MULTI_BATTLE
};

static const struct Coords8 gUnknown_083768B8[3][8] = {
    {{8,  44}, {92,  22}, {92,  46}, {92,  70}, {92,  94}, {92, 118}, {196, 136}, {196, 152}}, // PARTY_MENU_LAYOUT_STANDARD
    {{8,  28}, { 8,  84}, {92,  22}, {92,  54}, {92,  86}, {92, 118}, {196, 136}, {196, 152}}, // PARTY_MENU_LAYOUT_DOUBLE_BATTLE
    {{8,  28}, { 8,  84}, {92,  30}, {92,  54}, {92,  86}, {92, 110}, {196, 136}, {196, 152}}, // PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE
};

static u16 *const gUnknown_08376918[2][PARTY_SIZE] = {
    {(u16*)(BG_VRAM + 0xF1C6), (u16*)(BG_VRAM + 0xF06C), (u16*)(BG_VRAM + 0xF12C), (u16*)(BG_VRAM + 0xF1EC), (u16*)(BG_VRAM + 0xF2AC), (u16*)(BG_VRAM + 0xF36C)},
    {(u16*)(BG_VRAM + 0xF148), (u16*)(BG_VRAM + 0xF308), (u16*)(BG_VRAM + 0xF0AE), (u16*)(BG_VRAM + 0xF1AE), (u16*)(BG_VRAM + 0xF2AE), (u16*)(BG_VRAM + 0xF3AE)},
};

static const struct PartyMenuWindowCoords gUnknown_08376948[2][6] = {
    {{2, 4, 10, 9}, {16, 1, 29,  3}, {16, 4, 29, 6}, {16, 7, 29, 9}, {16, 10, 29, 12}, {16, 13, 29, 15}},
    {{2, 2, 10, 7}, { 2, 9, 10, 14}, {16, 1, 29, 3}, {16, 5, 29, 7}, {16,  9, 29, 11}, {16, 13, 29, 15}},
};

static const struct PartyMenuWindowCoords gUnknown_08376978[2][6] = {
    {{2, 7, 10,  9}, {21, 1, 29,  3}, {21, 4, 29,  6}, {21, 7, 29, 9}, {21, 10, 29, 12}, {21, 13, 29, 15}},
    {{2, 2, 10,  7}, { 2, 9, 10, 14}, {16, 1, 29,  3}, {16, 5, 29, 7}, {16,  9, 29, 11}, {16, 13, 29, 15}},
};

// This is actually a 2x6x2 array, but the code reads it as a flat array.
static const u8 gUnknown_083769A8[] = {
    0, 3,   11, 1,   11, 4,    11, 7,    11, 10,    11, 13,
    0, 1,    0, 8,   11, 1,    11, 5,    11,  9,    11, 13, // Double battle
};

// This is actually a 2x6x2 array, but the code reads it as a flat array.
//FIXME: sub_806B908() accesses this data via gUnknown_083769A8 (directly above this). This means these
// two arrays might be a struct, rather than separate arrays.
static const u8 gUnknown_083769C0[] = {
    0, 1,    0, 8,   11, 2,    11, 5,    11, 9,    11, 12,
    0, 1,    0, 8,   11, 2,    11, 5,    11, 9,    11, 12, // Double battle
};

static const u8 gUnknown_083769D8[] = {
    0x24,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x27,
    0x34,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x37,
    0x34,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x37,
    0x34,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x37,
    0x44,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x47,
    0x44,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x47,
    0x54,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x57,
};

static const u8 gUnknown_08376A25[] = {
    0x50,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x53,
    0x60,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x63,
    0x70,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x73,
};

static const u8 gUnknown_08376A5E[] = {
    0x20,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x23,
    0x30,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x33,
    0x40,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x43,
};

static const u8 gUnusedData_08376A97[] = {
    0x0C,0x0D,0x0E,0x0F,0x00,0x01,0x02,0x03,0x04,0x05,0x0F,
    0x10,0x11,0x12,0x13,0x14,0x15,0x0F,0x06,0x05,0x01,0x07,
    0x08,0x09,0x0F,0x16,0x15,0x11,0x17,0x18,0x19,0x0F,0x09,
    0x0A,0x0B,0x05,0x0C,0x0F,0x0F,0x19,0x1A,0x1B,0x15,0x1C,
    0x0F,0x0F,0x0D,0x0B,0x05,0x0C,0x0F,0x0F,0x0F,0x1D,0x1B,
    0x15,0x1C,0x0F,0x0F,0x0F,0x06,0x05,0x0B,0x05,0x0C,0x0F,
    0x0F,0x16,0x15,0x1B,0x15,0x1C,0x0F,0x0F,0x0F,0x0F,0x20,
    0x0C,0x09,0x0F,0x0F,0x0F,0x1F,0x30,0x1C,0x19,0x0F,0x0F,
    0x0F,0x25,0x0F,0x22,0x24,0x0F,0x0F,0x0F,0x35,0x41,0x32,
    0x34,0x0F,0x0F,0x0F,0x26,0x0F,0x23,0x0E,0x0F,0x0F,0x0F,
    0x36,0x43,0x33,0x1E,0x0F,0x0F,0x27,0x28,0x29,0x03,0x2A,
    0x0F,0x0F,0x37,0x38,0x39,0x13,0x3A,0x0F,0x0F,0x27,0x28,
    0x29,0x03,0x04,0x05,0x0F,0x37,0x38,0x39,0x13,0x14,0x15,
    0x0F,0x2B,0x2C,0x02,0x28,0x29,0x2D,0x21,0x3B,0x3C,0x12,
    0x38,0x39,0x3D,0x31,0x2B,0x2C,0x02,0x28,0x29,0x08,0x09,
    0x3B,0x3C,0x12,0x38,0x39,0x18,0x19,0x2E,0x2F,0x2B,0x2C,
    0x02,0x27,0x2D,0x3E,0x3F,0x3B,0x3C,0x12,0x45,0x3D,
};

static const TaskFunc gUnknown_08376B54[] = {
    ItemUseMoveMenu_HandleMoveSelection,
    ItemUseMoveMenu_HandleCancel,
};

#define PartyMonOAMSettings(x, y, palette, shape, size, priority, tileOffset) \
    ((shape)   << 14) | (y),                                                  \
    ((size)    << 14) | (x),                                                  \
    ((palette) << 12) | ((priority) << 10) | (tileOffset)

static const u16 PartyMonOAMSettings_LeftColumn[] = {
    PartyMonOAMSettings(24,  0, 15, ST_OAM_H_RECTANGLE, 1, 1,  0),
    PartyMonOAMSettings(56,  0, 15, ST_OAM_H_RECTANGLE, 1, 1,  4),
    PartyMonOAMSettings(24,  8, 15, ST_OAM_H_RECTANGLE, 1, 1,  8),
    PartyMonOAMSettings(56,  8, 15, ST_OAM_H_RECTANGLE, 1, 1, 12),
    PartyMonOAMSettings(32, 16, 15, ST_OAM_H_RECTANGLE, 1, 1, 16),
    PartyMonOAMSettings(37, 32, 15, ST_OAM_H_RECTANGLE, 1, 1, 24),
    PartyMonOAMSettings(69, 32, 15, ST_OAM_H_RECTANGLE, 1, 1, 28),
    0xFFFF,
};

static const u16 PartyMonOAMSettings_RightColumn[] = {
    PartyMonOAMSettings( 24,  0, 15, ST_OAM_H_RECTANGLE, 1, 1,  0),
    PartyMonOAMSettings( 56,  0, 15, ST_OAM_H_RECTANGLE, 1, 1,  4),
    PartyMonOAMSettings( 24,  8, 15, ST_OAM_H_RECTANGLE, 1, 1,  8),
    PartyMonOAMSettings( 56,  8, 15, ST_OAM_H_RECTANGLE, 1, 1, 12),
    PartyMonOAMSettings( 32, 16, 15, ST_OAM_H_RECTANGLE, 1, 1, 16),
    PartyMonOAMSettings(101, 16, 15, ST_OAM_H_RECTANGLE, 1, 1, 24),
    PartyMonOAMSettings(133, 16, 15, ST_OAM_H_RECTANGLE, 1, 1, 28),
    0xFFFF,
};

// Controls where and how the mons' text appears in the party menu screen (nickname, HP, and level).
static struct PartyMonTextSettingsStruct const PartyMonTextSettings[4][6] =
{
    { // PARTY_MENU_LAYOUT_STANDARD
        { 1,  4, PartyMonOAMSettings_LeftColumn},
        {12,  1, PartyMonOAMSettings_RightColumn},
        {12,  4, PartyMonOAMSettings_RightColumn},
        {12,  7, PartyMonOAMSettings_RightColumn},
        {12, 10, PartyMonOAMSettings_RightColumn},
        {12, 13, PartyMonOAMSettings_RightColumn},
    },
    { // PARTY_MENU_LAYOUT_DOUBLE_BATTLE
        { 1,  2, PartyMonOAMSettings_LeftColumn},
        { 1,  9, PartyMonOAMSettings_LeftColumn},
        {12,  1, PartyMonOAMSettings_RightColumn},
        {12,  5, PartyMonOAMSettings_RightColumn},
        {12,  9, PartyMonOAMSettings_RightColumn},
        {12, 13, PartyMonOAMSettings_RightColumn},
    },
    { // PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE
        { 1,  2, PartyMonOAMSettings_LeftColumn},
        { 1,  9, PartyMonOAMSettings_LeftColumn},
        {12,  2, PartyMonOAMSettings_RightColumn},
        {12,  5, PartyMonOAMSettings_RightColumn},
        {12,  9, PartyMonOAMSettings_RightColumn},
        {12, 12, PartyMonOAMSettings_RightColumn},
    },
    { // PARTY_MENU_LAYOUT_MULTI_BATTLE
        { 1,  2, PartyMonOAMSettings_LeftColumn},
        {12,  2, PartyMonOAMSettings_RightColumn},
        {12,  5, PartyMonOAMSettings_RightColumn},
        { 1,  9, PartyMonOAMSettings_LeftColumn},
        {12,  9, PartyMonOAMSettings_RightColumn},
        {12, 12, PartyMonOAMSettings_RightColumn},
    },
};

static const struct PartyMenuHandlersStruct PartyMenuHandlers[] =
{
    {HandleDefaultPartyMenu,         SetupDefaultPartyMenu,            0}, // PARTY_MENU_TYPE_STANDARD
    {HandleBattlePartyMenu,          SetUpBattlePartyMenu,             0}, // PARTY_MENU_TYPE_BATTLE
    {HandleSelectPartyMenu,          SetupContestPartyMenu,            0}, // PARTY_MENU_TYPE_CONTEST
    {HandleSelectPartyMenu,          SetupDefaultPartyMenu,            0}, // PARTY_MENU_TYPE_IN_GAME_TRADE
    {HandleBattleTowerPartyMenu,     SetupBattleTowerPartyMenu,        0}, // PARTY_MENU_TYPE_BATTLE_TOWER
    {HandleLinkMultiBattlePartyMenu, SetupLinkMultiBattlePartyMenu, 0xFF}, // PARTY_MENU_TYPE_LINK_MULTI_BATTLE
    {HandleDaycarePartyMenu,         SetupDefaultPartyMenu,         0x0F}, // PARTY_MENU_TYPE_DAYCARE
    {HandleMoveTutorPartyMenu,       SetupMoveTutorPartyMenu,          0}, // PARTY_MENU_TYPE_MOVE_TUTOR
};

extern u16 gBattleTypeFlags;
extern u8 gLastFieldPokeMenuOpened;
extern u8 gPlayerPartyCount;
extern s32 gBattleMoveDamage;
extern u16 gMoveToLearn;

extern u16 gUnknown_08E9A300[];
extern struct Coords8 const gUnknown_08376738[12][6];
extern struct Window gWindowTemplate_Contest_MoveDescription;

extern const u8 gPartyMenuMisc_Gfx[];
extern const u8 gPartyMenuMisc_Tilemap[];
extern const u8 gPartyMenuMisc_Pal[];
extern const u8 gFontDefaultPalette[];
extern const u8 gPartyMenuHpBar_Gfx[];
extern const u8 gPartyMenuOrderText_Gfx[];
extern const u8 gStatusGfx_Icons[];
extern const u8 gStatusPal_Icons[];

#if ENGLISH
#define WINDOW_LEFT (3)
#define WINDOW_RIGHT (26)
#elif GERMAN
#define WINDOW_LEFT (0)
#define WINDOW_RIGHT (29)
#endif

void CB2_PartyMenuMain(void)
{
    const struct PartyMonTextSettingsStruct *textSettings;
    s32 i;

    AnimateSprites();
    BuildOamBuffer();

    textSettings = PartyMonTextSettings[gPartyMenuType];
    for (i = 0; i < PARTY_SIZE; i++)
    {
        // Draw mon name, level, and hp sprites
        DrawPartyMenuMonText(
            textSettings->xOffset * 8,
            textSettings->yOffset * 8,
            textSettings->oamSettings,
            0,
            (i << 5) | 0x200);
        textSettings++;
    }

    RunTasks();
    UpdatePaletteFade();

#if DEBUG
    if (gLinkOpen == TRUE)
        debug_sub_8008264(gLink.recvQueue.count, 1, 1, 2, 2);
#endif
}

void VBlankCB_PartyMenu(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
    ReDrawPartyMonBackgrounds();
}

void SetPartyMenuSettings(u8 menuType, u8 battleTypeFlags, TaskFunc menuHandlerFunc, u8 textId)
{
    if (battleTypeFlags != 0xFF)
    {
        gBattleTypeFlags = battleTypeFlags;
    }

    ePartyMenu2.menuType = menuType;
    ePartyMenu2.menuHandler = menuHandlerFunc;
    ePartyMenu2.promptTextId = textId;
}

void DoOpenPartyMenu(u8 menuType, u8 battleFlags, TaskFunc menuHandlerFunc, u8 textId)
{
    SetPartyMenuSettings(menuType, battleFlags, menuHandlerFunc, textId);
    SetMainCallback2(CB2_InitPartyMenu);
}

void OpenPartyMenu(u8 menuType, u8 battleFlags)
{
    DoOpenPartyMenu(menuType, battleFlags, PartyMenuHandlers[menuType].menuHandler, PartyMenuHandlers[menuType].initialPromptTextId);
}

// This is a Task which is repeatedly called until it eventually returns TRUE when finished.
bool8 SetupDefaultPartyMenu(void)
{
    switch (ePartyMenu2.pmSetupState)
    {
    case 0:
        if (ePartyMenu2.pmMonIndex < gPlayerPartyCount)
        {
            TryCreatePartyMenuMonIcon(ePartyMenu2.menuHandlerTaskId,
                ePartyMenu2.pmMonIndex, &gPlayerParty[ePartyMenu2.pmMonIndex]);
            ePartyMenu2.pmMonIndex++;
        }
        else
        {
            ePartyMenu2.pmMonIndex = 0;
            ePartyMenu2.pmSetupState++;
        }
        break;
    case 1:
        LoadHeldItemIconGraphics();
        ePartyMenu2.pmSetupState++;
        break;
    case 2:
        CreateHeldItemIcons_806DC34(ePartyMenu2.menuHandlerTaskId);
        ePartyMenu2.pmSetupState++;
        break;
    case 3:
        if (sub_806BD58(ePartyMenu2.menuHandlerTaskId, ePartyMenu2.pmMonIndex) != 1)
        {
            ePartyMenu2.pmMonIndex++;
        }
        else
        {
            ePartyMenu2.pmMonIndex = 0;
            ePartyMenu2.pmSetupState++;
        }
        break;
    case 4:
        PartyMenuPrintMonsLevelOrStatus();
        ePartyMenu2.pmSetupState++;
        break;
    case 5:
        PrintPartyMenuMonNicknames();
        ePartyMenu2.pmSetupState++;
        break;
    case 6:
        PartyMenuTryPrintMonsHP();
        ePartyMenu2.pmSetupState++;
        break;
    case 7:
        nullsub_13();
        ePartyMenu2.pmSetupState++;
        break;
    case 8:
        PartyMenuDrawHPBars();
        ePartyMenu2.pmSetupState++;
        break;
    case 9:
        if (DrawPartyMonBackground(ePartyMenu2.pmMonIndex) == 1)
        {
            ePartyMenu2.pmMonIndex = 0;
            ePartyMenu2.pmSetupState = 0;
            return TRUE;
        }
        else
        {
            ePartyMenu2.pmMonIndex++;
            break;
        }
    }

    return FALSE;
}

bool8 InitPartyMenu(void)
{
    switch (gMain.state)
    {
    case 0:
        SetVBlankCallback(NULL);
        DmaFill16Large(3, 0, (void *)(VRAM + 0x0), VRAM_SIZE, 0x1000);
        DmaClear32(3, OAM, OAM_SIZE);
        DmaClear16(3, PLTT, PLTT_SIZE);
        gPaletteFade.bufferTransferDisabled = 1;
        gMain.state++;
        break;
    case 1:
        ScanlineEffect_Stop();
        gMain.state++;
        break;
    case 2:
        sub_806B4A8();
        ePartyMenu2.pmSetupState = 0;
        ePartyMenu2.pmMonIndex = 0;
        ePartyMenu2.pmUnk268 = 0;
        gMain.state++;
        break;
    case 3:
        ResetSpriteData();
        gMain.state++;
        break;
    case 4:
        if (ePartyMenu2.menuType != PARTY_MENU_TYPE_BATTLE &&
            ePartyMenu2.menuType != PARTY_MENU_TYPE_LINK_MULTI_BATTLE)
            ResetTasks();
        gMain.state++;
        break;
    case 5:
        FreeAllSpritePalettes();
        gMain.state++;
        break;
    case 6:
        ePartyMenu2.menuHandlerTaskId = CreateTask(ePartyMenu2.menuHandler, 0);
        gMain.state++;
        break;
    case 7:
        Text_LoadWindowTemplate(&gWindowTemplate_81E6C90);
        gMain.state++;
        break;
    case 8:
        Text_InitWindowWithTemplate(&gWindowTemplate_Contest_MoveDescription, &gWindowTemplate_81E6C90);
        MultistepInitWindowTileData(&gWindowTemplate_Contest_MoveDescription, 1);
        gMain.state++;
        break;
    case 9:
        if (MultistepLoadFont())
        {
            ePartyMenu2.pmSetupState = 1;
            gMain.state++;
        }
        break;
    case 10:
        if (LoadPartyMenuGraphics(ePartyMenu2.pmSetupState) == TRUE)
        {
            ePartyMenu2.pmSetupState = 0;
            gMain.state++;
        }
        else
        {
            ePartyMenu2.pmSetupState++;
        }
        break;
    case 11:
        sub_809D51C();
        gMain.state++;
        break;
    case 12:
        if (PartyMenuHandlers[ePartyMenu2.menuType].menuSetup() == TRUE)
            gMain.state++;
        break;
    case 13:
        MultistepInitMenuWindowBegin(&gWindowTemplate_81E6CC8);
        gMain.state++;
        break;
    case 14:
        if (MultistepInitMenuWindowContinue())
            gMain.state++;
        break;
    case 15:
        PrintPartyMenuPromptText(ePartyMenu2.promptTextId, 0);
        gMain.state++;
        break;
    case 16:
        BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, RGB(0, 0, 0));
        gPaletteFade.bufferTransferDisabled = 0;
        gMain.state++;
        break;
    case 17:
        SetVBlankCallback(VBlankCB_PartyMenu);
#if DEBUG
        if (gLinkOpen == TRUE)
            debug_sub_8008218((void *)(VRAM + 0xE5E0), 0x8000, (void *)(VRAM + 0x7800), 2);
#endif
        return TRUE;
    }

    return FALSE;
}

void CB2_InitPartyMenu(void)
{
    while (InitPartyMenu() != TRUE)
    {
        if (sub_80F9344() == TRUE)
            return;
    }

    if (ePartyMenu2.menuType != PARTY_MENU_TYPE_LINK_MULTI_BATTLE)
        ChangePartyMenuSelection(ePartyMenu2.menuHandlerTaskId, 0);

    SetMainCallback2(CB2_PartyMenuMain);
}

void sub_806B4A8(void)
{
    SetHBlankCallback(NULL);
    REG_DISPCNT = 8000;
    REG_BG0CNT = 0x1E05;
    REG_BG1CNT = 0x703;
    REG_BG2CNT = 0xF08;
    REG_BG3CNT = 0x602;
    REG_BLDCNT = 0;
    REG_BG0HOFS = 0;
    REG_BG0VOFS = 0;
    REG_BG1HOFS = 0;
    REG_BG1VOFS = 0;
    REG_BG2HOFS = 0;
    REG_BG2VOFS = 0;
    REG_BG3HOFS = 0;
    REG_BG3VOFS = 0;
    REG_BG3VOFS = -1;
}

bool8 IsLinkDoubleBattle()
{
    if ((gBattleTypeFlags & BATTLE_TYPE_LINK_DOUBLE) == BATTLE_TYPE_LINK_DOUBLE)
        return TRUE;
    else
        return FALSE;
}

// Draws the blue rectangular regions surrounding each of the party mons.
void ReDrawPartyMonBackgrounds(void)
{
    if (ePartyMenu2.unk261)
    {
        DmaCopy16Defvars(3, gBGTilemapBuffers[2], (void *)(BG_VRAM + 0x3000), 0x800);

        if (ePartyMenu2.unk261 == 2)
            ePartyMenu2.unk261 = 0;
    }
}

bool8 DrawPartyMonBackground(u8 monIndex)
{
    const u8 *arr;

    if (!IsDoubleBattle())
        gPartyMenuType = PARTY_MENU_LAYOUT_STANDARD;
    else if (IsLinkDoubleBattle() == TRUE)
        gPartyMenuType = PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE;
    else
        gPartyMenuType = PARTY_MENU_LAYOUT_DOUBLE_BATTLE;

    arr = &gUnknown_083769A8[gPartyMenuType * 12];

    switch (monIndex)
    {
    case 0:
        memset(&gBGTilemapBuffers[2], 0, 0x800);
        break;
    case 1:
        sub_806B9A4(arr[0], arr[1], 3);
        sub_806BF24(&arr[0], 0, 3, 0);
        break;
    case 2:
        if (!IsDoubleBattle())
        {
            if (gPlayerPartyCount > 1)
            {
                sub_806BA94(arr[2], arr[3], 0, 3);
                sub_806BF24(&arr[2], 1, 3, 0);
            }
            else
            {
                sub_806BA94(arr[2], arr[3], 1, 3);
            }
        }
        else if (IsLinkDoubleBattle() == TRUE)
        {
            sub_806B9A4(arr[2], arr[3], 4);
            sub_806BF24(&arr[2], 1, 4, 0);
        }
        else
        {
            sub_806B9A4(arr[2], arr[3], 3);
            sub_806BF24(&arr[2], 1, 3, 0);
        }
        break;
    case 3:
        if (!IsDoubleBattle())
        {
            if (gPlayerPartyCount > 2)
            {
                sub_806BA94(arr[4], arr[5], 0, 3);
                sub_806BF24(&arr[4], 2, 3, 0);
            }
            else
            {
                sub_806BA94(arr[4], arr[5], 1, 3);
            }
        }
        else if (IsLinkDoubleBattle() == TRUE)
        {
            if (GetMonData(&gPlayerParty[2], MON_DATA_SPECIES))
            {
                sub_806BA94(arr[4], arr[5], 0, 3);
                sub_806BF24(&arr[4], 2, 3, 0);
            }
            else
            {
                sub_806BA94(arr[4], arr[5], 1, 3);
            }
        }
        else if (gPlayerPartyCount > 2)
        {
            sub_806BA94(arr[4], arr[5], 0, 3);
            sub_806BF24(&arr[4], 2, 3, 0);
        }
        else
        {
            sub_806BA94(arr[4], arr[5], 1, 3);
        }
        break;
    case 4:
        if (!IsDoubleBattle())
        {
            if (gPlayerPartyCount > 3)
            {
                sub_806BA94(arr[6], arr[7], 0, 3);
                sub_806BF24(&arr[6], 3, 3, 0);
            }
            else
            {
                sub_806BA94(arr[6], arr[7], 1, 3);
            }
        }
        else if (IsLinkDoubleBattle() == TRUE)
        {
            if (GetMonData(&gPlayerParty[3], MON_DATA_SPECIES))
            {
                sub_806BA94(arr[6], arr[7], 0, 3);
                sub_806BF24(&arr[6], 3, 3, 0);
            }
            else
            {
                sub_806BA94(arr[6], arr[7], 1, 3);
            }
        }
        else if (gPlayerPartyCount > 3)
        {
            sub_806BA94(arr[6], arr[7], 0, 3);
            sub_806BF24(&arr[6], 3, 3, 0);
        }
        else
        {
            sub_806BA94(arr[6], arr[7], 1, 3);
        }
        break;
    case 5:
        if (!IsDoubleBattle())
        {
            if (gPlayerPartyCount > 4)
            {
                sub_806BA94(arr[8], arr[9], 0, 3);
                sub_806BF24(&arr[8], 4, 3, 0);
            }
            else
            {
                sub_806BA94(arr[8], arr[9], 1, 3);
            }
        }
        else if (IsLinkDoubleBattle() == TRUE)
        {
            if (GetMonData(&gPlayerParty[4], MON_DATA_SPECIES))
            {
                sub_806BA94(arr[8], arr[9], 0, 4);
                sub_806BF24(&arr[8], 4, 4, 0);
            }
            else
            {
                sub_806BA94(arr[8], arr[9], 1, 4);
            }
        }
        else if (gPlayerPartyCount > 4)
        {
            sub_806BA94(arr[8], arr[9], 0, 3);
            sub_806BF24(&arr[8], 4, 3, 0);
        }
        else
        {
            sub_806BA94(arr[8], arr[9], 1, 3);
        }
        break;
    case 6:
        if (!IsDoubleBattle())
        {
            if (gPlayerPartyCount > 5)
            {
                sub_806BA94(arr[10], arr[11], 0, 3);
                sub_806BF24(&arr[10], 5, 3, 0);
            }
            else
            {
                sub_806BA94(arr[10], arr[11], 1, 3);
            }
        }
        else if (IsLinkDoubleBattle() == TRUE)
        {
            if (GetMonData(&gPlayerParty[5], MON_DATA_SPECIES))
            {
                sub_806BA94(arr[10], arr[11], 0, 4);
                sub_806BF24(&arr[10], 5, 4, 0);
            }
            else
            {
                sub_806BA94(arr[10], arr[11], 1, 4);
            }
        }
        else if (gPlayerPartyCount > 5)
        {
            sub_806BA94(arr[10], arr[11], 0, 3);
            sub_806BF24(&arr[10], 5, 3, 0);
        }
        else
        {
            sub_806BA94(arr[10], arr[11], 1, 3);
        }
        break;
    case 7:
        if (ePartyMenu2.menuType == PARTY_MENU_TYPE_BATTLE_TOWER)
            sub_806BB9C(1);
        sub_806BBEC(1);
        break;
    case 8:
        ePartyMenu2.unk261 = 2;
        return TRUE;
    }

    return FALSE;
}

void sub_806B908(void)
{
    const u8 * r4;
    struct Pokemon * pokemon;
    memset(&gBGTilemapBuffers[2], 0, 0x800);
    gPartyMenuType = 3;

    r4 = ({const u8 * r0 = gUnknown_083769A8; r0 + 36;});
    pokemon = &gPlayerParty[1];
    sub_806B9A4(r4[0], r4[1], 3);

    if (GetMonData(pokemon, MON_DATA_SPECIES))
        sub_806BA94(r4[4], r4[5], 0, 3);
    else
        sub_806BA94(r4[4], r4[5], 1, 3);

    if (GetMonData(pokemon + 1, MON_DATA_SPECIES))
        sub_806BA94(r4[6], r4[7], 0, 3);
    else
        sub_806BA94(r4[6], r4[7], 1, 3);

    ePartyMenu2.unk261 = 2;
}

void sub_806B9A4(s16 a, u16 b, u8 c)
{
    u8 i;
    u16 var1 = b * 32;

    for (i = 0; i <= 6; i++)
    {
        u8 j = 0;

        if (a <= 0x1F)
        {
            for (; j <= 10 && a + j <= 0x1F; j++)
            {
                if (a + j >= 0)
                    gBGTilemapBuffers[2][var1 + (i * 32) + (a + j)] = (c << 12) | gUnknown_083769D8[i * 11 + j];
            }
        }
    }
}

void sub_806BA34(s16 a, u16 b)
{
    u8 i;
    u16 var1 = b * 32;

    for (i = 0; i <= 6; i++)
    {
        u8 j = 0;

        if (a <= 0x1F)
        {
            for (; j <= 10 && a + j <= 0x1F; j++)
            {
                if (a + j >= 0)
                    gBGTilemapBuffers[2][var1 + (i * 32) + (a + j)] = 0;
            }
        }
    }
}

void sub_806BA94(s16 a, u16 b, u8 c, u8 d)
{
    u8 i;
    const u8 *arr;
    u16 var1;

    if (c == 0)
        arr = gUnknown_08376A25;
    else
        arr = gUnknown_08376A5E;

    var1 = b * 32;

    for (i = 0; i < 3; i++)
    {
        u8 j = 0;

        if (a <= 0x1F)
        {
            while (j <= 0x12 && a + j <= 0x1F)
            {
                if (a + j >= 0)
                {
                    gBGTilemapBuffers[2][var1 + (i * 32) + (a + j)] = (d << 12) | arr[i * 19 + j];
                }

                j++;
            }
        }
    }
}

void sub_806BB3C(s16 a, u16 b)
{
    u8 i;
    u16 var1 = (b * 32);

    for (i = 0; i < 3; i++)
    {
        u8 j = 0;

        if (a <= 0x1F)
        {
            for (; j <= 0x12 && a + j <= 0x1F; j++)
            {
                if (a + j >= 0)
                {
                    gBGTilemapBuffers[2][var1 + (i * 32) + (a + j)] = 0;
                }
            }
        }
    }
}

void sub_806BB9C(u8 a)
{
    u8 i;
    u16 arr[12] =
    {
        0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
        0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
    };
    u16 *vramPtr = (u16 *)(BG_VRAM + 0x3C30);

    for (i = 0; i < PARTY_SIZE; i++)
    {
        vramPtr[i] = arr[i] + (a << 12);
        vramPtr[i + 0x20] = arr[i + PARTY_SIZE] + (a << 12);
    }
}

void sub_806BBEC(u8 a)
{
    u8 i;
    u16 arr[12] =
    {
        0x2A, 0x0B, 0x0C, 0x0D, 0x0E, 0x2F,
        0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    };
    u16 *vramPtr = (u16 *)(BG_VRAM + 0x3CB0);

    for (i = 0; i < PARTY_SIZE; i++)
    {
        vramPtr[i] = arr[i] + (a << 12);
        vramPtr[i + 0x20] = arr[i + PARTY_SIZE] + (a << 12);
    }
}

// Draws the descriptor text in the mon's party bubble.
// Descriptor texts are things like "ABLE", "NOT ABLE", "LEARNED", etc.
void DrawMonDescriptorStatus(u8 monIndex, u8 descriptorOffset)
{
    u8 i;
    u32 offset;
    u16 *vramPtr = gUnknown_08376918[IsDoubleBattle()][monIndex];
    int paletteNum = 0;

    for (i = 0; i < 7; i++)
    {
        offset = i + (descriptorOffset / 7) * 32;
        vramPtr[i] = paletteNum | (gUnknown_08E9A300[offset] + 0x10C);
        vramPtr[i + 0x20] = paletteNum | (gUnknown_08E9A300[offset + 0x20] + 0x10C);
    }
}

void unref_sub_806BCB8(u8 descriptorOffset)
{
    u8 i;

    for (i = 0; i < gPlayerPartyCount; i++)
        DrawMonDescriptorStatus(i, descriptorOffset);
}

// This is ultimately unreferenced, since it's caller is unreferenced.
void sub_806BCE8()
{
    u8 i;

    for (i = 0; i < gPlayerPartyCount; i++)
    {
        if (!GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG))
        {
            switch (GetMonGender(&gPlayerParty[i]))
            {
            case MON_MALE:
                DrawMonDescriptorStatus(i, 0x54);
                break;
            case MON_FEMALE:
                DrawMonDescriptorStatus(i, 0x62);
                break;
            default:
                DrawMonDescriptorStatus(i, 0x46);
                break;
            }
        }
        else
        {
            DrawMonDescriptorStatus(i, 0x46);
        }
    }
}

u8 sub_806BD58(u8 taskId, u8 b)
{
    u8 spriteId = CreateInvisibleSpriteWithCallback(SpriteCallbackDummy);
    sub_806CA18(taskId, spriteId);
    return 1;
}

u16 HandleDefaultPartyMenuInput(u8 taskId)
{
    s8 menuDirectionPressed = 0x0;

    switch (gMain.newAndRepeatedKeys)
    {
    case DPAD_UP:
        menuDirectionPressed = -1;
        break;
    case DPAD_DOWN:
        menuDirectionPressed = 1;
        break;
    case DPAD_LEFT:
        menuDirectionPressed = -2;
        break;
    case DPAD_RIGHT:
        menuDirectionPressed = 2;
        break;
    }

    if (menuDirectionPressed == 0)
    {
        // Check L/R input
        u8 var1 = sub_80F92BC();
        switch (var1)
        {
        case 1:
            menuDirectionPressed = -1;
            break;
        case 2:
            menuDirectionPressed = 1;
            break;
        }
    }

    if (menuDirectionPressed != 0)
    {
        ChangePartyMenuSelection(taskId, menuDirectionPressed);
        return gMain.newAndRepeatedKeys;
    }
    else if ((gMain.newKeys & A_BUTTON) && gSprites[sub_806CA00(taskId)].data[0] == 7)
    {
        // Selected "CANCEL"
        return B_BUTTON;
    }
    else
    {
        return gMain.newKeys & (A_BUTTON | B_BUTTON);
    }
}

u16 HandleBattleTowerPartyMenuInput(u8 taskId)
{
    u8 menuDirectionPressed = 0x0;

    switch (gMain.newAndRepeatedKeys)
    {
    case DPAD_UP:
        menuDirectionPressed = 0xFF;
        break;
    case DPAD_DOWN:
        menuDirectionPressed = 0x1;
        break;
    case DPAD_LEFT:
        menuDirectionPressed = 0xFE;
        break;
    case DPAD_RIGHT:
        menuDirectionPressed = 0x2;
        break;
    }

    if (menuDirectionPressed == 0)
    {
        switch (sub_80F92BC())
        {
        case 1:
            menuDirectionPressed = 0xFF;
            break;
        case 2:
            menuDirectionPressed = 0x1;
            break;
        }
    }

    if (gMain.newKeys & START_BUTTON)
    {
        SelectBattleTowerOKButton(taskId);
        return START_BUTTON;
    }
    else
    {
        s8 signedMenuDirection = menuDirectionPressed;
        if (signedMenuDirection)
        {
            ChangeBattleTowerPartyMenuSelection(taskId, signedMenuDirection);
            return gMain.newAndRepeatedKeys;
        }
        else
        {
            if (gMain.newKeys & A_BUTTON)
            {
                if (gSprites[sub_806CA00(taskId)].data[0] == 7)
                    return B_BUTTON;
            }
        }
    }

    return gMain.newKeys & (A_BUTTON | B_BUTTON);
}

void task_pc_turn_off(const u8 *a, u8 b)
{
    if (a[0])
        sub_806BA94(a[0], a[1], 0, b);
    else
        sub_806B9A4(a[0], a[1], b);
}

void sub_806BF24(const u8 *a, u8 monIndex, u8 c, u8 d)
{
    if (GetMonData(&gPlayerParty[monIndex], MON_DATA_SPECIES) && GetMonData(&gPlayerParty[monIndex], MON_DATA_HP) == 0)
        c = PARTY_SIZE - 1;

    if (d == 1)
        c += 4;

    task_pc_turn_off(a, c);
}

void ChangePartyMenuSelection(u8 taskId, s8 directionPressed)
{
    bool8 isLinkDoubleBattle;
    u8 spriteId = sub_806CA00(taskId);
    u8 menuIndex = gSprites[spriteId].data[0];

    UpdateMonIconFrame_806DA44(taskId, menuIndex, 0);

    isLinkDoubleBattle = IsLinkDoubleBattle();
    if (isLinkDoubleBattle == 1)
    {
        if (menuIndex == 0 || menuIndex == 2 || menuIndex == 3)
            sub_806BF24(&gUnknown_083769C0[menuIndex * 2], menuIndex, 3, 0);
        if (menuIndex == 1 || menuIndex == 4 || menuIndex == 5)
            sub_806BF24(&gUnknown_083769C0[menuIndex * 2], menuIndex, 4, 0);
        if (menuIndex == 7)
            sub_806BBEC(1);

        ChangeLinkDoubleBattlePartyMenuSelection(spriteId, menuIndex, directionPressed);

        if (gSprites[spriteId].data[0] == 0 || gSprites[spriteId].data[0] == 2 || gSprites[spriteId].data[0] == 3)
            sub_806BF24(&gUnknown_083769C0[gSprites[spriteId].data[0] * 2], gSprites[spriteId].data[0], 3, 1);
        if (gSprites[spriteId].data[0] == 1 || gSprites[spriteId].data[0] == 4 || gSprites[spriteId].data[0] == 5)
                sub_806BF24(&gUnknown_083769C0[gSprites[spriteId].data[0] * 2], gSprites[spriteId].data[0], 4, 1);
        if (gSprites[spriteId].data[0] == 7)
            sub_806BBEC(2);

        ePartyMenu2.unk261 = 2;

        gSprites[spriteId].x = gUnknown_083768B8[PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE][gSprites[spriteId].data[0]].x;
        gSprites[spriteId].y = gUnknown_083768B8[PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE][gSprites[spriteId].data[0]].y;
    }
    else
    {
        u8 isDoubleBattle = IsDoubleBattle();

        if (menuIndex < PARTY_SIZE)
            sub_806BF24(&gUnknown_083769A8[isDoubleBattle * 12 + menuIndex * 2], menuIndex, 3, 0);
        else
            sub_806BBEC(1);

        if (!isDoubleBattle)
            ChangeDefaultPartyMenuSelection(spriteId, menuIndex, directionPressed);
        else
            ChangeDoubleBattlePartyMenuSelection(spriteId, menuIndex, directionPressed);

        if (gSprites[spriteId].data[0] < PARTY_SIZE)
            sub_806BF24(&gUnknown_083769A8[isDoubleBattle * 12 + gSprites[spriteId].data[0] * 2], gSprites[spriteId].data[0], 3, 1);
        else
            sub_806BBEC(2);

        ePartyMenu2.unk261 = 2;

        gSprites[spriteId].x = gUnknown_083768B8[isDoubleBattle][gSprites[spriteId].data[0]].x;
        gSprites[spriteId].y = gUnknown_083768B8[isDoubleBattle][gSprites[spriteId].data[0]].y;
    }

    UpdateMonIconFrame_806DA44(taskId, gSprites[spriteId].data[0], 1);

    if (menuIndex != gSprites[spriteId].data[0])
        PlaySE(SE_SELECT);
}

void ChangeDefaultPartyMenuSelection(u8 spriteId, u8 menuIndex, s8 directionPressed)
{
    u8 nextIndex;
    s8 menuMovement = directionPressed + 2;

    switch (menuMovement)
    {
    case 2: // no movement
        gSprites[spriteId].data[1] = 0;
        break;
    case 1: // moving up
        if (menuIndex == 0)
        {
            gSprites[spriteId].data[0] = 7;
        }
        else if (menuIndex == 7)
        {
            gSprites[spriteId].data[0] = gPlayerPartyCount - 1;
        }
        else
        {
            s8 diff = directionPressed;
            gSprites[spriteId].data[0] += diff;
        }
        gSprites[spriteId].data[1] = 0;
        break;
    case 3: // moving down
        if (menuIndex == gPlayerPartyCount - 1)
        {
            gSprites[spriteId].data[0] = 7;
        }
        else if (menuIndex == 7)
        {
            gSprites[spriteId].data[0] = 0;
        }
        else
        {
            s8 diff = directionPressed;
            gSprites[spriteId].data[0] += diff;
        }
        gSprites[spriteId].data[1] = 0;
        break;
    case 4: // moving right
        if (gPlayerPartyCount > 1 && menuIndex == 0)
        {
            if (gSprites[spriteId].data[1] == 0)
                gSprites[spriteId].data[1] = 1;
            gSprites[spriteId].data[0] = gSprites[spriteId].data[1];
        }
        break;
    case 0: // moving left
        // Only move the selection to the left side if one of the mons in the right-hand column are currently selected
        nextIndex = menuIndex - 1;
        if (nextIndex <= 4)
        {
            gSprites[spriteId].data[0] = 0;
            gSprites[spriteId].data[1] = menuIndex;
        }
        break;
    }
}

void ChangeDoubleBattlePartyMenuSelection(u8 spriteId, u8 menuIndex, s8 directionPressed)
{
    u8 var1;
    s8 menuMovement = directionPressed + 2;

    switch(menuMovement)
    {
    case 2: // no movement
        gSprites[spriteId].data[1] = 0;
        break;
    case 3: // moving down
        if (menuIndex == 7)
        {
            gSprites[spriteId].data[0] = 0;
        }
        else if (menuIndex == gPlayerPartyCount - 1)
        {
            gSprites[spriteId].data[0] = 7;
        }
        else
        {
            s8 diff = directionPressed;
            gSprites[spriteId].data[0] += diff;
        }
        gSprites[spriteId].data[1] = 0;
        break;
    case 1: // moving up
        if (menuIndex == 0)
        {
            gSprites[spriteId].data[0] = 7;
        }
        else if (menuIndex == 7)
        {
            gSprites[spriteId].data[0] = gPlayerPartyCount - 1;
        }
        else
        {
            s8 diff = directionPressed;
            gSprites[spriteId].data[0] += diff;
        }
        gSprites[spriteId].data[1] = 0;
        break;
    case 4: // moving right
        if (menuIndex == 0)
        {
            if (gPlayerPartyCount > 2)
            {
                u16 var1 = gSprites[spriteId].data[1] - 2;
                if (var1 > 1)
                    gSprites[spriteId].data[0] = 2;
                else
                    gSprites[spriteId].data[0] = gSprites[spriteId].data[1];
            }
        }
        else if (menuIndex == 1)
        {
            if (gPlayerPartyCount > 4)
            {
                u16 var1 = gSprites[spriteId].data[1] - 4;
                if (var1 <= 1)
                    gSprites[spriteId].data[0] = gSprites[spriteId].data[1];
                else
                    gSprites[spriteId].data[0] = 4;
            }
        }
        break;
    case 0: // moving left
        var1 = menuIndex - 2;
        if (var1 <= 1)
        {
            gSprites[spriteId].data[0] = 0;
            gSprites[spriteId].data[1] = menuIndex;
        }
        else
        {
            u8 var2 = menuIndex - 4;
            if (var2 <= 1)
            {
                gSprites[spriteId].data[0] = 1;
                gSprites[spriteId].data[1] = menuIndex;
            }
        }
        break;
    }
}

void ChangeLinkDoubleBattlePartyMenuSelection(u8 spriteId, u8 menuIndex, s8 directionPressed)
{
    switch (directionPressed)
    {
    case 0: // no movement
        gSprites[spriteId].data[1] = 0;
        break;
    case 1: // moving down
        if (menuIndex == 7) {
            gSprites[spriteId].data[0] = 0;
        }
        else
        {
            while (1)
            {
                if (menuIndex == PARTY_SIZE - 1)
                {
                    gSprites[spriteId].data[0] = 7;
                    break;
                }
                menuIndex++;
                if (GetMonData(&gPlayerParty[menuIndex], MON_DATA_SPECIES) != SPECIES_NONE)
                {
                    gSprites[spriteId].data[0] = menuIndex;
                    break;
                }
            }
        }
        gSprites[spriteId].data[1] = 0;
        break;
    case -1: // moving up
        while (1)
        {
            if (menuIndex == 0)
            {
                gSprites[spriteId].data[0] = 7;
                break;
            }
            menuIndex--;
            if (menuIndex != PARTY_SIZE && GetMonData(&gPlayerParty[menuIndex], MON_DATA_SPECIES) != SPECIES_NONE)
            {
                gSprites[spriteId].data[0] = menuIndex;
                break;
            }
        }

        gSprites[spriteId].data[1] = 0;
        break;
    case 2: // moving right
        if (menuIndex == 0) {
            if (gSprites[spriteId].data[1] < 2 || gSprites[spriteId].data[1] > 3) {
                if (GetMonData(&gPlayerParty[2], MON_DATA_SPECIES) != SPECIES_NONE) {
                    gSprites[spriteId].data[0] = 2;
                } else if (GetMonData(&gPlayerParty[3], MON_DATA_SPECIES) != SPECIES_NONE) {
                    gSprites[spriteId].data[0] = 3;
                }
            } else {
                gSprites[spriteId].data[0] = gSprites[spriteId].data[1];
            }
        } else if (menuIndex == 1) {
            if (gSprites[spriteId].data[1] >= 4 && gSprites[spriteId].data[1] <= 5) {
                gSprites[spriteId].data[0] = gSprites[spriteId].data[1];
            } else {
                if (GetMonData(&gPlayerParty[4], MON_DATA_SPECIES) != SPECIES_NONE) {
                    gSprites[spriteId].data[0] = 4;
                } else if (GetMonData(&gPlayerParty[5], MON_DATA_SPECIES) != SPECIES_NONE) {
                    gSprites[spriteId].data[0] = 5;
                }
            }
        }
        break;
    case -2: // moving left
        if (menuIndex > 1 && menuIndex < 4) {
            gSprites[spriteId].data[0] = 0;
            gSprites[spriteId].data[1] = menuIndex;
        } else {
            if (menuIndex > 3 && menuIndex < 6) {
                gSprites[spriteId].data[0] = 1;
                gSprites[spriteId].data[1] = menuIndex;
            }
        }
        break;
    }
}

void ChangeBattleTowerPartyMenuSelection(u8 taskId, s8 directionPressed)
{
    u16 newMenuIndex;
    u8 newMenuIndex2;
    u8 newMenuIndex3;
    s8 menuMovement;
    u8 spriteId = sub_806CA00(taskId);
    u8 menuIndex = gSprites[spriteId].data[0];

    UpdateMonIconFrame_806DA44(taskId, menuIndex, 0);

    if (menuIndex < PARTY_SIZE)
        sub_806BF24(&gUnknown_083769A8[menuIndex * 2], menuIndex, 3, 0);
    else if (menuIndex == PARTY_SIZE)
        sub_806BB9C(1);
    else
        sub_806BBEC(1);

    menuMovement = directionPressed + 2;
    switch (menuMovement)
    {
    case 2: // no movement
        gSprites[spriteId].data[1] = 0;
        break;
    case 1: // moving up
        if (menuIndex == 0)
            gSprites[spriteId].data[0] = 7;
        else if (menuIndex == PARTY_SIZE)
            gSprites[spriteId].data[0] = gPlayerPartyCount - 1;
        else
            gSprites[spriteId].data[0] += directionPressed;
        gSprites[spriteId].data[1] = 0;
        break;
    case 3: // moving down
        if (menuIndex == gPlayerPartyCount - 1)
            gSprites[spriteId].data[0] = 6;
        else if (menuIndex == 7)
            gSprites[spriteId].data[0] = 0;
        else
            gSprites[spriteId].data[0] += directionPressed;
        gSprites[spriteId].data[1] = 0;
        break;
    case 4: // moving right
        if (gPlayerPartyCount > 1 && menuIndex == 0)
        {
            if (gSprites[spriteId].data[1] == 0)
                gSprites[spriteId].data[1] = 1;
            gSprites[spriteId].data[0] = gSprites[spriteId].data[1];
        }
        break;
    case 0: // moving left
        newMenuIndex3 = menuIndex - 1;
        if (newMenuIndex3 <= 4)
        {
            gSprites[spriteId].data[0] = 0;
            gSprites[spriteId].data[1] = menuIndex;
        }
        break;
    }

    gSprites[spriteId].x = gUnknown_083768B8[PARTY_MENU_LAYOUT_STANDARD][gSprites[spriteId].data[0]].x;
    gSprites[spriteId].y = gUnknown_083768B8[PARTY_MENU_LAYOUT_STANDARD][gSprites[spriteId].data[0]].y;


    newMenuIndex = gSprites[spriteId].data[0];
    if (gSprites[spriteId].data[0] < PARTY_SIZE)
        sub_806BF24(&gUnknown_083769A8[gSprites[spriteId].data[0] * 2], newMenuIndex, 3, 1);
    else if (gSprites[spriteId].data[0] == PARTY_SIZE)
        sub_806BB9C(2);
    else
        sub_806BBEC(2);

    ePartyMenu2.unk261 = 2;

    newMenuIndex2 = gSprites[spriteId].data[0];
    UpdateMonIconFrame_806DA44(taskId, newMenuIndex2, 1);

    if (menuIndex != gSprites[spriteId].data[0])
        PlaySE(SE_SELECT);
}

// Selects the "OK" button in the Battle Tower party menu.
void SelectBattleTowerOKButton(u8 taskId)
{
    u8 spriteId = sub_806CA00(taskId);

    u8 menuIndex = gSprites[spriteId].data[0];
    if (menuIndex != 6)
    {
        UpdateMonIconFrame_806DA44(taskId, menuIndex, 0);

        if (menuIndex < PARTY_SIZE)
            sub_806BF24(&gUnknown_083769A8[menuIndex * 2], menuIndex, 3, 0);
        else
            sub_806BBEC(1);

        gSprites[spriteId].data[1] = 0;
        gSprites[spriteId].data[0] = 6;
        gSprites[spriteId].x = gUnknown_083768B8[PARTY_MENU_LAYOUT_STANDARD][6].x;
        gSprites[spriteId].y = gUnknown_083768B8[PARTY_MENU_LAYOUT_STANDARD][6].y;

        sub_806BB9C(2);

        ePartyMenu2.unk261 = 2;
        PlaySE(SE_SELECT);
    }
}

void sub_806C92C(u8 spriteId)
{
    u8 menuIndex1 = gSprites[spriteId].data[0];
    u8 menuIndex2 = gSprites[spriteId].data[1];

    if (!IsDoubleBattle())
    {
        if (menuIndex1 < 1)
        {
            if (menuIndex2 < 1)
                menuIndex2 = 1;
        }
        else
        {
            if (menuIndex2 >= 1)
                menuIndex2 = 0;
        }
    }
    else
    {
        if (menuIndex1 < 2)
        {
            if (menuIndex2 < 2)
                menuIndex2 = 2;
        }
        else
        {
            if (menuIndex2 >= 2)
                menuIndex2 = 0;
        }
    }

    gSprites[spriteId].data[1] = menuIndex2;
}

void sub_806C994(u8 taskId, u8 b)
{
    u8 spriteId = sub_806CA00(taskId);

    gSprites[spriteId].data[0] = b;
    sub_806C92C(spriteId);
}

void sub_806C9C4(u8 taskId, u8 spriteId)
{
    u8 spriteId2 = sub_806CA00(taskId);

    gSprites[spriteId].x = gSprites[spriteId2].x;
    gSprites[spriteId].y = gSprites[spriteId2].y;
    gSprites[spriteId].data[0] = gSprites[spriteId2].data[0];
}

u8 sub_806CA00(u8 taskId)
{
    return gTasks[taskId].data[3] >> 8;
}

void sub_806CA18(u8 taskId, u8 b)
{
    u8 var1 = gTasks[taskId].data[3];
    gTasks[taskId].data[3] = var1 | (b << 8);
}

u8 sub_806CA38(u8 taskId)
{
    u8 spriteId = sub_806CA00(taskId);
    return gSprites[spriteId].data[0];
}

void SetupDefaultPartyMenuSwitchPokemon(u8 taskId)
{
    gTasks[taskId].func = TaskDummy;
    ePartyMenu.unk0 = taskId;

    CreateTask(HandlePartyMenuSwitchPokemonInput, 0);
    ePartyMenu.slotId = CreateInvisibleSpriteWithCallback(SpriteCallbackDummy);

    sub_806C9C4(taskId, ePartyMenu.slotId);
    ePartyMenu.slotId2 = sub_806CA00(taskId);

    PrintPartyMenuPromptText(ePartyMenu2.pmUnk272, 0);

    sub_806BF24(&gUnknown_083769A8[gSprites[ePartyMenu.slotId].data[0] * 2], gSprites[ePartyMenu.slotId].data[0], 6, 0);
    ePartyMenu2.unk261 = 2;
}

// Handles changing the the current mon selection when choosing mons to swap places.
void ChangePartyMenuSwitchPokemonSelection(u8 taskId, s16 menuDirectionPressed)
{
    struct Sprite *sprite1 = &gSprites[ePartyMenu.slotId];
    struct Sprite *sprite2 = &gSprites[ePartyMenu.slotId2];

    ChangePartyMenuSelection(taskId, menuDirectionPressed);

    if (sprite1->data[0] != sprite2->data[0])
        sub_806BF24(&gUnknown_083769A8[sprite1->data[0] * 2], sprite1->data[0], 6, 1);
    else
        sub_806BF24(&gUnknown_083769A8[sprite1->data[0] * 2], sprite1->data[0], 6, 0);
}

void HandlePartyMenuSwitchPokemonInput(u8 taskId)
{
    switch (gMain.newAndRepeatedKeys)
    {
    case DPAD_UP:
        ChangePartyMenuSwitchPokemonSelection(ePartyMenu.unk0, -1);
        break;
    case DPAD_DOWN:
        ChangePartyMenuSwitchPokemonSelection(ePartyMenu.unk0, 1);
        break;
    case DPAD_LEFT:
        ChangePartyMenuSwitchPokemonSelection(ePartyMenu.unk0, -2);
        break;
    case DPAD_RIGHT:
        ChangePartyMenuSwitchPokemonSelection(ePartyMenu.unk0, 2);
        break;
    }

    if (gMain.newKeys & A_BUTTON)
    {
        PlaySE(5);
        gTasks[taskId].func = ePartyMenu.unkC;
        gTasks[taskId].func(taskId);
    }
    else if (gMain.newKeys == B_BUTTON)
    {
        PlaySE(5);
        sub_806CD44(taskId);
    }
}

void sub_806CC2C(u8 taskId)
{
    DestroySprite(&gSprites[ePartyMenu.slotId]);
    PrintPartyMenuPromptText(0, 0);

    ePartyMenu2.unk261 = 2;
    SwitchTaskToFollowupFunc(ePartyMenu.unk0);
    DestroyTask(taskId);
}

void sub_806CC74(u8 taskId)
{
    sub_806BF24(&gUnknown_083769A8[gSprites[ePartyMenu.slotId2].data[0] * 2], gSprites[ePartyMenu.slotId2].data[0], 3, 1);
    sub_806BF24(&gUnknown_083769A8[gSprites[ePartyMenu.slotId].data[0] * 2], gSprites[ePartyMenu.slotId].data[0], 3, 0);
    sub_806CC2C(taskId);
}

void sub_806CCE4()
{
    u8 monIndex1 = gSprites[ePartyMenu.slotId].data[0];
    u8 monIndex2 = gSprites[ePartyMenu.slotId2].data[0];

    if (monIndex1 <= 5)
        sub_806BF24(&gUnknown_083769A8[monIndex1 * 2], monIndex1, 3, 0);
    if (monIndex2 <= 5)
        sub_806BF24(&gUnknown_083769A8[monIndex2 * 2], monIndex2, 3, 1);
}

void sub_806CD44(u8 taskId)
{
    sub_806CCE4();
    sub_806CC2C(taskId);
}

void sub_806CD5C(u8 taskId)
{
    u8 monIndex1 = gSprites[ePartyMenu.slotId].data[0];
    u8 monIndex2 = gSprites[ePartyMenu.slotId2].data[0];

    if (monIndex1 == monIndex2 || monIndex1 == 7 || monIndex2 == 7)
    {
        sub_806CD44(taskId);
    }
    else
    {
        u8 var1;

        sub_806D5B8(monIndex1);
        sub_806D5B8(monIndex2);

        if (monIndex1 > monIndex2)
        {
            ePartyMenu.unk5 = monIndex2;
            ePartyMenu.unk6 = monIndex1;
        }
        else
        {
            ePartyMenu.unk5 = monIndex1;
            ePartyMenu.unk6 = monIndex2;
        }

        ePartyMenu.unk3 = GetMonIconSpriteId(ePartyMenu.unk0, ePartyMenu.unk5);
        ePartyMenu.unk4 = GetMonIconSpriteId(ePartyMenu.unk0, ePartyMenu.unk6);

        var1 = ePartyMenu.unk5;
        if (!var1)
        {
            gSprites[ePartyMenu.unk3].data[0] = -8;
            gSprites[ePartyMenu.unk3].data[2] = -0xA8;
            ePartyMenu.unk8 = var1;
            ePartyMenu.unkA = 11;

            gTasks[taskId].func = sub_806D014;
            ePartyMenu2.unk261 = 1;
        }
        else
        {
            gSprites[ePartyMenu.unk3].data[0] = 8;
            gSprites[ePartyMenu.unk3].data[2] = 0xA8;
            ePartyMenu.unk8 = 11;
            ePartyMenu.unkA = 11;

            gTasks[taskId].func = sub_806D118;
            ePartyMenu2.unk261 = 1;
        }

        gSprites[ePartyMenu.unk3].callback = SpriteCB_sub_806D37C;

        gSprites[ePartyMenu.unk4].data[0] = 8;
        gSprites[ePartyMenu.unk4].data[2] = 0xA8;
        gSprites[ePartyMenu.unk4].callback = SpriteCB_sub_806D37C;

        gSprites[ePartyMenu.unk3].callback(&gSprites[ePartyMenu.unk3]);
        gSprites[ePartyMenu.unk4].callback(&gSprites[ePartyMenu.unk4]);
    }
}

void SwapValues_s16(s16 *a, s16 *b)
{
    s16 temp = *a;
    *a = *b;
    *b = temp;
}

void sub_806CF04(void)
{
    SwapValues_s16(&gSprites[ePartyMenu.unk3].x, &gSprites[ePartyMenu.unk4].x);
    SwapValues_s16(&gSprites[ePartyMenu.unk3].y, &gSprites[ePartyMenu.unk4].y);
    SwapValues_s16(&gSprites[ePartyMenu.unk3].x2, &gSprites[ePartyMenu.unk4].x2);
    SwapValues_s16(&gSprites[ePartyMenu.unk3].data[0], &gSprites[ePartyMenu.unk4].data[0]);

    gSprites[ePartyMenu.unk3].callback = SpriteCB_sub_806D37C;
    gSprites[ePartyMenu.unk4].callback = SpriteCB_sub_806D37C;
}

void sub_806CFA0(u8 taskId, u8 b)
{
    u8 var1 = ((ePartyMenu.unk6 - 1) * 3) + 1;

    sub_806BA34(ePartyMenu.unk8, 3);
    sub_806BB3C(ePartyMenu.unkA, var1);

    if (!b)
    {
        ePartyMenu.unk8--;
        ePartyMenu.unkA++;
    }
    else
    {
        ePartyMenu.unk8++;
        ePartyMenu.unkA--;
    }

    sub_806B9A4(ePartyMenu.unk8, 3, 10);
    sub_806BA94(ePartyMenu.unkA, var1, 0, 10);
}

void sub_806D014(u8 taskId)
{
    sub_806CFA0(taskId, 0);

    if (ePartyMenu.unk8 < -13 && ePartyMenu.unkA > 32)
    {
        sub_806CF04();
        gTasks[taskId].func = sub_806D05C;
    }
}

void sub_806D05C(u8 taskId)
{
    sub_806CFA0(taskId, 1);

    if (ePartyMenu.unk8 == 0 && ePartyMenu.unkA == 11)
        gTasks[taskId].func = sub_806D198;
}

void sub_806D098(u8 a, u8 b)
{
    u8 var1 = ((ePartyMenu.unk5 - 1) * 3) + 1;
    u8 var2 = ((ePartyMenu.unk6 - 1) * 3) + 1;

    sub_806BB3C(ePartyMenu.unk8, var1);
    sub_806BB3C(ePartyMenu.unkA, var2);

    if (!b)
    {
        ePartyMenu.unk8++;
        ePartyMenu.unkA++;
    }
    else
    {
        ePartyMenu.unk8--;
        ePartyMenu.unkA--;
    }

    sub_806BA94(ePartyMenu.unk8, var1, 0, 10);
    sub_806BA94(ePartyMenu.unkA, var2, 0, 10);
}

void sub_806D118(u8 taskId)
{
    sub_806D098(taskId, 0);

    if (ePartyMenu.unk8 > 32 && ePartyMenu.unkA > 32)
    {
        sub_806CF04();
        gTasks[taskId].func = sub_806D15C;
    }
}

void sub_806D15C(u8 taskId)
{
    sub_806D098(taskId, 1);

    if (ePartyMenu.unk8 == 11 && ePartyMenu.unkA == 11)
        gTasks[taskId].func = sub_806D198;
}

void sub_806D198(u8 taskId)
{
    u8 spriteId;

    SetMonIconSpriteId(ePartyMenu.unk0, ePartyMenu.unk5, ePartyMenu.unk4);
    SetMonIconSpriteId(ePartyMenu.unk0, ePartyMenu.unk6, ePartyMenu.unk3);

    gSprites[ePartyMenu.unk3].x = gUnknown_08376678[IsDoubleBattle()][ePartyMenu.unk6].x;
    gSprites[ePartyMenu.unk3].y = gUnknown_08376678[IsDoubleBattle()][ePartyMenu.unk6].y;
    gSprites[ePartyMenu.unk3].x2 = 0;
    gSprites[ePartyMenu.unk3].y2 = 0;
    gSprites[ePartyMenu.unk3].callback = UpdateMonIconFrame_806DA38;

    gSprites[ePartyMenu.unk4].x = gUnknown_08376678[IsDoubleBattle()][ePartyMenu.unk5].x;
    gSprites[ePartyMenu.unk4].y = gUnknown_08376678[IsDoubleBattle()][ePartyMenu.unk5].y;
    gSprites[ePartyMenu.unk4].x2 = 0;
    gSprites[ePartyMenu.unk4].y2 = 0;
    gSprites[ePartyMenu.unk4].callback = UpdateMonIconFrame_806DA38;

    spriteId = GetMonIconSpriteId(ePartyMenu.unk0, gSprites[ePartyMenu.slotId2].data[0]);
    gSprites[spriteId].callback = UpdateMonIconFrame_806DA0C;

    SwapPokemon(&gPlayerParty[ePartyMenu.unk5], &gPlayerParty[ePartyMenu.unk6]);

    PartyMenuPrintMonLevelOrStatus(ePartyMenu.unk5, &gPlayerParty[ePartyMenu.unk5]);
    TryPrintPartyMenuMonNickname(ePartyMenu.unk5, &gPlayerParty[ePartyMenu.unk5]);
    PartyMenuTryPrintHP(ePartyMenu.unk5, &gPlayerParty[ePartyMenu.unk5]);
    nullsub_12(ePartyMenu.unk5, &gPlayerParty[ePartyMenu.unk5]);

    PartyMenuPrintMonLevelOrStatus(ePartyMenu.unk6, &gPlayerParty[ePartyMenu.unk6]);
    TryPrintPartyMenuMonNickname(ePartyMenu.unk6, &gPlayerParty[ePartyMenu.unk6]);
    PartyMenuTryPrintHP(ePartyMenu.unk6, &gPlayerParty[ePartyMenu.unk6]);
    nullsub_12(ePartyMenu.unk6, &gPlayerParty[ePartyMenu.unk6]);

    PartyMenuDrawHPBars();
    sub_806CC74(taskId);
}

void SpriteCB_sub_806D37C(struct Sprite *sprite)
{
    UpdateMonIconFrame(sprite);

    if (sprite->x2 == sprite->data[2])
    {
        sprite->data[0] *= -1;
        sprite->data[2] = 0;
        sprite->callback = UpdateMonIconFrame_806DA38;
    }
    else
    {
        sprite->x2 += sprite->data[0];
    }
}

void sub_806D3B4(u8 taskId, u16 species1, u16 species2)
{
    const u8 *var1 = gUnknown_083769C0;

    sub_806BA34(var1[2] + gTasks[taskId].data[0], var1[3]);
    sub_806BB3C(var1[8] + gTasks[taskId].data[0], var1[9]);
    sub_806BB3C(var1[10] + gTasks[taskId].data[0], var1[11]);

    gTasks[taskId].data[0]--;

    sub_806B9A4(var1[2] + gTasks[taskId].data[0], var1[3], 4);

    if (species1)
        sub_806BA94(var1[8] + gTasks[taskId].data[0], var1[9], 0, 4);
    else
        sub_806BA94(var1[8] + gTasks[taskId].data[0], var1[9], 1, 4);

    if (species2)
        sub_806BA94(var1[10] + gTasks[taskId].data[0], var1[11], 0, 4);
    else
        sub_806BA94(var1[10] + gTasks[taskId].data[0], var1[11], 1, 4);
}

void sub_806D4AC(u8 taskId, u16 species, u8 c)
{
    if (species)
    {
        u8 monIndex = c + 3;
        u8 spriteId = GetMonIconSpriteId(taskId, monIndex);

        gSprites[spriteId].data[0] = -8;
        gSprites[spriteId].data[2] = gTasks[taskId].data[0] * -8;
        gSprites[spriteId].callback = SpriteCB_sub_806D37C;
    }
}

void sub_806D50C(u8 taskId, u8 monIndex)
{
    gSprites[GetMonIconSpriteId(taskId, monIndex)].x += 0xF0;
}

void PrintPartyMenuPromptText(u8 textId, u8 b)
{
    if (textId != 0xFF)
    {
        switch (b)
        {
        case 0:
            Menu_DrawStdWindowFrame(0, 16, 23, 19);
            break;
        case 1:
            Menu_DrawStdWindowFrame(0, 16, 19, 19);
            break;
        case 2:
            Menu_DrawStdWindowFrame(0, 16, 22, 19);
            break;
        case 3:
            Menu_DrawStdWindowFrame(0, 16, 18, 19);
            break;
        }

        Menu_PrintText(PartyMenuPromptTexts[textId], 1, 17);
    }
}

void PartyMenuEraseMsgBoxAndFrame(void)
{
    Menu_EraseWindowRect(0, 16, 29, 19);
}

void sub_806D5B8(u8 monIndex)
{
    u8 left = gUnknown_08376948[IsDoubleBattle()][monIndex].left;
    u8 top = gUnknown_08376948[IsDoubleBattle()][monIndex].top;
    u8 right = gUnknown_08376948[IsDoubleBattle()][monIndex].right;
    u8 bottom = gUnknown_08376948[IsDoubleBattle()][monIndex].bottom;

    Text_EraseWindowRect(&gWindowTemplate_Contest_MoveDescription, left, top, right, bottom);

    CpuFastFill(0, OBJ_VRAM1 + monIndex * 0x400, 0x400);
}

void sub_806D668(u8 monIndex)
{
    u8 left = gUnknown_08376978[IsDoubleBattle()][monIndex].left;
    u8 top = gUnknown_08376978[IsDoubleBattle()][monIndex].top;
    u8 right = gUnknown_08376978[IsDoubleBattle()][monIndex].right;
    u8 bottom = gUnknown_08376978[IsDoubleBattle()][monIndex].bottom;

    Text_EraseWindowRect(&gWindowTemplate_Contest_MoveDescription, left, top, right, bottom);

    CpuFastFill(0, OBJ_VRAM1 + 0x300 + monIndex * 0x400, 0x100);
}

bool8 LoadPartyMenuGraphics(u8 a)
{
    u16 palette = 0x7FFF;
    bool8 retVal = FALSE;

    if (a < 2)
        LZDecompressVram(gPartyMenuMisc_Gfx, (void *)BG_VRAM);

    if (a == 2 || a == 0)
        LZDecompressVram(gPartyMenuMisc_Tilemap, (void *)(BG_VRAM + 0x3800));

    if (a == 3 || a == 0)
        LoadCompressedPalette(gPartyMenuMisc_Pal, 0, 0x160);

    if (a == 4 || a == 0)
    {
        LoadPalette(&palette, 0, 2);
        LoadPalette(gFontDefaultPalette, 0xF0, 0x20);
    }

    if (a == 5 || a == 0)
        LZDecompressVram(gPartyMenuHpBar_Gfx, (void *)(BG_VRAM + 0x6000));

    if (a == 6 || a == 0)
        LZDecompressVram(gPartyMenuOrderText_Gfx, (void *)(BG_VRAM + 0x6180));

    if (a == 7 || a == 0)
        LZDecompressVram(gStatusGfx_Icons, (void *)(BG_VRAM + 0x7180));

    if (a == 8 || a == 0)
    {
        LoadCompressedPalette(gStatusPal_Icons, 0xB0, 0x20);
        retVal = TRUE;
    }

    return retVal;
}

void SetMonIconAnimByHP(u8 spriteId, u16 currentHP, u16 maxHP)
{
    u8 hpBarLevel = GetHPBarLevel(currentHP, maxHP);
    u8 animNum = 0;

    if (currentHP != maxHP)
    {
        if (hpBarLevel == 3)
            animNum = 1;
        else if (hpBarLevel == 2)
            animNum = 2;
        else if (hpBarLevel == 1)
            animNum = 3;
        else
            animNum = 4;
    }

    SetPartyHPBarSprite(&gSprites[spriteId], animNum);
}

void SetMonIconAnim(u8 spriteId, struct Pokemon *pokemon)
{
    u16 currentHP = GetMonData(pokemon, MON_DATA_HP);
    u16 maxHP = GetMonData(pokemon, MON_DATA_MAX_HP);

    SetMonIconAnimByHP(spriteId, currentHP, maxHP);
}

void CreatePartyMenuMonIcon(u8 taskId, u8 monIndex, u8 menuType, struct Pokemon *pokemon)
{
    u8 x = gUnknown_08376678[menuType][monIndex].x;
    u8 y = gUnknown_08376678[menuType][monIndex].y;

    u16 species2 = GetMonData(pokemon, MON_DATA_SPECIES2);
    u32 personality = GetMonData(pokemon, MON_DATA_PERSONALITY);

    u8 spriteId = CreateMonIcon(species2, SpriteCB_PokemonIcon, x, y, 5, personality);
    SetMonIconSpriteId(taskId, monIndex, spriteId);
    SetMonIconAnim(spriteId, pokemon);
}

void TryCreatePartyMenuMonIcon(u8 taskId, u8 monIndex, struct Pokemon *pokemon)
{
    if (GetMonData(pokemon, MON_DATA_SPECIES))
    {
        if (IsLinkDoubleBattle() == TRUE)
            CreatePartyMenuMonIcon(taskId, monIndex, PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE, pokemon);
        else
            CreatePartyMenuMonIcon(taskId, monIndex, IsDoubleBattle(), pokemon);
    }
}

void unref_sub_806D964(u8 taskId)
{
    u8 i;

    for (i = 0; i < gPlayerPartyCount; i++)
        TryCreatePartyMenuMonIcon(taskId, i, &gPlayerParty[i]);
}

void CreateMonIcon_LinkMultiBattle(u8 taskId, u8 monIndex, u8 menuType, struct MultiBattlePokemonTx *pokemon)
{
    u8 x = gUnknown_08376678[menuType][monIndex].x;
    u8 y = gUnknown_08376678[menuType][monIndex].y;

    u8 spriteId = CreateMonIcon(pokemon->species, SpriteCB_PokemonIcon, x, y, 5, pokemon->personality);
    SetMonIconSpriteId(taskId, monIndex, spriteId);
    SetMonIconAnimByHP(spriteId, pokemon->hp, pokemon->maxhp);
}

void UpdateMonIconFrame_806DA0C(struct Sprite *sprite)
{
    u8 var1 = UpdateMonIconFrame(sprite);

    if (var1)
    {
        if (var1 & 1)
            sprite->y2 = -3;
        else
            sprite->y2 = 1;
    }
}

void UpdateMonIconFrame_806DA38(struct Sprite *sprite)
{
    UpdateMonIconFrame(sprite);
}

void UpdateMonIconFrame_806DA44(u8 taskId, u8 monIndex, u8 c)
{
    u8 spriteId;

    if (monIndex < PARTY_SIZE)
    {
        spriteId = GetMonIconSpriteId(taskId, monIndex);
        gSprites[spriteId].y2 = 0;
        gSprites[spriteId].data[0] = 0;

        if (!c)
            gSprites[spriteId].callback = UpdateMonIconFrame_806DA38;
        else
            gSprites[spriteId].callback = UpdateMonIconFrame_806DA0C;
    }
}

void LoadHeldItemIconGraphics(void)
{
    LoadSpriteSheet(&HeldItemsSpriteSheet);
    LoadSpritePalette(&HeldItemsPalette);
}

void SpriteCB_HeldItemIcon(struct Sprite *sprite)
{
    u8 data7 = sprite->data[7];

    if (gSprites[data7].invisible)
    {
        sprite->invisible = TRUE;
    }
    else
    {
        sprite->invisible = FALSE;
        sprite->x = gSprites[data7].x + gSprites[data7].x2;
        sprite->y = gSprites[data7].y + gSprites[data7].y2;
    }
}

void CreateHeldItemIcon(u8 a, u8 b)
{
    u8 subPriority;
    u8 spriteId;

    subPriority = gSprites[a].subpriority;
    spriteId = CreateSprite(&gSpriteTemplate_837660C, 0xFA, 0xAA, subPriority - 1);

    gSprites[spriteId].x2 = 4;
    gSprites[spriteId].y2 = 10;
    gSprites[spriteId].callback = SpriteCB_HeldItemIcon;
    gSprites[spriteId].data[7] = a;

    StartSpriteAnim(&gSprites[spriteId], b);
    gSprites[spriteId].callback(&gSprites[spriteId]);
}

void CreateHeldItemIcons(u8 *a, u8 *b, u8 c)
{
    u16 i;
    u16 heldItem;

    switch (c)
    {
    case 0:
        for (i = 0; i < a[0]; i++)
        {
            heldItem = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM);
            if (heldItem)
                CreateHeldItemIcon(b[i], ItemIsMail(heldItem));
        }
        break;
    case 1:
        for (i = 0; i < a[1]; i++)
        {
            heldItem = GetMonData(&gEnemyParty[i], MON_DATA_HELD_ITEM);
            if (heldItem)
                CreateHeldItemIcon(b[i + 6], ItemIsMail(heldItem));
        }
        break;
    }
}

void CreateHeldItemIcons_806DC34(u8 taskId)
{
    u8 i;
    u8 monIconSpriteId;
    u8 heldItemSpriteId;

    for (i = 0; i < PARTY_SIZE; i++)
    {
        if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES))
        {
            monIconSpriteId = GetMonIconSpriteId(taskId, i);
            heldItemSpriteId = CreateSprite(&gSpriteTemplate_837660C, 0xFA, 0xAA, 4);

            gSprites[heldItemSpriteId].x2 = 4;
            gSprites[heldItemSpriteId].y2 = 10;
            gSprites[heldItemSpriteId].data[7] = monIconSpriteId;
            gSprites[monIconSpriteId].data[7] = heldItemSpriteId;

            SetHeldItemIconVisibility(taskId, i);
            gSprites[heldItemSpriteId].callback(&gSprites[heldItemSpriteId]);
        }
    }
}

void CreateHeldItemIcon_806DCD4(u8 taskId, u8 monIndex, u16 item)
{
    u8 monIconSpriteId;
    u8 heldItemSpriteId;
    struct Sprite * r1;
    struct Sprite * heldItemSprite;

    monIconSpriteId = GetMonIconSpriteId(taskId, monIndex);
    heldItemSpriteId = CreateSprite(&gSpriteTemplate_837660C, 0xFA, 0xAA, 4);

    r1 = gSprites;
    heldItemSprite = r1 + heldItemSpriteId;
    heldItemSprite->x2 = 4;
    heldItemSprite->y2 = 10;
    heldItemSprite->data[7] = monIconSpriteId;
    gSprites[monIconSpriteId].data[7] = heldItemSpriteId;

    if (item == ITEM_NONE)
    {
        heldItemSprite->invisible = TRUE;
    }
    else if (ItemIsMail(item))
    {
        StartSpriteAnim(heldItemSprite, 1);
        heldItemSprite->invisible = FALSE;
    }
    else
    {
        StartSpriteAnim(heldItemSprite, 0);
        heldItemSprite->invisible = FALSE;
    }

    gSprites[heldItemSpriteId].callback(&gSprites[heldItemSpriteId]);
}

void SpriteCB_UpdateHeldItemIconPosition(struct Sprite *sprite)
{
    u8 spriteId = sprite->data[7];

    sprite->x = gSprites[spriteId].x + gSprites[spriteId].x2;
    sprite->y = gSprites[spriteId].y;
}

// Soft casting of gTasks[taskId].data to u8* but big-endian.
u8 GetMonIconSpriteId(u8 taskId, u8 monIndex)
{
    switch (monIndex)
    {
    case 1:
        return gTasks[taskId].data[0];
        break;
    case 2:
        return gTasks[taskId].data[1] >> 8;
        break;
    case 3:
        return gTasks[taskId].data[1];
        break;
    case 4:
        return gTasks[taskId].data[2] >> 8;
        break;
    case 5:
        return gTasks[taskId].data[2];
        break;
    case 0:
    default:
        return gTasks[taskId].data[0] >> 8;
        break;
    }
}

void SetMonIconSpriteId(u8 taskId, u8 monIndex, u8 spriteId)
{
    switch (monIndex)
    {
    case 0:
        gTasks[taskId].data[0] = (u8)gTasks[taskId].data[0] | (spriteId << 8);
        break;
    case 1:
        gTasks[taskId].data[0] = (gTasks[taskId].data[0] & ~0xFF) | spriteId;
        break;
    case 2:
        gTasks[taskId].data[1] = (u8)gTasks[taskId].data[1] | (spriteId << 8);
        break;
    case 3:
        gTasks[taskId].data[1] = (gTasks[taskId].data[1] & ~0xFF) | spriteId;
        break;
    case 4:
        gTasks[taskId].data[2] = (u8)gTasks[taskId].data[2] | (spriteId << 8);
        break;
    case 5:
        gTasks[taskId].data[2] = (gTasks[taskId].data[2] & ~0xFF) | spriteId;
        break;
    }
}

u16 GetMonHeldItemIconSpriteId(u8 taskId, u8 monIndex)
{
    u8 spriteId = GetMonIconSpriteId(taskId, monIndex);
    u8 retVal = gSprites[spriteId].data[7];
    return retVal;
}

// #ifdef NONMATCHING (for grep)
void SetHeldItemIconVisibility(u8 taskId, u8 monIndex)
{
    u8 spriteId;
    u16 heldItem;

    spriteId = GetMonHeldItemIconSpriteId(taskId, monIndex);
    if (!GetMonData(&gPlayerParty[monIndex], MON_DATA_HELD_ITEM))
    {
        gSprites[spriteId].invisible = TRUE;
    }
    else
    {
        register struct Sprite *sprite asm("r4");
// sprite2 is required to mimic a failed optimization where r0 would have been loaded at the end of the if statement
        register struct Sprite *sprite2 asm("r0");
        u8 animNum;
        heldItem = GetMonData(&gPlayerParty[monIndex], MON_DATA_HELD_ITEM);
        if (ItemIsMail(heldItem))
        {
            sprite = &gSprites[spriteId];
            sprite2 = sprite; // hack
            animNum = 1;
        }
        else
        {
            sprite = &gSprites[spriteId];
            sprite2 = sprite; // hack
            animNum = 0;
        }
        StartSpriteAnim(sprite2, animNum);
        sprite->invisible = FALSE;
    }
}

void PartyMenuDoPrintMonNickname(u8 monIndex, int b, const u8 *nameBuffer)
{
    CpuFastFill(0, gTileBufferPtr, 0x400);
    Text_InitWindow8004E3C((struct WindowTemplate *)&gWindowTemplate_81E6CAC, gTileBufferPtr, nameBuffer);
    CpuFastCopy(gTileBufferPtr, OBJ_VRAM1 + (monIndex * 0x400), 0x200);
}

void PrintPartyMenuMonNickname(u8 monIndex, u8 b, struct Pokemon *pokemon)
{
    u8 nameBuffer[12];
    GetMonNickname(pokemon, nameBuffer);
    PartyMenuDoPrintMonNickname(monIndex, b, nameBuffer);
}

void TryPrintPartyMenuMonNickname(u8 monIndex, struct Pokemon *pokemon)
{
    if (GetMonData(pokemon, MON_DATA_SPECIES))
    {
        if (IsLinkDoubleBattle() == TRUE)
            PrintPartyMenuMonNickname(monIndex, PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE, pokemon);
        else
            PrintPartyMenuMonNickname(monIndex, IsDoubleBattle(), pokemon);
    }
}

void PrintPartyMenuMonNicknames(void)
{
    u8 i;

    for (i = 0; i < PARTY_SIZE; i++)
        TryPrintPartyMenuMonNickname(i, &gPlayerParty[i]);
}

u8 *GetMonNickname(struct Pokemon *pokemon, u8 *stringBuffer)
{
    GetMonData(pokemon, MON_DATA_NICKNAME, stringBuffer);
    return StringGetEnd10(stringBuffer);
}

void PartyMenuPutStatusTilemap(u8 monIndex, u8 menuLayout, u8 status)
{
    u8 i;
    u8 x = gUnknown_08376738[menuLayout][monIndex].x - 1;
    u8 y = gUnknown_08376738[menuLayout][monIndex].y + 1;
    u16 *vramPtr = (u16*)(VRAM + 0xF000) + (x + y * 32);
    u8 var1 = status * 4;

    for (i = 0; i < 4; i++)
        vramPtr[i] = (0x18C + var1 + i) | -0x5000;
}

static void PartyMenuClearLevelStatusTilemap(u8 monIndex)
{
    u8 menuLayout;
    u8 x;
    u8 y;
    u16 *vramPtr;
    u8 i;

    if (IsLinkDoubleBattle() == TRUE)
        menuLayout = PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE;
    else
        menuLayout = IsDoubleBattle();

    x = gUnknown_08376738[menuLayout][monIndex].x - 1;
    y = gUnknown_08376738[menuLayout][monIndex].y + 1;

    vramPtr = (u16*)(VRAM + 0xF000) + (x + y * 32);
    for (i = 0; i < 4; i++)
        vramPtr[i] = 0;
}

static void PartyMenuWriteTilemap(u8 a, u8 x, u8 y)
{
    u16 *vramPtr = (u16*)(VRAM + 0xF000) + (x + y * 32);
    *vramPtr = a + 0x10C;
}

void PartyMenuDoPrintLevel(u8 monIndex, u8 menuLayout, u8 level)
{
    u8 *stringVar;
    u8 x = gUnknown_08376738[menuLayout][monIndex].x;
    u8 y = gUnknown_08376738[menuLayout][monIndex].y;

    PartyMenuWriteTilemap(0x40, x - 1, y + 1);

    stringVar = gStringVar1;
    stringVar[0] = 0xFC;
    stringVar[1] = 0x12;
    stringVar[2] = 0x8;

    ConvertIntToDecimalString(&stringVar[3], level);

    CpuFastFill(0, gTileBufferPtr + 0x100, 0x80);
    Text_InitWindow8004E3C((struct WindowTemplate *)&gWindowTemplate_81E6CAC, gTileBufferPtr, gStringVar1);
    CpuFastCopy(gTileBufferPtr + 0x100, OBJ_VRAM1 + 0x200 + (monIndex * 0x400), 0x80);
}

void PartyMenuPrintLevel(u8 monIndex, u8 menuLayout, struct Pokemon *pokemon)
{
    u16 level = GetMonData(pokemon, MON_DATA_LEVEL);
    PartyMenuDoPrintLevel(monIndex, menuLayout, level);
}

void PartyMenuPrintMonLevelOrStatus(u8 monIndex, struct Pokemon *pokemon)
{
    if (GetMonData(pokemon, MON_DATA_SPECIES) && !GetMonData(pokemon, MON_DATA_IS_EGG))
    {
        u8 statusAndPkrs = GetMonStatusAndPokerus(pokemon);
        u8 menuLayout;

        if (IsLinkDoubleBattle() == TRUE)
            menuLayout = PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE;
        else
            menuLayout = IsDoubleBattle();

        if (statusAndPkrs && statusAndPkrs != STATUS_PRIMARY_POKERUS)
            PartyMenuPutStatusTilemap(monIndex, menuLayout, statusAndPkrs - 1);
        else
            PartyMenuPrintLevel(monIndex, menuLayout, pokemon);

        PartyMenuPrintGenderIcon(monIndex, menuLayout, pokemon);
    }
}

void PartyMenuPrintMonsLevelOrStatus(void)
{
    u8 i;

    for (i = 0; i < PARTY_SIZE; i++)
        PartyMenuPrintMonLevelOrStatus(i, &gPlayerParty[i]);
}

void PartyMenuDoPrintGenderIcon(u16 species, u8 gender, u8 menuLayout, u8 monIndex, u8 *nickname)
{
    if (!ShouldHideGenderIcon(species, nickname))
    {
        u8 x = gUnknown_08376738[menuLayout][monIndex].x + 3;
        u8 y = gUnknown_08376738[menuLayout][monIndex].y + 1;

        switch (gender)
        {
        case MON_MALE:
            PartyMenuWriteTilemap(0x42, x, y);
            break;
        case MON_FEMALE:
            PartyMenuWriteTilemap(0x44, x, y);
            break;
        }
    }
}

void PartyMenuPrintGenderIcon(u8 monIndex, u8 menuLayout, struct Pokemon *pokemon)
{
    u16 species2;
    u8 gender;

    GetMonNickname(pokemon, gStringVar1);
    species2 = GetMonData(pokemon, MON_DATA_SPECIES2);
    gender = GetMonGender(pokemon);
    PartyMenuDoPrintGenderIcon(species2, gender, menuLayout, monIndex, gStringVar1);
}

void PartyMenuDoPrintHP(u8 monIndex, u8 b, u16 currentHP, u16 maxHP)
{
    register u8 *stringVar1 asm("r2") = gStringVar1;
    register u8 *textPtr asm("r2") = AlignInt1InMenuWindow(stringVar1, currentHP, 15, 1);
    textPtr[0] = CHAR_SLASH;

    AlignInt1InMenuWindow(++textPtr, maxHP, 35, 1);
    CpuFastFill(0, gTileBufferPtr + 0x100, 0x100);
    Text_InitWindow8004E3C((struct WindowTemplate *)&gWindowTemplate_81E6CAC, gTileBufferPtr, gStringVar1);
    CpuFastCopy(gTileBufferPtr + 0x100, OBJ_VRAM1 + 0x300 + (monIndex * 0x400), 0x100);
}

void PartyMenuPrintHP(u8 monIndex, u8 b, struct Pokemon *pokemon)
{
    u16 currentHP = GetMonData(pokemon, MON_DATA_HP);
    u16 maxHP = GetMonData(pokemon, MON_DATA_MAX_HP);

    PartyMenuDoPrintHP(monIndex, b, currentHP, maxHP);
}

void PartyMenuTryPrintHP(u8 monIndex, struct Pokemon *pokemon)
{
    if (GetMonData(pokemon, MON_DATA_SPECIES) && !GetMonData(pokemon, MON_DATA_IS_EGG))
    {
        if (IsLinkDoubleBattle() == TRUE)
            PartyMenuPrintHP(monIndex, PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE, pokemon);
        else
            PartyMenuPrintHP(monIndex, IsDoubleBattle(), pokemon);
    }
}

void PartyMenuTryPrintMonsHP(void)
{
    u8 i;

    for (i = 0; i < PARTY_SIZE; i++)
        PartyMenuTryPrintHP(i, &gPlayerParty[i]);
}

void unref_sub_806E564(void) { }
void unref_sub_806E568(void) { }
void nullsub_12(u8 monIndex, struct Pokemon *pokemon) { }
void nullsub_13(void) { }

void PartyMenuDoDrawHPBar(u8 monIndex, u8 menuLayout, u16 currentHP, u16 maxHP)
{
    u8 hpBarLevel;
    u16 *vramPtr;
    int var = -32768;
    struct BattleInterfaceStruct1 battleInterface;
    battleInterface.unk0 = maxHP;
    battleInterface.unk4 = currentHP;
    battleInterface.unk8 = 0;

    hpBarLevel = GetHPBarLevel(currentHP, maxHP);
    if (hpBarLevel > 2)
        battleInterface.unkC_0 = 4;
    if (hpBarLevel == 2)
        battleInterface.unkC_0 = 5;
    if (hpBarLevel < 2)
        battleInterface.unkC_0 = 6;

    battleInterface.unk10 = 0x100;

    vramPtr = gUnknown_08376858[menuLayout][monIndex];
    sub_80460C8(&battleInterface, &var, vramPtr, 0);

    vramPtr -= 2;

    vramPtr[0] = 0x3109;
    vramPtr[1] = 0x310A;
    vramPtr[8] = 0x310B;
}

void PartyMenuDrawHPBar(u8 monIndex, u8 menuLayout, struct Pokemon *pokemon)
{
    u16 currentHP = GetMonData(pokemon, MON_DATA_HP);
    u16 maxHP = GetMonData(pokemon, MON_DATA_MAX_HP);

    PartyMenuDoDrawHPBar(monIndex, menuLayout, currentHP, maxHP);
}

void PartyMenuTryDrawHPBar(u8 monIndex, struct Pokemon *pokemon)
{
    if (GetMonData(pokemon, MON_DATA_SPECIES) && !GetMonData(pokemon, MON_DATA_IS_EGG))
    {
        if (IsLinkDoubleBattle() == TRUE)
            PartyMenuDrawHPBar(monIndex, PARTY_MENU_LAYOUT_LINK_DOUBLE_BATTLE, pokemon);
        else
            PartyMenuDrawHPBar(monIndex, IsDoubleBattle(), pokemon);
    }
}

void PartyMenuDrawHPBars(void)
{
    u8 i;

    for (i = 0; i < PARTY_SIZE; i++)
        PartyMenuTryDrawHPBar(i, &gPlayerParty[i]);
}

void SwapPokemon(struct Pokemon *a, struct Pokemon *b)
{
    struct Pokemon temp = *a;
    *a = *b;
    *b = temp;
}

void SetPartyPopupMenuOffsets(u8 menuIndex, u8 *left, u8 *top, const struct PartyPopupMenu *menu)
{
    u8 bottomOffset = (2 * menu[menuIndex].numChoices) + 2;
    u8 rightOffset = menu[menuIndex].width + 1;

    *left = 30 - rightOffset;
    *top = 20 - bottomOffset;
}

void ShowPartyPopupMenu(u8 menuIndex, const struct PartyPopupMenu *menu, const struct MenuAction2 *menuActions, u8 cursorPos)
{
    u8 left;
    u8 top;

    SetPartyPopupMenuOffsets(menuIndex, &left, &top, menu);
    sub_8089C50(left, top, menu[menuIndex].width, menu[menuIndex].numChoices, menuActions, menu[menuIndex].items);

    InitMenu(0, left + 1, top + 1, menu[menuIndex].numChoices, cursorPos, menu[menuIndex].width - 1);
}

void ClosePartyPopupMenu(u8 index, const struct PartyPopupMenu *menu)
{
    u8 left;
    u8 top;

    SetPartyPopupMenuOffsets(index, &left, &top, menu);

    Menu_EraseWindowRect(left, top, left + menu[index].width, menu[index].numChoices * 2 + top + 1);
    Menu_DestroyCursor();
}

TaskFunc PartyMenuGetPopupMenuFunc(u8 menuIndex, const struct PartyPopupMenu *menus, const struct MenuAction2 *menuActions, u8 itemIndex)
{
    u8 action = menus[menuIndex].items[itemIndex];
    return menuActions[action].func;
}

u8 DisplayPartyMenuMessage(const u8 *message, u8 noClearAfter)
{
    u8 taskId;

    gPartyMenuMessage_IsPrinting = 1;

    Menu_DrawStdWindowFrame(WINDOW_LEFT, 14, WINDOW_RIGHT, 19);
    MenuPrintMessage(message, WINDOW_LEFT + 1, 15);

    taskId = CreateTask(Task_PartyMenuPrintRun, 1);
    gTasks[taskId].data[0] = noClearAfter;

    return taskId;
}

static void Task_PartyMenuPrintRun(u8 taskId)
{
    if (Menu_UpdateWindowText())
    {
        gPartyMenuMessage_IsPrinting = 0;
        if (gTasks[taskId].data[0] == 0)
            Menu_EraseWindowRect(WINDOW_LEFT, 14, WINDOW_RIGHT, 19);
        DestroyTask(taskId);
    }
}

void sub_806E8D0(u8 taskId, u16 b, TaskFunc c)
{
    gPartyMenu.unk10 = c;
    gPartyMenu.unk4 = taskId;
    gPartyMenu.primarySelectedMonIndex = sub_806CA38(taskId);
    gPartyMenu.secondarySelectedIndex = b;
    gPartyMenu.pokemon = &gPlayerParty[gPartyMenu.primarySelectedMonIndex];
}

bool8 PartyMenuUpdateMonHeldItem(struct Pokemon *pkmn, u16 item)
{
    u8 itemData[2];

    if (ItemIsMail(item) == TRUE)
    {
        if (GiveMailToMon(pkmn, item) == 0xFF)
            return TRUE;
        gUnknown_0202E8F4 = 2;
    }
    else
    {
        gUnknown_0202E8F4 = 1;
    }

    itemData[0] = item;
    itemData[1] = item >> 8;
    SetMonData(pkmn, MON_DATA_HELD_ITEM, itemData);
    return FALSE;
}

void PartyMenuTryGiveMonHeldItem(u8 taskId, u16 newItem, TaskFunc c)
{
    u16 currentItem;

    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, newItem, c);
    currentItem = GetMonData(gPartyMenu.pokemon, MON_DATA_HELD_ITEM);
    gUnknown_0202E8F4 = 0;
    gUnknown_0202E8F8 = 0;
    if (currentItem != 0)
    {
        if (ItemIsMail(currentItem) == TRUE)
        {
            DisplayPartyMenuMessage(gOtherText_MailMustBeRemoved, 1);
            CreateTask(party_menu_link_mon_held_item_object, 5);
        }
        else
        {
            GetMonNickname(gPartyMenu.pokemon, gStringVar1);
            CopyItemName(currentItem, gStringVar2);
            StringExpandPlaceholders(gStringVar4, gOtherText_AlreadyHolding);
            DisplayPartyMenuMessage(gStringVar4, 1);
            CreateTask(Task_ConfirmGiveHeldItem, 5);
            if (ItemIsMail(newItem) == TRUE)
                gUnknown_0202E8F8 = currentItem;
        }
    }
    else
    {
        PartyMenuUpdateMonHeldItem(gPartyMenu.pokemon, newItem);
        RemoveBagItem(newItem, 1);
        if (ItemIsMail(newItem))
        {
            gTasks[taskId].func = c;
        }
        else
        {
            DisplayGiveHeldItemMessage(gPartyMenu.primarySelectedMonIndex, newItem, 1);
            CreateTask(party_menu_link_mon_held_item_object, 5);
        }
    }
}

void party_menu_link_mon_held_item_object(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        SetHeldItemIconVisibility(gPartyMenu.unk4, gPartyMenu.primarySelectedMonIndex);
        gTasks[gPartyMenu.unk4].func = gPartyMenu.unk10;
        DestroyTask(taskId);
    }
}

void PartyMenuTryGiveMonHeldItem_806EACC(u8 taskId)
{
    s8 selection = Menu_ProcessInputNoWrap_();

    if (selection == 0)
    {
        u16 currentItem;

        Menu_EraseWindowRect(23, 8, 29, 13);
        currentItem = GetMonData(gPartyMenu.pokemon, MON_DATA_HELD_ITEM);
        RemoveBagItem(gPartyMenu.secondarySelectedIndex, 1);
        if (AddBagItem(currentItem, 1) == TRUE)
        {
            PartyMenuUpdateMonHeldItem(gPartyMenu.pokemon, gPartyMenu.secondarySelectedIndex);
            if (ItemIsMail(gPartyMenu.secondarySelectedIndex))
            {
                DisplayTakeHeldItemMessage(gPartyMenu.primarySelectedMonIndex, currentItem, 1);
            }
            else
            {
                CopyItemName(gPartyMenu.secondarySelectedIndex, gStringVar1);
                StringExpandPlaceholders(gStringVar4, gOtherText_TakenAndReplaced);
                DisplayPartyMenuMessage(gStringVar4, 1);
            }
        }
        else
        {
            DisplayPartyMenuMessage(gOtherText_BagFullCannotRemoveItem, 0);
            AddBagItem(gPartyMenu.secondarySelectedIndex, 1);
        }
    }
    else
    {
        if (selection != 1 && selection != -1)
            return;
        if (selection == -1)
            PlaySE(SE_SELECT);
        Menu_EraseWindowRect(23, 8, 29, 13);
    }
    gTasks[taskId].func = party_menu_link_mon_held_item_object;
}

void Task_ConfirmGiveHeldItem(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        DisplayYesNoMenu(23, 8, 1);
        gTasks[taskId].func = PartyMenuTryGiveMonHeldItem_806EACC;
    }
}

void DisplayGiveHeldItemMessage(u8 a, u16 b, u8 c)
{
    GetMonNickname(&gPlayerParty[a], gStringVar1);
    CopyItemName(b, gStringVar2);
    StringExpandPlaceholders(gStringVar4, gOtherText_WasGivenToHold);
    DisplayPartyMenuMessage(gStringVar4, c);
}

void PartyMenuTryGiveMonMail(u8 taskId, TaskFunc func)
{
    u32 var1;
    u16 currentItem;
    struct MailStruct *mail;

    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, 0, func);
    currentItem = GetMonData(gPartyMenu.pokemon, MON_DATA_HELD_ITEM);
    gUnknown_0202E8F4 = 0;
    var1 = eMailboxInfo.cursorPos + 6;
    mail = &gSaveBlock1.mail[var1 + eMailboxInfo.itemsAbove];

    if (currentItem != 0)
    {
        DisplayPartyMenuMessage(gOtherText_PokeHoldingItemCantMail, 1);
        CreateTask(party_menu_link_mon_held_item_object, 5);
    }
    else
    {
        GiveMailToMon2(gPartyMenu.pokemon, mail);
        ClearMailStruct(mail);
        DisplayPartyMenuMessage(gOtherText_MailTransferredMailbox, 1);
        CreateTask(party_menu_link_mon_held_item_object, 5);
    }
}

void PartyMenuTryGiveMonHeldItem_806ECE8(u8 taskId, TaskFunc func)
{
    u16 currentItem;

    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, 0, func);
    currentItem = GetMonData(gPartyMenu.pokemon, MON_DATA_HELD_ITEM);
    if (currentItem == 0)
    {
        StringExpandPlaceholders(gStringVar4, gOtherText_NotHoldingAnything);
        DisplayPartyMenuMessage(gStringVar4, 0);
        CreateTask(party_menu_link_mon_held_item_object, 5);
    }
    else
    {
        u8 itemData[2];

        itemData[0] = 0;
        itemData[1] = 0;
        if (AddBagItem(currentItem, 1) == TRUE)
        {
            if (ItemIsMail(currentItem) == TRUE)
                TakeMailFromMon(gPartyMenu.pokemon);
            DisplayTakeHeldItemMessage(gPartyMenu.primarySelectedMonIndex, currentItem, 0);
            SetMonData(gPartyMenu.pokemon, MON_DATA_HELD_ITEM, itemData);
        }
        else
        {
            DisplayPartyMenuMessage(gOtherText_BagFullCannotRemoveItem, 0);
        }
        CreateTask(party_menu_link_mon_held_item_object, 5);
    }
}

void DisplayTakeHeldItemMessage(u8 a, u16 b, u8 c)
{
    GetMonNickname(&gPlayerParty[a], gStringVar1);
    CopyItemName(b, gStringVar2);
    StringExpandPlaceholders(gStringVar4, gOtherText_ReceivedTheThingFrom);
    DisplayPartyMenuMessage(gStringVar4, c);
}

void DoTakeMail(u8 taskId, TaskFunc func)
{
    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, 0, func);
    gPartyMenu.secondarySelectedIndex = GetMonData(gPartyMenu.pokemon, MON_DATA_HELD_ITEM);
    DisplayPartyMenuMessage(gOtherText_SendRemovedMailPrompt, 1);
    CreateTask(Task_ConfirmTakeHeldMail, 5);
}

void Task_LoseMailMessage(u8 taskId)
{
    s8 selection = Menu_ProcessInputNoWrap_();

    if (selection == 0)
    {
        if (AddBagItem(gPartyMenu.secondarySelectedIndex, 1) == TRUE)
        {
            TakeMailFromMon(gPartyMenu.pokemon);
            DisplayPartyMenuMessage(gOtherText_MailTaken, 0);
        }
        else
        {
            DisplayPartyMenuMessage(gOtherText_BagFullCannotRemoveItem, 0);
        }
        Menu_EraseWindowRect(23, 8, 29, 13);
        gTasks[taskId].func = party_menu_link_mon_held_item_object;
    }
    else
    {
        if (selection != 1 && selection != -1)
            return;
        if (selection == -1)
            PlaySE(SE_SELECT);
        Menu_EraseWindowRect(23, 8, 29, 13);
        Menu_EraseWindowRect(0, 14, 29, 19);
        gTasks[taskId].func = party_menu_link_mon_held_item_object;
    }
}

void Task_ConfirmLoseMailMessage(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        DisplayYesNoMenu(23, 8, 1);
        gTasks[taskId].func = Task_LoseMailMessage;
    }
}

void Task_TakeHeldMail(u8 taskId)
{
    s8 selection = Menu_ProcessInputNoWrap_();

    if (selection == 0)
    {
        Menu_EraseWindowRect(23, 8, 29, 13);
        if (TakeMailFromMon2(gPartyMenu.pokemon) != 0xFF)
            DisplayPartyMenuMessage(gOtherText_MailWasSent, 0);
        else
            DisplayPartyMenuMessage(gOtherText_MailboxIsFull, 0);
        gTasks[taskId].func = party_menu_link_mon_held_item_object;
    }
    else
    {
        if (selection != 1 && selection != -1)
            return;
        if (selection == -1)
            PlaySE(SE_SELECT);
        Menu_EraseWindowRect(23, 8, 29, 13);
        DisplayPartyMenuMessage(gOtherText_MailRemovedMessageLost, 1);
        gTasks[taskId].func = Task_ConfirmLoseMailMessage;
    }
}

void Task_ConfirmTakeHeldMail(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        DisplayYesNoMenu(23, 8, 1);
        gTasks[taskId].func = Task_TakeHeldMail;
    }
}

u16 ItemIdToBattleMoveId(u16 item)
{
    u16 machineNumber = item - ITEM_TM01_FOCUS_PUNCH;
    return TMHMMoves[machineNumber];
}

bool8 pokemon_has_move(struct Pokemon *pkmn, u16 move)
{
    u8 i;

    for (i = 0; i < 4; i++)
    {
        if (GetMonData(pkmn, MON_DATA_MOVE1 + i) == move)
            return TRUE;
    }
    return FALSE;
}

void TeachMonTMMove(u8 taskId, u16 move, TaskFunc func)
{
    PlaySE(SE_SELECT);
    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, move, func);
    CreateTask(Task_TeamMonTMMove, 5);
}

#if DEBUG
extern u8 gUnknown_020297ED;
#endif

void Task_TeamMonTMMove(u8 taskId)
{
    GetMonNickname(gPartyMenu.pokemon, gStringVar1);
    gPartyMenu.unk8 = ItemIdToBattleMoveId(gPartyMenu.secondarySelectedIndex);
    StringCopy(gStringVar2, gMoveNames[gPartyMenu.unk8]);
    ePartyMenu2.pmUnk282 = 0;
    if (pokemon_has_move(gPartyMenu.pokemon, gPartyMenu.unk8))
    {
        StringExpandPlaceholders(gStringVar4, gOtherText_AlreadyKnows);
        DisplayPartyMenuMessage(gStringVar4, 1);
        gTasks[taskId].func = party_menu_link_mon_held_item_object;
    }
    else
    {
        if (
#if DEBUG
         !gUnknown_020297ED &&
#endif
         !CanMonLearnTMHM(gPartyMenu.pokemon, gPartyMenu.secondarySelectedIndex - 33))
        {
            StringExpandPlaceholders(gStringVar4, gOtherText_NotCompatible);
            DisplayPartyMenuMessage(gStringVar4, 1);
            gTasks[taskId].func = party_menu_link_mon_held_item_object;
        }
        else
        {
            if (GiveMoveToMon(gPartyMenu.pokemon, gPartyMenu.unk8) != 0xFFFF)
            {
                Task_TeamMonTMMove2(taskId);
            }
            else
            {
                StringExpandPlaceholders(gStringVar4, gOtherText_WantsToLearn);
                DisplayPartyMenuMessage(gStringVar4, 1);
                gTasks[taskId].func = sub_806F358;
            }
        }
    }
}

void Task_TeamMonTMMove2(u8 taskId)
{
    StringCopy(gStringVar2, gMoveNames[gPartyMenu.unk8]);
    StringExpandPlaceholders(gStringVar4, gOtherText_LearnedMove);
    DisplayPartyMenuMessage(gStringVar4, 1);
    AdjustFriendship(gPartyMenu.pokemon, FRIENDSHIP_EVENT_LEARN_TMHM);
    if (ePartyMenu2.pmUnk282 == 0 && gPartyMenu.secondarySelectedIndex < ITEM_HM01_CUT)
        RemoveBagItem(gPartyMenu.secondarySelectedIndex, 1);
    gTasks[taskId].func = Task_TeamMonTMMove3;
}

void Task_TeamMonTMMove3(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        PlayFanfare(MUS_LEVEL_UP);
        gTasks[taskId].func = Task_TeamMonTMMove4;
    }
}

void Task_TeamMonTMMove4(u8 taskId)
{
    if (IsFanfareTaskInactive())
    {
        if ((gMain.newKeys & A_BUTTON) || (gMain.newKeys & B_BUTTON))
        {
            SetHeldItemIconVisibility(gPartyMenu.unk4, gPartyMenu.primarySelectedMonIndex);
            if (ePartyMenu2.pmUnk282 == 1)
            {
                TeachMonMoveInPartyMenu(taskId);
            }
            else
            {
                gTasks[gPartyMenu.unk4].func = gPartyMenu.unk10;
                DestroyTask(taskId);
            }
        }
    }
}

void sub_806F2FC(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        SetHeldItemIconVisibility(gPartyMenu.unk4, gPartyMenu.primarySelectedMonIndex);
        if (ePartyMenu2.pmUnk282 == 1)
        {
            TeachMonMoveInPartyMenu(taskId);
        }
        else
        {
            gTasks[gPartyMenu.unk4].func = gPartyMenu.unk10;
            DestroyTask(taskId);
        }
    }
}

void sub_806F358(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        DisplayYesNoMenu(23, 8, 1);
        gTasks[taskId].func = sub_806F390;
    }
}

void sub_806F390(u8 taskId)
{
    s8 selection = Menu_ProcessInputNoWrap_();

    if (selection == 0)
    {
        Menu_EraseWindowRect(23, 8, 29, 13);
        DisplayPartyMenuMessage(gOtherText_WhichMoveToForget2, 1);
        gTasks[taskId].func = sub_806F44C;
    }
    else
    {
        if (selection != 1 && selection != -1)
            return;
        if (selection == -1)
            PlaySE(SE_SELECT);
        StopTryingToTeachMove_806F614(taskId);
    }
}

void sub_806F3FC(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        ShowSelectMovePokemonSummaryScreen(gPlayerParty, gPartyMenu.primarySelectedMonIndex, gPlayerPartyCount - 1, sub_808B564, gPartyMenu.unk8);
        DestroyTask(taskId);
    }
}

void sub_806F44C(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB(0, 0, 0));
        gTasks[taskId].func = sub_806F3FC;
    }
}

void TaughtMove(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        u8 moveIndex;
        u16 r4;

        gTasks[taskId].func = TaskDummy;
        sub_806E8D0(taskId, gSpecialVar_ItemId, sub_808B508);
        moveIndex = sub_809FA30();
        r4 = GetMonData(gPartyMenu.pokemon, MON_DATA_MOVE1 + moveIndex);
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        StringCopy(gStringVar2, gMoveNames[r4]);
        StringExpandPlaceholders(gStringVar4, gOtherText_ForgetMove123_2);
        DisplayPartyMenuMessage(gStringVar4, 1);
        CreateTask(TMMoveUpdateMoveSlot, 5);
    }
}

void TMMoveUpdateMoveSlot(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        RemoveMonPPBonus(gPartyMenu.pokemon, sub_809FA30());
        SetMonMoveSlot(gPartyMenu.pokemon, gPartyMenu.unk8, sub_809FA30());
        Task_TeamMonTMMove2(taskId);
    }
}

void StopTryingToTeachMove_806F588(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        gTasks[taskId].func = TaskDummy;
        sub_806E8D0(taskId, gSpecialVar_ItemId, sub_808B508);
        StringCopy(gStringVar2, gMoveNames[gPartyMenu.unk8]);
        StringExpandPlaceholders(gStringVar4, gOtherText_StopTryingTo);
        DisplayPartyMenuMessage(gStringVar4, 1);
        CreateTask(StopTryingToTeachMove_806F67C, 5);
    }
}

void StopTryingToTeachMove_806F614(u8 taskId)
{
    Menu_EraseWindowRect(23, 8, 29, 13);
    StringCopy(gStringVar2, gMoveNames[gPartyMenu.unk8]);
    StringExpandPlaceholders(gStringVar4, gOtherText_StopTryingTo);
    DisplayPartyMenuMessage(gStringVar4, 1);
    gTasks[taskId].func = StopTryingToTeachMove_806F67C;
}

void StopTryingToTeachMove_806F67C(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        DisplayYesNoMenu(23, 8, 1);
        gTasks[taskId].func = StopTryingToTeachMove_806F6B4;
    }
}

void StopTryingToTeachMove_806F6B4(u8 taskId)
{
    s8 selection = Menu_ProcessInputNoWrap_();

    if (selection == 0)
    {
        Menu_EraseWindowRect(23, 8, 29, 13);
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        StringCopy(gStringVar2, gMoveNames[gPartyMenu.unk8]);
        StringExpandPlaceholders(gStringVar4, gOtherText_DidNotLearnMove2);
        DisplayPartyMenuMessage(gStringVar4, 1);
        gTasks[taskId].func = sub_806F2FC;
    }
    else
    {
        if (selection != 1 && selection != -1)
            return;
        if (selection == -1)
            PlaySE(SE_SELECT);
        Menu_EraseWindowRect(23, 8, 29, 13);
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        StringCopy(gStringVar2, gMoveNames[gPartyMenu.unk8]);
        StringExpandPlaceholders(gStringVar4, gOtherText_WantsToLearn);
        DisplayPartyMenuMessage(gStringVar4, 1);
        gTasks[taskId].func = sub_806F358;
    }
}

bool8 IsHMMove(u16 move)
{
    u8 i;

    for (i = 0; i < NUM_HIDDEN_MACHINES; i++)
    {
        if (TMHMMoves[NUM_TECHNICAL_MACHINES + i] == move)
            return TRUE;
    }

    return FALSE;
}

s16 sub_806F7E8(u8 taskId, struct BattleInterfaceStruct1 *b, s8 c)
{
    s16 *taskData;
    u8 hpBarLevel;
    void *vramPtr;

    taskData = gTasks[taskId].data;

    b->unk0 = taskData[10];
    b->unk4 = taskData[11];
    b->unk8 = taskData[12] * c;
    b->unk10 = 0x100;
    hpBarLevel = GetHPBarLevel(ePartyMenu2.pmUnk282, b->unk0);
    if (hpBarLevel > 2)
        b->unkC_0 = 4;
    if (hpBarLevel == 2)
        b->unkC_0 = 5;
    if (hpBarLevel < 2)
        b->unkC_0 = 6;
    vramPtr = gUnknown_08376858[IsDoubleBattle()][gPartyMenu.primarySelectedMonIndex];
    return sub_80460C8(b, (int *)&gPartyMenu.unkC, vramPtr, 0);
}

void sub_806F8AC(u8 taskId)
{
    struct BattleInterfaceStruct1 sp0;
    u16 sp14;

    ePartyMenu2.pmUnk282 = sub_806F7E8(taskId, &sp0, -1);
    if (ePartyMenu2.pmUnk282 == -1)
    {
        gPartyMenu.unkC = 0;
        if (-sp0.unk8 + sp0.unk4 > sp0.unk0)
            sp14 = sp0.unk0 - sp0.unk4;
        else
            sp14 = -sp0.unk8;
        gBattleMoveDamage = -sp14;
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        ConvertIntToDecimalStringN(gStringVar2, sp14, 0, 3);
        if (gTasks[taskId].data[14] == 0)
            StringExpandPlaceholders(gStringVar4, gOtherText_HPRestoredBy);
        else
            StringExpandPlaceholders(gStringVar4, gOtherText_RegainedHealth);
        SetMonIconAnim(GetMonIconSpriteId(gPartyMenu.unk4, gPartyMenu.primarySelectedMonIndex), gPartyMenu.pokemon);
        task_pc_turn_off(&gUnknown_083769A8[IsDoubleBattle() * 12 + gPartyMenu.primarySelectedMonIndex * 2], 7);
        ePartyMenu2.unk261 = 2;
        DisplayPartyMenuMessage(gStringVar4, 1);
        sp14 += sp0.unk4;
        SetMonData(gPartyMenu.pokemon, MON_DATA_HP, &sp14);
        RemoveBagItem(gPartyMenu.secondarySelectedIndex, 1);
        sub_8032638();
        gTasks[taskId].func = sub_806FB44;
    }
    else
    {
        PartyMenuDoPrintHP(gPartyMenu.primarySelectedMonIndex, IsDoubleBattle(), ePartyMenu2.pmUnk282, sp0.unk0);
    }
}

void sub_806FA18(u8 taskId)
{
    struct BattleInterfaceStruct1 sp0;

    ePartyMenu2.pmUnk282 = sub_806F7E8(taskId, &sp0, 1);
    if (ePartyMenu2.pmUnk282 == -1)
    {
        PlaySE(SE_USE_ITEM);
        gPartyMenu.unkC = 0;
        gTasks[taskId].data[11] -= gTasks[taskId].data[12];
        SetMonData(gPartyMenu.pokemon, MON_DATA_HP, &gTasks[taskId].data[11]);
        SetMonIconAnim(GetMonIconSpriteId(gPartyMenu.unk4, ePartyMenu.slotId), gPartyMenu.pokemon);
        gPartyMenu.primarySelectedMonIndex = gSprites[ePartyMenu.slotId2].data[0];
        gPartyMenu.pokemon = &gPlayerParty[gPartyMenu.primarySelectedMonIndex];
        gTasks[taskId].data[10] = GetMonData(gPartyMenu.pokemon, MON_DATA_MAX_HP);
        gTasks[taskId].data[11] = GetMonData(gPartyMenu.pokemon, MON_DATA_HP);
        gPartyMenu.unkC = -32768;
        gPartyMenu.unk14 = gPartyMenu.unk10;
        gTasks[taskId].func = sub_806F8AC;
        ePartyMenu2.pmUnk282 = gTasks[taskId].data[11];
    }
    else
    {
        PartyMenuDoPrintHP(gPartyMenu.primarySelectedMonIndex, IsDoubleBattle(), ePartyMenu2.pmUnk282, sp0.unk0);
    }
}

void sub_806FB0C(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        gTasks[gPartyMenu.unk4].func = gPartyMenu.unk10;
        DestroyTask(taskId);
    }
}

void sub_806FB44(u8 taskId)
{
    if (gPartyMenuMessage_IsPrinting == 0)
    {
        gTasks[gPartyMenu.unk4].func = gPartyMenu.unk14;
        DestroyTask(taskId);
    }
}

bool8 IsHPRecoveryItem(u16 item)
{
    const u8 *itemEffect;

    if (item == ITEM_ENIGMA_BERRY)
        itemEffect = gSaveBlock1.enigmaBerry.itemEffect;
    else
        itemEffect = gItemEffectTable[item - ITEM_POTION];

    if (itemEffect[4] & 4)
        return TRUE;
    else
        return FALSE;
}

void PartyMenuUpdateLevelOrStatus(struct Pokemon *pkmn, u8 monIndex)
{
    PartyMenuClearLevelStatusTilemap(monIndex);
    PartyMenuPrintMonLevelOrStatus(monIndex, pkmn);
}

void GetMedicineItemEffectMessage(u16 item)
{
    switch (GetItemEffectType(item))
    {
    case 3:
        StringExpandPlaceholders(gStringVar4, gOtherText_CuredPoisoning);
        break;
    case 4:
        StringExpandPlaceholders(gStringVar4, gOtherText_WokeUp);
        break;
    case 5:
        StringExpandPlaceholders(gStringVar4, gOtherText_BurnHealed);
        break;
    case 6:
        StringExpandPlaceholders(gStringVar4, gOtherText_ThawedOut);
        break;
    case 7:
        StringExpandPlaceholders(gStringVar4, gOtherText_CuredParalysis);
        break;
    case 8:
        StringExpandPlaceholders(gStringVar4, gOtherText_SnapConfusion);
        break;
    case 9:
        StringExpandPlaceholders(gStringVar4, gOtherText_GotOverLove);
        break;
    case 11:
        StringExpandPlaceholders(gStringVar4, gOtherText_BecameHealthy);
        break;
    case 13:
        StringCopy(gStringVar2, gOtherText_Hp2);
        StringExpandPlaceholders(gStringVar4, gOtherText_WasRaised);
        break;
    case 12:
        StringCopy(gStringVar2, gOtherText_Attack);
        StringExpandPlaceholders(gStringVar4, gOtherText_WasRaised);
        break;
    case 17:
        StringCopy(gStringVar2, gOtherText_Defense);
        StringExpandPlaceholders(gStringVar4, gOtherText_WasRaised);
        break;
    case 16:
        StringCopy(gStringVar2, gOtherText_Speed);
        StringExpandPlaceholders(gStringVar4, gOtherText_WasRaised);
        break;
    case 14:
        StringCopy(gStringVar2, gOtherText_SpAtk2);
        StringExpandPlaceholders(gStringVar4, gOtherText_WasRaised);
        break;
    case 15:
        StringCopy(gStringVar2, gOtherText_SpDef2);
        StringExpandPlaceholders(gStringVar4, gOtherText_WasRaised);
        break;
    case 19:
    case 20:
        StringExpandPlaceholders(gStringVar4, gOtherText_PPIncreased);
        break;
    case 21:
        StringExpandPlaceholders(gStringVar4, gOtherText_PPRestored);
        break;
    default:
        StringExpandPlaceholders(gStringVar4, gOtherText_WontHaveAnyEffect);
        break;
    }
}

bool8 IsMedicineIneffective(struct Pokemon *pkmn, u16 item)
{
    if (GetItemEffectType(item) == 13
     && GetMonData(pkmn, MON_DATA_SPECIES) == SPECIES_SHEDINJA)
        return TRUE;
    else
        return FALSE;
}

bool8 ExecuteTableBasedItemEffect__(u8 monIndex, u16 item, u8 moveIndex)
{
    if (gMain.inBattle)
        return ExecuteTableBasedItemEffect_(&gPlayerParty[monIndex], item, sub_8094C20(monIndex), moveIndex);
    else
        return ExecuteTableBasedItemEffect_(&gPlayerParty[monIndex], item, monIndex, moveIndex);
}

void UseMedicine(u8 taskId, u16 item, TaskFunc func)
{
    u8 r7;
    bool8 r9 = FALSE;
    bool8 r0;

    gTasks[taskId].func = TaskDummy;
    r7 = CreateTask(TaskDummy, 5);
    sub_806E8D0(taskId, item, func);
    if (!IsMedicineIneffective(gPartyMenu.pokemon, item))
    {
        r9 = IsHPRecoveryItem(item);
        if (r9 == TRUE)
        {
            gTasks[r7].data[10] = GetMonData(gPartyMenu.pokemon, MON_DATA_MAX_HP);
            gTasks[r7].data[11] = GetMonData(gPartyMenu.pokemon, MON_DATA_HP);
            if (gTasks[r7].data[10] == gTasks[r7].data[11])
            {
                r9 = FALSE;
                gTasks[r7].data[10] = 0;
                gTasks[r7].data[11] = 0;
            }
        }
        r0 = ExecuteTableBasedItemEffect__(gPartyMenu.primarySelectedMonIndex, item, 0);
    }
    else
    {
        r0 = TRUE;
    }

    if (r0)
    {
        gUnknown_0202E8F4 = 0;
        PlaySE(SE_SELECT);
        DisplayPartyMenuMessage(gOtherText_WontHaveAnyEffect, 1);
        gTasks[r7].func = sub_806FB0C;
    }
    else
    {
        u8 statusAndPkrs;

        gUnknown_0202E8F4 = 1;
        if (!IsBlueYellowRedFlute(item))
            PlaySE(SE_USE_ITEM);
        else
            PlaySE(SE_GLASS_FLUTE);
        statusAndPkrs = GetMonStatusAndPokerus(gPartyMenu.pokemon);
        if (statusAndPkrs == STATUS_PRIMARY_POKERUS || statusAndPkrs == STATUS_PRIMARY_NONE)
            PartyMenuUpdateLevelOrStatus(gPartyMenu.pokemon, gPartyMenu.primarySelectedMonIndex);
        if (r9 == TRUE)
        {
            gTasks[r7].data[12] = GetMonData(gPartyMenu.pokemon, MON_DATA_HP) - gTasks[r7].data[11];
            gPartyMenu.unkC = -32768;
            if (gTasks[r7].data[11] == 0)
                gTasks[r7].data[14] = 1;
            else
                gTasks[r7].data[14] = 0;
            gPartyMenu.unk14 = gPartyMenu.unk10;
            gTasks[r7].func = sub_806F8AC;
            ePartyMenu2.pmUnk282 = gTasks[r7].data[11];
        }
        else
        {
            GetMonNickname(gPartyMenu.pokemon, gStringVar1);
            if (!IsBlueYellowRedFlute(item))
                RemoveBagItem(item, 1);
            GetMedicineItemEffectMessage(item);
            TryPrintPartyMenuMonNickname(gPartyMenu.primarySelectedMonIndex, gPartyMenu.pokemon);
            DisplayPartyMenuMessage(gStringVar4, 1);
            gTasks[r7].func = sub_806FB0C;
        }
    }
}

bool8 IsBlueYellowRedFlute(u16 item)
{
    if (item == ITEM_BLUE_FLUTE
     || item == ITEM_RED_FLUTE
     || item == ITEM_YELLOW_FLUTE)
        return TRUE;
    else
        return FALSE;
}

void DoSacredAshItemEffect(u8 taskId, u16 item, TaskFunc func)
{
    gPartyMenu.unk10 = func;
    gPartyMenu.unk4 = taskId;
    gPartyMenu.secondarySelectedIndex = item;
    gPartyMenu.primarySelectedMonIndex = 0;
    gPartyMenu.unk14 = sub_80701DC;
    ePartyMenu2.pmUnk27E = 0;
    ePartyMenu2.pmUnk280 = 0;
    sub_8070088(taskId);
}

void sub_8070088(u8 taskId)
{
    u8 taskId2;

    gTasks[taskId].func = TaskDummy;
    if (GetMonData(&gPlayerParty[gPartyMenu.primarySelectedMonIndex], MON_DATA_SPECIES) == SPECIES_NONE)
    {
        gTasks[taskId].func = sub_80701DC;
    }
    else
    {
        s16 *taskData;

        taskId2 = CreateTask(TaskDummy, 5);
        taskData = gTasks[taskId2].data;
        gPartyMenu.pokemon = &gPlayerParty[gPartyMenu.primarySelectedMonIndex];
        taskData[10] = GetMonData(gPartyMenu.pokemon, MON_DATA_MAX_HP);
        taskData[11] = GetMonData(gPartyMenu.pokemon, MON_DATA_HP);
        if (ExecuteTableBasedItemEffect__(gPartyMenu.primarySelectedMonIndex, gPartyMenu.secondarySelectedIndex, 0))
        {
            DestroyTask(taskId2);
            gTasks[taskId].func = sub_80701DC;
        }
        else
        {
            gUnknown_0202E8F4 = 1;
            Menu_EraseWindowRect(WINDOW_LEFT, 14, WINDOW_RIGHT, 19);
            PlaySE(SE_USE_ITEM);
            PartyMenuUpdateLevelOrStatus(gPartyMenu.pokemon, gPartyMenu.primarySelectedMonIndex);
            task_pc_turn_off(&gUnknown_083769A8[IsDoubleBattle() * 12 + gPartyMenu.primarySelectedMonIndex * 2], 9);
            ePartyMenu2.unk261 = 2;
            taskData[12] = GetMonData(gPartyMenu.pokemon, MON_DATA_HP) - taskData[11];
            taskData[14] = 1;
            ePartyMenu2.pmUnk27E = 1;
            ePartyMenu2.pmUnk280 = 1;
            ePartyMenu2.pmUnk282 = taskData[11];
            gPartyMenu.unkC = -32768;
            gTasks[taskId2].func = sub_806F8AC;
        }
    }
}

void sub_80701DC(u8 taskId)
{
    if (ePartyMenu2.pmUnk27E == 1)
    {
        AddBagItem(gPartyMenu.secondarySelectedIndex, 1);
        if (GetMonData(&gPlayerParty[gPartyMenu.primarySelectedMonIndex], MON_DATA_SPECIES) != 0)
        {
            task_pc_turn_off(&gUnknown_083769A8[IsDoubleBattle() * 12 + gPartyMenu.primarySelectedMonIndex * 2], 3);
            ePartyMenu2.unk261 = 2;
        }
        ePartyMenu2.pmUnk27E = 0;
    }
    gPartyMenu.primarySelectedMonIndex++;
    if (gPartyMenu.primarySelectedMonIndex == 6)
    {
        gUnknown_0202E8F4 = 0;
        if (ePartyMenu2.pmUnk280 == 0)
        {
            gTasks[taskId].func = TaskDummy;
            DisplayPartyMenuMessage(gOtherText_WontHaveAnyEffect, 1);
            CreateTask(sub_806FB0C, 8);
        }
        else
        {
            RemoveBagItem(gPartyMenu.secondarySelectedIndex, 1);
            gTasks[taskId].func = gPartyMenu.unk10;
        }
        gLastFieldPokeMenuOpened = 0;
    }
    else
    {
        sub_8070088(taskId);
    }
}

void CreateItemUseMoveMenu(u8 partyMonIndex)
{
    u8 r6;
    u8 i;

    r6 = 0;
    Menu_DrawStdWindowFrame(19, 10, 29, 19);
    for (i = 0; i < 4; i++)
    {
        u16 move = GetMonData(&gPlayerParty[partyMonIndex], MON_DATA_MOVE1 + i);

        Menu_PrintText(gMoveNames[move], 20, i * 2 + 11);
        if (move != 0)
            r6++;
    }
    InitMenu(0, 20, 11, r6, 0, 9);
}

void Task_HandleItemUseMoveMenuInput(u8 taskId)
{
    if (gMain.newKeys & DPAD_UP)
    {
        PlaySE(SE_SELECT);
        Menu_MoveCursor(-1);
    }
    else if (gMain.newKeys & DPAD_DOWN)
    {
        PlaySE(SE_SELECT);
        Menu_MoveCursor(1);
    }
    else if (gMain.newKeys & A_BUTTON)
    {
        gUnknown_08376B54[0](taskId);
    }
    else if (gMain.newKeys & B_BUTTON)
    {
        PlaySE(SE_SELECT);
        gUnknown_08376B54[1](taskId);
    }
}

void DoPPRecoveryItemEffect(u8 taskId, u16 item, TaskFunc c)
{
    const u8 *itemEffect;
    u8 taskId2;

    if (item == ITEM_ENIGMA_BERRY)
        itemEffect = gSaveBlock1.enigmaBerry.itemEffect;
    else
        itemEffect = gItemEffectTable[item - ITEM_POTION];
    gTasks[taskId].func = TaskDummy;
    taskId2 = CreateTask(TaskDummy, 5);
    sub_806E8D0(taskId, item, c);
    if (!(itemEffect[4] & 0x10))
    {
        gTasks[taskId2].data[11] = 0;
        DoRecoverPP(taskId2);
    }
    else
    {
        PlaySE(SE_SELECT);
        PrintPartyMenuPromptText(10, 3);
        CreateItemUseMoveMenu(gPartyMenu.primarySelectedMonIndex);
        gTasks[taskId2].func = Task_HandleItemUseMoveMenuInput;
        gMain.newKeys = 0;
    }
}

void ItemUseMoveMenu_HandleMoveSelection(u8 taskId)
{
    Menu_DestroyCursor();
    Menu_EraseWindowRect(19, 10, 29, 19);
    PartyMenuEraseMsgBoxAndFrame();
    gTasks[taskId].data[11] = Menu_GetCursorPos();
    DoRecoverPP(taskId);
}

void ItemUseMoveMenu_HandleCancel(u8 taskId)
{
    Menu_DestroyCursor();
    Menu_EraseWindowRect(19, 10, 29, 19);
    if (gMain.inBattle)
        gTasks[gPartyMenu.unk4].func = HandleBattlePartyMenu;
    else
        gTasks[gPartyMenu.unk4].func = sub_808B0C0;
    PrintPartyMenuPromptText(3, 0);
    DestroyTask(taskId);
}

void DoRecoverPP(u8 taskId)
{
    u16 r5 = 0;

    if (ExecuteTableBasedItemEffect__(gPartyMenu.primarySelectedMonIndex, gPartyMenu.secondarySelectedIndex, gTasks[taskId].data[11]))
    {
        gUnknown_0202E8F4 = 0;
        PlaySE(SE_SELECT);
        DisplayPartyMenuMessage(gOtherText_WontHaveAnyEffect, 1);
    }
    else
    {
        gUnknown_0202E8F4 = 1;
        PlaySE(SE_USE_ITEM);
        RemoveBagItem(gPartyMenu.secondarySelectedIndex, 1);
        r5 = GetMonData(gPartyMenu.pokemon, MON_DATA_MOVE1 + gTasks[taskId].data[11]);
        StringCopy(gStringVar1, gMoveNames[r5]);
        GetMedicineItemEffectMessage(gPartyMenu.secondarySelectedIndex);
        DisplayPartyMenuMessage(gStringVar4, 1);
    }
    gTasks[taskId].func = sub_806FB0C;
}

void DoPPUpItemEffect(u8 taskId, u16 b, TaskFunc c)
{
    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, b, c);
    PlaySE(SE_SELECT);
    PrintPartyMenuPromptText(11, 3);
    CreateItemUseMoveMenu(gPartyMenu.primarySelectedMonIndex);
    CreateTask(Task_HandleItemUseMoveMenuInput, 5);
    gMain.newKeys = 0;
}

static const u8 *const StatNames[] =
{
    gOtherText_HP,
    gOtherText_Attack,
    gOtherText_Defense,
    gOtherText_SpAtk,
    gOtherText_SpDef,
    gOtherText_Speed,
};

static const u8 StatDataTypes[] =
{
    MON_DATA_MAX_HP,
    MON_DATA_ATK,
    MON_DATA_DEF,
    MON_DATA_SPATK,
    MON_DATA_SPDEF,
    MON_DATA_SPEED,
};

#if DEBUG

const u8 gUnknown_Debug_839B6CE[] = {0x40, 0, 0, 0, 0x44, 0, 0xFF, 0, 0, 0};

const u8 gUnknown_Debug_839B6D8[] = _(
    "{STR_VAR_1}かい　じっこう\n"
    "{STR_VAR_2}たんいじかん　しょうひ");

#endif

void DoRareCandyItemEffect(u8 taskId, u16 item, TaskFunc c)
{
    u8 i;
    bool8 noEffect;

    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, item, c);

    if (GetMonData(gPartyMenu.pokemon, MON_DATA_LEVEL) != 100)
    {
        for (i = 0; i < NUM_STATS; i++)
            ePartyMenu2.pmStatGrowths[i] = GetMonData(gPartyMenu.pokemon, StatDataTypes[i]);
        noEffect = ExecuteTableBasedItemEffect__(gPartyMenu.primarySelectedMonIndex, item, 0);
    }
    else
        noEffect = TRUE;

    if (noEffect)
    {
        gUnknown_0202E8F4 = 0;
        PlaySE(SE_SELECT);
        DisplayPartyMenuMessage(gOtherText_WontHaveAnyEffect, 1);
        CreateTask(sub_806FB0C, 5);
    }
    else
    {
        u8 level;

        gUnknown_0202E8F4 = 1;
        PlayFanfareByFanfareNum(0);
        RedrawPokemonInfoInMenu(gPartyMenu.primarySelectedMonIndex, gPartyMenu.pokemon);
        RemoveBagItem(item, 1);
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        level = GetMonData(gPartyMenu.pokemon, MON_DATA_LEVEL);
        ConvertIntToDecimalStringN(gStringVar2, level, 0, 3);
        StringExpandPlaceholders(gStringVar4, gOtherText_ElevatedTo);
        DisplayPartyMenuMessage(gStringVar4, 1);
        CreateTask(Task_RareCandy1, 5);
    }
}

void Task_RareCandy1(u8 taskId)
{
    if (WaitFanfare(0) && gPartyMenuMessage_IsPrinting == 0)
    {
        if ((gMain.newKeys & A_BUTTON) || (gMain.newKeys & B_BUTTON))
        {
            PlaySE(SE_SELECT);
            PrintStatGrowthsInLevelUpWindow(taskId);
            gTasks[taskId].func = Task_RareCandy2;
        }
    }
}

void Task_RareCandy2(u8 taskId)
{
    if ((gMain.newKeys & A_BUTTON) || (gMain.newKeys & B_BUTTON))
    {
        PlaySE(SE_SELECT);
        PrintNewStatsInLevelUpWindow(taskId);
        gTasks[taskId].func = Task_RareCandy3;
    }
}

void PrintStatGrowthsInLevelUpWindow(u8 taskId)
{
    u8 i;

    Menu_DrawStdWindowFrame(11, 0, 29, 7);

    for (i = 0; i < NUM_STATS; i++)
    {
        u8 x;
        u8 y;
        u32 stat;
#if GERMAN
        u8 *ptr;
#endif

        stat = GetMonData(gPartyMenu.pokemon, StatDataTypes[i]);

        ePartyMenu2.pmStatGrowths[i + NUM_STATS] = stat;
        ePartyMenu2.pmStatGrowths[i] = stat - ePartyMenu2.pmStatGrowths[i];

        x = (i / 3) * 9 + 11;
        y = ((i % 3) << 1) + 1;

#if GERMAN
        ptr = StringCopy(gStringVar1, StatNames[i]);
        *ptr++ = EXT_CTRL_CODE_BEGIN;
        *ptr++ = 0x13;
        *ptr++ = 0x2E;
        ptr = StringCopy(ptr, gOtherText_TallPlusAndRightArrow);
        *ptr++ = EXT_CTRL_CODE_BEGIN;
        *ptr++ = 0x13;
        *ptr++ = 0x34;
        ConvertIntToDecimalStringN(ptr, ePartyMenu2.pmStatGrowths[i], 1, 2);
        Menu_PrintText(gStringVar1, x + 1, y);
#else
        Menu_PrintTextPixelCoords(StatNames[i], (x + 1) * 8, y * 8, 1);
        if (i == 2)
            Menu_PrintTextPixelCoords(gOtherText_TallPlusAndRightArrow, (x + 6) * 8 + 6, y * 8, 0);
        else
            Menu_PrintTextPixelCoords(gOtherText_TallPlusAndRightArrow, (x + 6) * 8 + 6, y * 8, 1);
        gStringVar1[0] = EXT_CTRL_CODE_BEGIN;
        gStringVar1[1] = 0x14;
        gStringVar1[2] = 0x06;
        ConvertIntToDecimalStringN(gStringVar1 + 3, ePartyMenu2.pmStatGrowths[i], 1, 2);
        Menu_PrintTextPixelCoords(gStringVar1, (x + 6) * 8 + 12, y * 8, 0);
#endif
    }
}

void PrintNewStatsInLevelUpWindow(u8 taskId)
{
    u8 i;

    for (i = 0; i < NUM_STATS; i++)
    {
        u8 x;
        u8 y;
        u32 stat;
        u32 newStatIndex;

        stat = GetMonData(gPartyMenu.pokemon, StatDataTypes[i]);
        newStatIndex = i + 6;
        ePartyMenu2.pmStatGrowths[newStatIndex] = stat;

        x = ((i / 3) * 9) + 11;
        y = ((i % 3) << 1) + 1;

        gStringVar1[0] = EXT_CTRL_CODE_BEGIN;
        gStringVar1[1] = 0x14;
        gStringVar1[2] = 0x06;

        ConvertIntToDecimalStringN(gStringVar1 + 3, ePartyMenu2.pmStatGrowths[newStatIndex], 1, 3);
        Menu_PrintTextPixelCoords(gStringVar1, (x + 6) * 8 + 6, y * 8, 0);
    }
}

void RedrawPokemonInfoInMenu(u8 monIndex, struct Pokemon *pokemon)
{
    u8 statusAndPkrs = GetMonStatusAndPokerus(pokemon);
    u8 icon;

    if (statusAndPkrs == STATUS_PRIMARY_NONE || statusAndPkrs == STATUS_PRIMARY_POKERUS)
        PartyMenuUpdateLevelOrStatus(pokemon, monIndex);

    PartyMenuDoPrintHP(monIndex, IsDoubleBattle(), GetMonData(pokemon, MON_DATA_HP), GetMonData(pokemon, MON_DATA_MAX_HP));
    PartyMenuTryDrawHPBar(monIndex, pokemon);

    icon = GetMonIconSpriteId(gPartyMenu.unk4, monIndex);
    SetMonIconAnim(icon, pokemon);

    task_pc_turn_off(&gUnknown_083769A8[IsDoubleBattle() * 12 + monIndex * 2], 7);
    ePartyMenu2.unk261 = 2;
}

void Task_RareCandy3(u8 taskId)
{
    if (WaitFanfare(0))
    {
        if ((gMain.newKeys & A_BUTTON) || (gMain.newKeys & B_BUTTON))
        {
            u16 learnedMove;
            u16 evolutionSpecies;

            Menu_EraseWindowRect(11, 0, 29, 7);

            learnedMove = MonTryLearningNewMove(gPartyMenu.pokemon, TRUE);
            ePartyMenu2.pmUnk282 = 1;

            switch (learnedMove)
            {
            case 0:
                // No move is learned.
                evolutionSpecies = GetEvolutionTargetSpecies(gPartyMenu.pokemon, 0, 0);
                if (evolutionSpecies != 0)
                {
                    gCB2_AfterEvolution = sub_80A53F8;
                    BeginEvolutionScene(gPartyMenu.pokemon, evolutionSpecies, TRUE, gPartyMenu.primarySelectedMonIndex);
                    DestroyTask(taskId);
                }
                else
                {
                    sub_8070D90(taskId);
                }
                break;
            case 0xFFFF:
                // Mon already knows 4 moves.
                GetMonNickname(gPartyMenu.pokemon, gStringVar1);
                StringCopy(gStringVar2, gMoveNames[gMoveToLearn]);

                StringExpandPlaceholders(gStringVar4, gOtherText_WantsToLearn);
                DisplayPartyMenuMessage(gStringVar4, 1);

                gPartyMenu.unk8 = gMoveToLearn;
                gTasks[taskId].func = sub_806F358;
                break;
            case 0xFFFE:
                // Move was already known by the mon.
                gTasks[taskId].func = TeachMonMoveInPartyMenu;
                break;
            default:
                // Mon automatically learned a move because it knew less than four moves.
                GetMonNickname(gPartyMenu.pokemon, gStringVar1);
                StringCopy(gStringVar2, gMoveNames[learnedMove]);

                StringExpandPlaceholders(gStringVar4, gOtherText_LearnedMove);
                DisplayPartyMenuMessage(gStringVar4, 1);

                gTasks[taskId].func = Task_TeamMonTMMove3;
                break;
            }
        }
    }
}

void TeachMonMoveInPartyMenu(u8 taskId)
{
    u16 learnedMove;
    u16 evolutionSpecies;

    learnedMove = MonTryLearningNewMove(gPartyMenu.pokemon, FALSE);
    switch (learnedMove)
    {
    case 0:
        // No move is learned.
        evolutionSpecies = GetEvolutionTargetSpecies(gPartyMenu.pokemon, 0, 0);
        if (evolutionSpecies != 0)
        {
            gCB2_AfterEvolution = sub_80A53F8;
            BeginEvolutionScene(gPartyMenu.pokemon, evolutionSpecies, TRUE, gPartyMenu.primarySelectedMonIndex);
            DestroyTask(taskId);
        }
        else
        {
            sub_8070D90(taskId);
        }
        break;
    case 0xFFFF:
        // Mon already knows 4 moves.
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        StringCopy(gStringVar2, gMoveNames[gMoveToLearn]);

        StringExpandPlaceholders(gStringVar4, gOtherText_WantsToLearn);
        DisplayPartyMenuMessage(gStringVar4, 1);

        gPartyMenu.unk8 = gMoveToLearn;
        gTasks[taskId].func = sub_806F358;
        break;
    case 0xFFFE:
        // Move was already known by the mon. Go on the the next move to be learned.
        TeachMonMoveInPartyMenu(taskId);
        break;
    default:
        // Mon automatically learned a move because it knew less than four moves.
        GetMonNickname(gPartyMenu.pokemon, gStringVar1);
        StringCopy(gStringVar2, gMoveNames[learnedMove]);

        StringExpandPlaceholders(gStringVar4, gOtherText_LearnedMove);
        DisplayPartyMenuMessage(gStringVar4, 1);

        gTasks[taskId].func = Task_TeamMonTMMove3;
        break;
    }
}

static void sub_8070D90(u8 taskId)
{
    gTasks[gPartyMenu.unk4].func = gPartyMenu.unk10;
    DestroyTask(taskId);
}

void DoEvolutionStoneItemEffect(u8 taskId, u16 evolutionStoneItem, TaskFunc c)
{
    PlaySE(SE_SELECT);

    gTasks[taskId].func = TaskDummy;
    sub_806E8D0(taskId, evolutionStoneItem, c);

    gCB2_AfterEvolution = sub_80A53F8;

    if (ExecuteTableBasedItemEffect__(gPartyMenu.primarySelectedMonIndex, evolutionStoneItem, 0))
    {
        gUnknown_0202E8F4 = 0;
        DisplayPartyMenuMessage(gOtherText_WontHaveAnyEffect, 1);

        CreateTask(sub_806FB0C, 5);
    }
    else
    {
        RemoveBagItem(evolutionStoneItem, 1);
    }
}

u8 GetItemEffectType(u16 item)
{
    const u8 *itemEffect;
    u32 statusCure;

#ifdef UBFIX
    // Enforces that the item ID is within the bounds of gItemEffectTable.
    // In the retail ROM, the otherwise situation will never arise.
    // Nevertheless, GameFreak added this sanity check in FireRed.
    if (!ITEM_HAS_EFFECT(item))
        return ITEM_EFFECT_NONE;
#endif

    // Read the item's effect properties.
    if (item == ITEM_ENIGMA_BERRY)
        itemEffect = gSaveBlock1.enigmaBerry.itemEffect;
    else
        itemEffect = gItemEffectTable[item - ITEM_POTION];

    if ((itemEffect[0] & (ITEM0_DIRE_HIT | ITEM0_X_ATTACK)) || itemEffect[1] || itemEffect[2] || (itemEffect[3] & ITEM3_GUARD_SPEC))
        return ITEM_EFFECT_X_ITEM;
    else if (itemEffect[0] & ITEM0_SACRED_ASH)
        return ITEM_EFFECT_SACRED_ASH;
    else if (itemEffect[3] & ITEM3_LEVEL_UP)
        return ITEM_EFFECT_RAISE_LEVEL;

    statusCure = itemEffect[3] & ITEM3_STATUS_ALL;
    if (statusCure || (itemEffect[0] >> 7))
    {
        if (statusCure == ITEM3_SLEEP)
            return ITEM_EFFECT_CURE_SLEEP;
        else if (statusCure == ITEM3_POISON)
            return ITEM_EFFECT_CURE_POISON;
        else if (statusCure == ITEM3_BURN)
            return ITEM_EFFECT_CURE_BURN;
        else if (statusCure == ITEM3_FREEZE)
            return ITEM_EFFECT_CURE_FREEZE;
        else if (statusCure == ITEM3_PARALYSIS)
            return ITEM_EFFECT_CURE_PARALYSIS;
        else if (statusCure == ITEM3_CONFUSION)
            return ITEM_EFFECT_CURE_CONFUSION;
        else if (itemEffect[0] >> 7 && !statusCure)
            return ITEM_EFFECT_CURE_INFATUATION;
        else
            return ITEM_EFFECT_CURE_ALL_STATUS;
    }

    if (itemEffect[4] & (ITEM4_REVIVE | ITEM4_HEAL_HP))
        return ITEM_EFFECT_HEAL_HP;
    else if (itemEffect[4] & ITEM4_EV_ATK)
        return ITEM_EFFECT_ATK_EV;
    else if (itemEffect[4] & ITEM4_EV_HP)
        return ITEM_EFFECT_HP_EV;
    else if (itemEffect[5] & ITEM5_EV_SPATK)
        return ITEM_EFFECT_SPATK_EV;
    else if (itemEffect[5] & ITEM5_EV_SPDEF)
        return ITEM_EFFECT_SPDEF_EV;
    else if (itemEffect[5] & ITEM5_EV_SPEED)
        return ITEM_EFFECT_SPEED_EV;
    else if (itemEffect[5] & ITEM5_EV_DEF)
        return ITEM_EFFECT_DEF_EV;
    else if (itemEffect[4] & ITEM4_EVO_STONE)
        return ITEM_EFFECT_EVO_STONE;
    else if (itemEffect[4] & ITEM4_PP_UP)
        return ITEM_EFFECT_PP_UP;
    else if (itemEffect[5] & ITEM5_PP_MAX)
        return ITEM_EFFECT_PP_MAX;
    else if (itemEffect[4] & (ITEM4_HEAL_PP | ITEM4_HEAL_PP_ONE))
        return ITEM_EFFECT_HEAL_PP;
    else
        return ITEM_EFFECT_NONE;
}

// Maybe this goes in start_menu.c
#if !DEBUG
void unref_sub_8070F90(void)
{
    FlagSet(FLAG_SYS_POKEDEX_GET);
    FlagSet(FLAG_SYS_POKEMON_GET);
    FlagSet(FLAG_SYS_POKENAV_GET);
}
#endif
