#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "battle_anim_special.h"
#include "battle_interface.h"
#include "blend_palette.h"
#include "decompress.h"
#include "ewram.h"
#include "m4a.h"
#include "main.h"
#include "palette.h"
#include "pokeball.h"
#include "rom_8077ABC.h"
#include "sound.h"
#include "sprite.h"
#include "task.h"
#include "trig.h"
#include "util.h"
#include "constants/items.h"
#include "constants/songs.h"

int gUnknown_03005F0C;
u16 gUnknown_03005F10;
u16 gUnknown_03005F14;

extern s16 gBattleAnimArgs[];
extern u8 gBattleAnimAttacker;
extern u8 gBattleAnimTarget;
extern u8 gHealthboxSpriteIds[];
extern u8 gBattlerSpriteIds[];
extern u16 gBattlerPartyIndexes[];
extern u16 gLastUsedItem;
extern u8 gDoingBattleAnim;
extern u8 gEffectBattler;

extern const u8 gUnknown_08D2EE48[];
extern const u8 gUnknown_08D2EDFC[];
extern const u16 gUnknown_08D2E150[];
extern const struct SpriteTemplate gSpriteTemplates_840B3B4[];
extern const struct SpriteTemplate gSpriteTemplate_8402500;
extern const struct SpriteTemplate gBattleAnimSpriteTemplate_84024E8;
extern const struct CompressedSpriteSheet gBattleAnimPicTable[];
extern const struct CompressedSpritePalette gBattleAnimPaletteTable[];

static void sub_813F300(u8 taskId);
static void sub_813F6CC(u8 taskId);
static void sub_813FD34(u8 taskId);
static void sub_813FD90(struct Sprite *sprite);
static void sub_813FB7C(u8 taskId);
static void sub_813FCBC(u8 taskId);
static void sub_813FDC0(struct Sprite *sprite);
static void sub_813FE70(struct Sprite *sprite);
static void sub_81407B8(struct Sprite *sprite);
static void sub_813FEC8(struct Sprite *sprite);
static void sub_8140014(struct Sprite *sprite);
static void sub_8140058(struct Sprite *sprite);
static void sub_8140410(struct Sprite *sprite);
static void sub_8140158(struct Sprite *sprite);
static void sub_81401A0(struct Sprite *sprite);
static void sub_8140434(struct Sprite *sprite);
static void sub_81405F4(struct Sprite *sprite);
static void sub_8140454(struct Sprite *sprite);
static void sub_81404E4(struct Sprite *sprite);
static void sub_81405C8(struct Sprite *sprite);
static void sub_81406BC(struct Sprite *sprite);
static void sub_81407F4(struct Sprite *sprite);
static void PokeBallOpenParticleAnimation_Step1(struct Sprite *sprite);
static void PokeBallOpenParticleAnimation_Step2(struct Sprite *sprite);
static void DestroyBallOpenAnimationParticle(struct Sprite *sprite);
static void FanOutBallOpenParticles_Step1(struct Sprite *sprite);
static void RepeatBallOpenParticleAnimation_Step1(struct Sprite *sprite);
static void PremierBallOpenParticleAnimation_Step1(struct Sprite *sprite);
static void sub_81413DC(u8 taskId);
static void sub_814146C(u8 taskId);
static void sub_81414BC(u8 taskId);
static void sub_814191C(u8 taskId);
static void sub_8141B20(struct Sprite *sprite);
static void sub_8141B74(struct Sprite *sprite);
static void sub_8141AD8(u8 taskId);
static void sub_8141CBC(struct Sprite *sprite);
static void sub_8141CF4(struct Sprite *sprite);
static void sub_8141D20(struct Sprite *sprite);

extern const u8 gBattleAnimSpriteSheet_Particles[];
const struct CompressedSpriteSheet gBallOpenParticleSpritesheets[] =
{
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6EC},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6ED},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6EE},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6EF},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F0},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F1},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F2},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F3},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F4},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F5},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F6},
    {gBattleAnimSpriteSheet_Particles, 0x100, 0xD6F7},
};

extern const u8 gBattleAnimSpritePalette_136[];
const struct CompressedSpritePalette gBallOpenParticlePalettes[] =
{
    {gBattleAnimSpritePalette_136, 0xD6EC},
    {gBattleAnimSpritePalette_136, 0xD6ED},
    {gBattleAnimSpritePalette_136, 0xD6EE},
    {gBattleAnimSpritePalette_136, 0xD6EF},
    {gBattleAnimSpritePalette_136, 0xD6F0},
    {gBattleAnimSpritePalette_136, 0xD6F1},
    {gBattleAnimSpritePalette_136, 0xD6F2},
    {gBattleAnimSpritePalette_136, 0xD6F3},
    {gBattleAnimSpritePalette_136, 0xD6F4},
    {gBattleAnimSpritePalette_136, 0xD6F5},
    {gBattleAnimSpritePalette_136, 0xD6F6},
    {gBattleAnimSpritePalette_136, 0xD6F7},
};

const union AnimCmd gSpriteAnim_840B318[] =
{
    ANIMCMD_FRAME(0, 1),
    ANIMCMD_FRAME(1, 1),
    ANIMCMD_FRAME(2, 1),
    ANIMCMD_FRAME(0, 1, .hFlip = TRUE),
    ANIMCMD_FRAME(2, 1),
    ANIMCMD_FRAME(1, 1),
    ANIMCMD_JUMP(0),
};

const union AnimCmd gSpriteAnim_840B334[] =
{
    ANIMCMD_FRAME(3, 1),
    ANIMCMD_END,
};

const union AnimCmd gSpriteAnim_840B33C[] =
{
    ANIMCMD_FRAME(4, 1),
    ANIMCMD_END,
};

const union AnimCmd gSpriteAnim_840B344[] =
{
    ANIMCMD_FRAME(5, 1),
    ANIMCMD_END,
};

const union AnimCmd gSpriteAnim_840B34C[] =
{
    ANIMCMD_FRAME(6, 4),
    ANIMCMD_FRAME(7, 4),
    ANIMCMD_JUMP(0),
};

const union AnimCmd gSpriteAnim_840B358[] =
{
    ANIMCMD_FRAME(7, 4),
    ANIMCMD_END,
};

const union AnimCmd *const gSpriteAnimTable_840B360[] =
{
    gSpriteAnim_840B318,
    gSpriteAnim_840B334,
    gSpriteAnim_840B33C,
    gSpriteAnim_840B344,
    gSpriteAnim_840B34C,
    gSpriteAnim_840B358,
};

const u8 gBallOpenParticleAnimNums[] =
{
    0,
    0,
    0,
    5,
    1,
    2,
    2,
    3,
    5,
    5,
    4,
    4,
};

void PokeBallOpenParticleAnimation(u8);
void GreatBallOpenParticleAnimation(u8);
void SafariBallOpenParticleAnimation(u8);
void UltraBallOpenParticleAnimation(u8);
void MasterBallOpenParticleAnimation(u8);
void SafariBallOpenParticleAnimation(u8);
void DiveBallOpenParticleAnimation(u8);
void UltraBallOpenParticleAnimation(u8);
void RepeatBallOpenParticleAnimation(u8);
void TimerBallOpenParticleAnimation(u8);
void GreatBallOpenParticleAnimation(u8);
void PremierBallOpenParticleAnimation(u8);

const TaskFunc gBallOpenParticleAnimationFuncs[] =
{
    PokeBallOpenParticleAnimation,
    GreatBallOpenParticleAnimation,
    SafariBallOpenParticleAnimation,
    UltraBallOpenParticleAnimation,
    MasterBallOpenParticleAnimation,
    SafariBallOpenParticleAnimation,
    DiveBallOpenParticleAnimation,
    UltraBallOpenParticleAnimation,
    RepeatBallOpenParticleAnimation,
    TimerBallOpenParticleAnimation,
    GreatBallOpenParticleAnimation,
    PremierBallOpenParticleAnimation,
};

const struct SpriteTemplate gSpriteTemplates_840B3B4[] =
{
    {
        .tileTag = 55020,
        .paletteTag = 55020,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55021,
        .paletteTag = 55021,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55022,
        .paletteTag = 55022,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55023,
        .paletteTag = 55023,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55024,
        .paletteTag = 55024,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55025,
        .paletteTag = 55025,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55026,
        .paletteTag = 55026,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55027,
        .paletteTag = 55027,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55028,
        .paletteTag = 55028,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55029,
        .paletteTag = 55029,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55030,
        .paletteTag = 55030,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
    {
        .tileTag = 55031,
        .paletteTag = 55031,
        .oam = &gOamData_837DF24,
        .anims = gSpriteAnimTable_840B360,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
};

const u16 gUnknown_0840B4D4[] =
{
    0x7ADF,
    0x7AF0,
    0x53D7,
    0x3FFF,
    0x7297,
    0x67F5,
    0x7B2C,
    0x2B7E,
    0x431F,
    0x7BDD,
    0x2A3F,
    0x293F,
    0x0000,
    0x0201,
    0x0403,
    0x0101,
    0x0100,
    0x0503,
    0x0506,
    0x0004,
};

static void sub_8141C30(struct Sprite *sprite);
const struct SpriteTemplate gBattleAnimSpriteTemplate_840B4FC =
{
    .tileTag = ANIM_TAG_UNUSED_RED_BRICK,
    .paletteTag = ANIM_TAG_UNUSED_RED_BRICK,
    .oam = &gOamData_837DF2C,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_8141C30,
};

void unref_sub_813F0F4(u8 taskId)
{
    struct Struct_sub_8078914 subStruct;
    u8 healthBoxSpriteId;
    u8 battler;
    u8 spriteId1, spriteId2, spriteId3, spriteId4;

    battler = gBattleAnimAttacker;
    gBattle_WIN0H = 0;
    gBattle_WIN0V = 0;
    REG_WININ = 0x3F3F;
    REG_WINOUT = 0x3F3D;
    REG_DISPCNT |= DISPCNT_OBJWIN_ON;
    REG_BLDCNT = 0x3F42;
    REG_BLDALPHA = 0x1000;
    REG_BG1CNT_BITFIELD.priority = 0;
    REG_BG1CNT_BITFIELD.screenSize = 0;
    REG_BG1CNT_BITFIELD.areaOverflowMode = 1;
    REG_BG1CNT_BITFIELD.charBaseBlock = 1;

    healthBoxSpriteId = gHealthboxSpriteIds[battler];
    spriteId1 = gSprites[healthBoxSpriteId].oam.affineParam;
    spriteId2 = gSprites[healthBoxSpriteId].data[5];
    spriteId3 = CreateInvisibleSpriteWithCallback(SpriteCallbackDummy);
    spriteId4 = CreateInvisibleSpriteWithCallback(SpriteCallbackDummy);
    gSprites[healthBoxSpriteId].oam.priority = 1;
    gSprites[spriteId1].oam.priority = 1;
    gSprites[spriteId2].oam.priority = 1;
    gSprites[spriteId3] = gSprites[healthBoxSpriteId];
    gSprites[spriteId4] = gSprites[spriteId1];
    gSprites[spriteId3].oam.objMode = ST_OAM_OBJ_WINDOW;
    gSprites[spriteId4].oam.objMode = ST_OAM_OBJ_WINDOW;
    gSprites[spriteId3].callback = SpriteCallbackDummy;
    gSprites[spriteId4].callback = SpriteCallbackDummy;

    sub_8078914(&subStruct);
    DmaFill32Defvars(3, 0, subStruct.field_4, 0x1000);
    LZDecompressVram(&gUnknown_08D2EE48, subStruct.field_4);
    LZDecompressVram(&gUnknown_08D2EDFC, subStruct.field_0);
    LoadCompressedPalette(gUnknown_08D2E150, subStruct.field_8 << 4, 32);

    gBattle_BG1_X = -gSprites[spriteId3].x + 32;
    gBattle_BG1_Y = -gSprites[spriteId3].y - 32;
    gTasks[taskId].data[1] = 640;
    gTasks[taskId].data[0] = spriteId3;
    gTasks[taskId].data[2] = spriteId4;
    gTasks[taskId].func = sub_813F300;
}

static void sub_813F300(u8 taskId)
{
    struct Struct_sub_8078914 subStruct;
    u8 spriteId1, spriteId2;
    u8 battler;

    battler = gBattleAnimAttacker;
    gTasks[taskId].data[13] += gTasks[taskId].data[1];
    gBattle_BG1_Y += (u16)gTasks[taskId].data[13] >> 8;
    gTasks[taskId].data[13] &= 0xFF;

    switch (gTasks[taskId].data[15])
    {
    case 0:
        if (gTasks[taskId].data[11]++ > 1)
        {
            gTasks[taskId].data[11] = 0;
            gTasks[taskId].data[12]++;
            REG_BLDALPHA = ((16 - gTasks[taskId].data[12]) << 8) | gTasks[taskId].data[12];
            if (gTasks[taskId].data[12] == 8)
                gTasks[taskId].data[15]++;
        }
        break;
    case 1:
        if (++gTasks[taskId].data[10] == 30)
            gTasks[taskId].data[15]++;
        break;
    case 2:
        if (gTasks[taskId].data[11]++ > 1)
        {
            gTasks[taskId].data[11] = 0;
            gTasks[taskId].data[12]--;
            REG_BLDALPHA = ((16 - gTasks[taskId].data[12]) << 8) | gTasks[taskId].data[12];
            if (gTasks[taskId].data[12] == 0)
            {
                sub_8076464(0);
                gBattle_WIN0H = 0;
                gBattle_WIN0V = 0;
                REG_WININ = 0x3F3F;
                REG_WINOUT = 0x3F3F;
                if (!IsContest())
                    REG_BG1CNT_BITFIELD.charBaseBlock = 0;

                REG_DISPCNT ^= DISPCNT_OBJWIN_ON;
                REG_BLDCNT = 0;
                REG_BLDALPHA = 0;
                DestroySprite(&gSprites[gTasks[taskId].data[0]]);
                DestroySprite(&gSprites[gTasks[taskId].data[2]]);
                sub_8078914(&subStruct);
                DmaFill32Defvars(3, 0, subStruct.field_4, 0x800);
                REG_BG1CNT_BITFIELD.areaOverflowMode = 0;
                spriteId1 = gSprites[gHealthboxSpriteIds[battler]].oam.affineParam;
                spriteId2 = gSprites[gHealthboxSpriteIds[battler]].data[5];
                gSprites[gHealthboxSpriteIds[battler]].oam.priority = 1;
                gSprites[spriteId1].oam.priority = 1;
                gSprites[spriteId2].oam.priority = 1;
                DestroyAnimVisualTask(taskId);
            }
        }
        break;
    }
}

void sub_813F4EC(u8 taskId)
{
    u8 healthBoxSpriteId;
    u8 spriteId1, spriteId2;
    u8 paletteNum1, paletteNum2;
    u16 offset1, offset2;

    healthBoxSpriteId = gHealthboxSpriteIds[gBattleAnimAttacker];
    spriteId1 = gSprites[healthBoxSpriteId].oam.affineParam;
    spriteId2 = gSprites[healthBoxSpriteId].data[5];
    paletteNum1 = AllocSpritePalette(0xD709);
    paletteNum2 = AllocSpritePalette(0xD70A);

    offset1 = (gSprites[healthBoxSpriteId].oam.paletteNum * 16) + 0x100;
    offset2 = (gSprites[spriteId2].oam.paletteNum * 16) + 0x100;
    LoadPalette(&gPlttBufferUnfaded[offset1], paletteNum1 * 16 + 0x100, 0x20);
    LoadPalette(&gPlttBufferUnfaded[offset2], paletteNum2 * 16 + 0x100, 0x20);

    gSprites[healthBoxSpriteId].oam.paletteNum = paletteNum1;
    gSprites[spriteId1].oam.paletteNum = paletteNum1;
    gSprites[spriteId2].oam.paletteNum = paletteNum2;
    DestroyAnimVisualTask(taskId);
}

void sub_813F5E8(u8 taskId)
{
    u8 healthBoxSpriteId;
    u8 spriteId1, spriteId2;
    u8 paletteIndex1, paletteIndex2;

    healthBoxSpriteId = gHealthboxSpriteIds[gBattleAnimAttacker];
    spriteId1 = gSprites[healthBoxSpriteId].oam.affineParam;
    spriteId2 = gSprites[healthBoxSpriteId].data[5];

    FreeSpritePaletteByTag(0xD709);
    FreeSpritePaletteByTag(0xD70A);
    paletteIndex1 = IndexOfSpritePaletteTag(0xD6FF);
    paletteIndex2 = IndexOfSpritePaletteTag(0xD704);
    gSprites[healthBoxSpriteId].oam.paletteNum = paletteIndex1;
    gSprites[spriteId1].oam.paletteNum = paletteIndex1;
    gSprites[spriteId2].oam.paletteNum = paletteIndex2;

    DestroyAnimVisualTask(taskId);
}

void sub_813F6A0(u8 taskId)
{
    gTasks[taskId].data[10] = gBattleAnimArgs[0];
    gTasks[taskId].data[11] = gBattleAnimArgs[1];
    gTasks[taskId].func = sub_813F6CC;
}

static void sub_813F6CC(u8 taskId)
{
    u8 paletteNum;
    int paletteOffset, colorOffset;

    gTasks[taskId].data[0]++;
    if (gTasks[taskId].data[0]++ >= gTasks[taskId].data[11])
    {
        gTasks[taskId].data[0] = 0;
        paletteNum = IndexOfSpritePaletteTag(0xD709);
        colorOffset = gTasks[taskId].data[10] == 0 ? 6 : 2;
        switch (gTasks[taskId].data[1])
        {
        case 0:
            gTasks[taskId].data[2] += 2;
            if (gTasks[taskId].data[2] > 16)
                gTasks[taskId].data[2] = 16;

            paletteOffset = paletteNum * 16 + 0x100;
            BlendPalette(paletteOffset + colorOffset, 1, gTasks[taskId].data[2], RGB(20, 27, 31));
            if (gTasks[taskId].data[2] == 16)
                gTasks[taskId].data[1]++;
            break;
        case 1:
            gTasks[taskId].data[2] -= 2;
            if (gTasks[taskId].data[2] < 0)
                gTasks[taskId].data[2] = 0;

            paletteOffset = paletteNum * 16 + 0x100;
            BlendPalette(paletteOffset + colorOffset, 1, gTasks[taskId].data[2], RGB(20, 27, 31));
            if (gTasks[taskId].data[2] == 0)
                DestroyAnimVisualTask(taskId);
            break;
        }
    }
}

void sub_813F798(u8 taskId)
{
    u8 spriteId;

    spriteId = gBattlerSpriteIds[gBattleAnimAttacker];
    switch (gTasks[taskId].data[0])
    {
    case 0:
        PrepareBattlerSpriteForRotScale(spriteId, 0);
        gTasks[taskId].data[10] = 0x100;
        gTasks[taskId].data[0]++;
        break;
    case 1:
        gTasks[taskId].data[10] += 0x30;
        obj_id_set_rotscale(spriteId, gTasks[taskId].data[10], gTasks[taskId].data[10], 0);
        sub_8079A64(spriteId);
        if (gTasks[taskId].data[10] >= 0x2D0)
            gTasks[taskId].data[0]++;
        break;
    case 2:
        sub_8078F40(spriteId);
        gSprites[spriteId].invisible = TRUE;
        DestroyAnimVisualTask(taskId);
        break;
    }
}

void sub_813F844(u8 taskId)
{
    u8 spriteId;
    u16 ball;
    u8 ballIndex;
    u8 x, y;
    u8 priority, subpriority;
    u32 selectedPalettes;

    spriteId = gBattlerSpriteIds[gBattleAnimAttacker];
    if (GetBattlerSide(gBattleAnimAttacker) == B_SIDE_PLAYER)
        ball = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_POKEBALL);
    else
        ball = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_POKEBALL);

    ballIndex = ball_number_to_ball_processing_index(ball);
    switch (gTasks[taskId].data[0])
    {
    case 0:
        x = GetBattlerSpriteCoord(gBattleAnimAttacker, 0);
        y = GetBattlerSpriteCoord(gBattleAnimAttacker, 1);
        priority = gSprites[spriteId].oam.priority;
        subpriority = gSprites[spriteId].subpriority;
        gTasks[taskId].data[10] = AnimateBallOpenParticles(x, y + 32, priority, subpriority, ballIndex);
        selectedPalettes = sub_80791A8(1, 0, 0, 0, 0, 0, 0);
        gTasks[taskId].data[11] = sub_8141314(0, gBattleAnimAttacker, selectedPalettes, ballIndex);
        gTasks[taskId].data[0]++;
        break;
    case 1:
        if (!gTasks[gTasks[taskId].data[10]].isActive && !gTasks[gTasks[taskId].data[11]].isActive)
            DestroyAnimVisualTask(taskId);
        break;
    }
}

void sub_813F990(u8 taskId)
{
    u8 ballIndex = ball_number_to_ball_processing_index(gLastUsedItem);
    LoadBallGraphics(ballIndex);
    DestroyAnimVisualTask(taskId);
}

void sub_813F9B8(u8 taskId)
{
    u8 ballIndex = ball_number_to_ball_processing_index(gLastUsedItem);
    FreeBallGraphics(ballIndex);
    DestroyAnimVisualTask(taskId);
}

void sub_813F9E0(u8 taskId)
{
    if (ewram17840.unk8 == 5)
        gBattleAnimArgs[7] = -1;
    else
        gBattleAnimArgs[7] = 0;

    DestroyAnimVisualTask(taskId);
}

u8 ball_number_to_ball_processing_index(u16 ballItem)
{
    switch (ballItem)
    {
    case ITEM_MASTER_BALL:
        return 4;
    case ITEM_ULTRA_BALL:
        return 3;
    case ITEM_GREAT_BALL:
        return 1;
    case ITEM_SAFARI_BALL:
        return 2;
    case ITEM_NET_BALL:
        return 5;
    case ITEM_DIVE_BALL:
        return 6;
    case ITEM_NEST_BALL:
        return 7;
    case ITEM_REPEAT_BALL:
        return 8;
    case ITEM_TIMER_BALL:
        return 9;
    case ITEM_LUXURY_BALL:
        return 10;
    case ITEM_PREMIER_BALL:
        return 11;
    case ITEM_POKE_BALL:
    default:
        return 0;
    }
}

void sub_813FA94(u8 taskId)
{
    u8 ballIndex;
    u8 spriteId;

    ballIndex = ball_number_to_ball_processing_index(gLastUsedItem);
    spriteId = CreateSprite(&gBallSpriteTemplates[ballIndex], 32, 80, 29);
    gSprites[spriteId].data[0] = 34;
    gSprites[spriteId].data[1] = GetBattlerSpriteCoord(gBattleAnimTarget, 0);
    gSprites[spriteId].data[2] = GetBattlerSpriteCoord(gBattleAnimTarget, 1) - 16;
    gSprites[spriteId].callback = sub_813FD90;
    ewram17840.unk9_1 = gSprites[gBattlerSpriteIds[gBattleAnimTarget]].invisible;
    gTasks[taskId].data[0] = spriteId;
    gTasks[taskId].func = sub_813FB7C;
}

static void sub_813FB7C(u8 taskId)
{
    u8 spriteId = gTasks[taskId].data[0];
    if ((u16)gSprites[spriteId].data[0] == 0xFFFF)
        DestroyAnimVisualTask(taskId);
}

void sub_813FBB8(u8 taskId)
{
    int x, y;
    u8 ballIndex;
    u8 subpriority;
    u8 spriteId;

    if (gBattleTypeFlags & BATTLE_TYPE_WALLY_TUTORIAL)
    {
        x = 32;
        y = 11;
    }
    else
    {
        x = 23;
        y = 5;
    }

    ballIndex = ball_number_to_ball_processing_index(gLastUsedItem);
    subpriority = GetBattlerSubpriority(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)) + 1;
    spriteId = CreateSprite(&gBallSpriteTemplates[ballIndex], x + 32, y | 80, subpriority);
    gSprites[spriteId].data[0] = 34;
    gSprites[spriteId].data[1] = GetBattlerSpriteCoord(gBattleAnimTarget, 0);
    gSprites[spriteId].data[2] = GetBattlerSpriteCoord(gBattleAnimTarget, 1) - 16;
    gSprites[spriteId].callback = SpriteCallbackDummy;
    StartSpriteAnim(&gSprites[gBattlerSpriteIds[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)]], 1);
    gTasks[taskId].data[0] = spriteId;
    gTasks[taskId].func = sub_813FCBC;
}

static void sub_813FCBC(u8 taskId)
{
    if (gSprites[gBattlerSpriteIds[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)]].animCmdIndex == 1)
    {
        PlaySE12WithPanning(SE_BALL_THROW, 0);
        gSprites[gTasks[taskId].data[0]].callback = sub_813FD90;
        CreateTask(sub_813FD34, 10);
        gTasks[taskId].func = sub_813FB7C;
    }
}

static void sub_813FD34(u8 taskId)
{
    if (gSprites[gBattlerSpriteIds[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)]].animEnded)
    {
        StartSpriteAnim(&gSprites[gBattlerSpriteIds[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)]], 0);
        DestroyTask(taskId);
    }
}

static void sub_813FD90(struct Sprite *sprite)
{
    u16 temp = sprite->data[1];
    u16 temp2 = sprite->data[2];
    sprite->data[1] = sprite->x;
    sprite->data[2] = temp;
    sprite->data[3] = sprite->y;
    sprite->data[4] = temp2;
    sprite->data[5] = -40;
    InitAnimArcTranslation(sprite);
    sprite->callback = sub_813FDC0;
}

static void sub_813FDC0(struct Sprite *sprite)
{
    int i;
    u8 ballIndex;
    int ballIndex2; // extra var needed to match

    if (TranslateAnimArc(sprite))
    {
        if (ewram17840.unk8 == 5)
        {
            sprite->callback = sub_81407B8;
        }
        else
        {
            StartSpriteAnim(sprite, 1);
            sprite->x += sprite->x2;
            sprite->y += sprite->y2;
            sprite->x2 = 0;
            sprite->y2 = 0;

            for (i = 0; i < 8; i++)
            {
                sprite->data[i] = 0;
            }

            sprite->data[5] = 0;
            sprite->callback = sub_813FE70;
            ballIndex = ball_number_to_ball_processing_index(gLastUsedItem);
            ballIndex2 = ballIndex;
            if (ballIndex2 > 11)
                return;
            if (ballIndex2 < 0)
                return;

            AnimateBallOpenParticles(sprite->x, sprite->y - 5, 1, 28, ballIndex);
            sub_8141314(0, gBattleAnimTarget, 14, ballIndex);
        }
    }
}

static void sub_813FE70(struct Sprite *sprite)
{
    if (++sprite->data[5] == 10)
    {
        sprite->data[5] = CreateTask(TaskDummy, 50);
        sprite->callback = sub_813FEC8;
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].data[1] = 0;
    }
}

static void sub_813FEC8(struct Sprite *sprite)
{
    u8 spriteId;
    u8 taskId;

    spriteId = gBattlerSpriteIds[gBattleAnimTarget];
    taskId = sprite->data[5];

    if (++gTasks[taskId].data[1] == 11)
        PlaySE(SE_BALL_TRADE);

    switch (gTasks[taskId].data[0])
    {
    case 0:
        PrepareBattlerSpriteForRotScale(spriteId, 0);
        gTasks[taskId].data[10] = 256;
        gUnknown_03005F0C = 28;
        gUnknown_03005F14 = (gSprites[spriteId].y + gSprites[spriteId].y2) - (sprite->y + sprite->y2);
        gUnknown_03005F10 = (u32)(gUnknown_03005F14 * 256) / 28;
        gTasks[taskId].data[2] = gUnknown_03005F10;
        gTasks[taskId].data[0]++;
        break;
    case 1:
        gTasks[taskId].data[10] += 0x20;
        obj_id_set_rotscale(spriteId, gTasks[taskId].data[10], gTasks[taskId].data[10], 0);
        gTasks[taskId].data[3] += gTasks[taskId].data[2];
        gSprites[spriteId].y2 = -gTasks[taskId].data[3] >> 8;
        if (gTasks[taskId].data[10] >= 0x480)
            gTasks[taskId].data[0]++;
        break;
    case 2:
        sub_8078F40(spriteId);
        gSprites[spriteId].invisible = TRUE;
        gTasks[taskId].data[0]++;
        break;
    default:
        if (gTasks[taskId].data[1] > 10)
        {
            DestroyTask(taskId);
            StartSpriteAnim(sprite, 2);
            sprite->data[5] = 0;
            sprite->callback = sub_8140014;
        }
        break;
    }
}

static void sub_8140014(struct Sprite *sprite)
{
    int angle;

    if (sprite->animEnded)
    {
        sprite->data[3] = 0;
        sprite->data[4] = 32;
        sprite->data[5] = 0;
        angle = 0;
        sprite->y += Cos(angle, 32);
        sprite->y2 = -Cos(angle, sprite->data[4]);
        sprite->callback = sub_8140058;
    }
}

static void sub_8140058(struct Sprite *sprite)
{
    bool8 lastBounce;
    int bounceCount;

    lastBounce = 0;

    switch (sprite->data[3] & 0xFF)
    {
    case 0:
        sprite->y2 = -Cos(sprite->data[5], sprite->data[4]);
        sprite->data[5] += (sprite->data[3] >> 8) + 4;
        if (sprite->data[5] >= 64)
        {
            sprite->data[4] -= 10;
            sprite->data[3] += 257;

            bounceCount = sprite->data[3] >> 8;
            if (bounceCount == 4)
                lastBounce = 1;

            // Play a different sound effect for each pokeball bounce.
            switch (bounceCount)
            {
            case 1:
                PlaySE(SE_BALL_BOUNCE_1);
                break;
            case 2:
                PlaySE(SE_BALL_BOUNCE_2);
                break;
            case 3:
                PlaySE(SE_BALL_BOUNCE_3);
                break;
            default:
                PlaySE(SE_BALL_BOUNCE_4);
                break;
            }
        }
        break;
    case 1:
        sprite->y2 = -Cos(sprite->data[5], sprite->data[4]);
        sprite->data[5] -= (sprite->data[3] >> 8) + 4;
        if (sprite->data[5] <= 0)
        {
            sprite->data[5] = 0;
            sprite->data[3] &= -0x100;
        }
        break;
    }

    if (lastBounce)
    {
        sprite->data[3] = 0;
        sprite->y += Cos(64, 32);
        sprite->y2 = 0;
        if (ewram17840.unk8 == 0)
        {
            sprite->data[5] = 0;
            sprite->callback = sub_8140410;
        }
        else
        {
            sprite->callback = sub_8140158;
            sprite->data[4] = 1;
            sprite->data[5] = 0;
        }
    }
}

static void sub_8140158(struct Sprite *sprite)
{
    if (++sprite->data[3] == 31)
    {
        sprite->data[3] = 0;
        sprite->affineAnimPaused = 1;
        StartSpriteAffineAnim(sprite, 1);
        ewram17840.unkC = 0;
        sprite->callback = sub_81401A0;
        PlaySE(SE_BALL);
    }
}

static void sub_81401A0(struct Sprite *sprite)
{
    s8 state;
    u16 var0;

    switch (sprite->data[3] & 0xFF)
    {
    case 0:
        if ((s16)ewram17840.unkC > 0xFF)
        {
            sprite->x2 += sprite->data[4];
            ewram17840.unkC &= 0xFF;
        }
        else
        {
            ewram17840.unkC += 0xB0;
        }

        sprite->data[5]++;
        sprite->affineAnimPaused = 0;
        var0 = sprite->data[5] + 7;
        if (var0 > 14)
        {
            ewram17840.unkC = 0;
            sprite->data[3]++;
            sprite->data[5] = 0;
        }
        break;
    case 1:
        if (++sprite->data[5] == 1)
        {
            sprite->data[5] = 0;
            sprite->data[4] = -sprite->data[4];
            sprite->data[3]++;
            sprite->affineAnimPaused = 0;
            if (sprite->data[4] < 0)
                ChangeSpriteAffineAnim(sprite, 2);
            else
                ChangeSpriteAffineAnim(sprite, 1);
        }
        else
        {
            sprite->affineAnimPaused = 1;
        }
        break;
    case 2:
        if ((s16)ewram17840.unkC > 0xFF)
        {
            sprite->x2 += sprite->data[4];
            ewram17840.unkC &= 0xFF;
        }
        else
        {
            ewram17840.unkC += 0xB0;
        }

        sprite->data[5]++;
        sprite->affineAnimPaused = 0;
        var0 = sprite->data[5] + 12;
        if (var0 > 24)
        {
            ewram17840.unkC = 0;
            sprite->data[3]++;
            sprite->data[5] = 0;
        }
        break;
    case 3:
        if (sprite->data[5]++ < 0)
        {
            sprite->affineAnimPaused = 1;
            break;
        }

        sprite->data[5] = 0;
        sprite->data[4] = -sprite->data[4];
        sprite->data[3]++;
        sprite->affineAnimPaused = 0;
        if (sprite->data[4] < 0)
            ChangeSpriteAffineAnim(sprite, 2);
        else
            ChangeSpriteAffineAnim(sprite, 1);
        // fall through
    case 4:
        if ((s16)ewram17840.unkC > 0xFF)
        {
            sprite->x2 += sprite->data[4];
            ewram17840.unkC &= 0xFF;
        }
        else
        {
            ewram17840.unkC += 0xB0;
        }

        sprite->data[5]++;
        sprite->affineAnimPaused = 0;
        var0 = sprite->data[5] + 4;
        if (var0 > 8)
        {
            ewram17840.unkC = 0;
            sprite->data[3]++;
            sprite->data[5] = 0;
            sprite->data[4] = -sprite->data[4];
        }
        break;
    case 5:
        sprite->data[3] += 0x100;
        state = sprite->data[3] >> 8;
        if (state == ewram17840.unk8)
        {
            sprite->affineAnimPaused = 1;
            sprite->callback = sub_8140410;
        }
        else
        {
            if (ewram17840.unk8 == 4 && state == 3)
            {
                sprite->callback = sub_8140434;
                sprite->affineAnimPaused = 1;
            }
            else
            {
                sprite->data[3]++;
                sprite->affineAnimPaused = 1;
            }
        }
        break;
    case 6:
    default:
        if (++sprite->data[5] == 31)
        {
            sprite->data[5] = 0;
            sprite->data[3] &= -0x100;
            StartSpriteAffineAnim(sprite, 3);
            if (sprite->data[4] < 0)
                StartSpriteAffineAnim(sprite, 2);
            else
                StartSpriteAffineAnim(sprite, 1);

            PlaySE(SE_BALL);
        }
        break;
    }
}

static void sub_8140410(struct Sprite *sprite)
{
    if (++sprite->data[5] == 31)
    {
        sprite->data[5] = 0;
        sprite->callback = sub_81405F4;
    }
}

static void sub_8140434(struct Sprite *sprite)
{
    sprite->animPaused = 1;
    sprite->callback = sub_8140454;
    sprite->data[3] = 0;
    sprite->data[4] = 0;
    sprite->data[5] = 0;
}

static void sub_8140454(struct Sprite *sprite)
{
    u8 *battler = &gBattleAnimTarget;

    sprite->data[4]++;
    if (sprite->data[4] == 40)
        return;

    if (sprite->data[4] == 95)
    {
        gDoingBattleAnim = 0;
        UpdateOamPriorityInAllHealthboxes(1);
        m4aMPlayAllStop();
        PlaySE(MUS_EVOLVED);
    }
    else if (sprite->data[4] == 315)
    {
        FreeOamMatrix(gSprites[gBattlerSpriteIds[*battler]].oam.matrixNum);
        DestroySprite(&gSprites[gBattlerSpriteIds[*battler]]);
        sprite->data[0] = 0;
        sprite->callback = sub_81404E4;
    }
}

static void sub_81404E4(struct Sprite *sprite)
{
    u8 paletteIndex;

    switch (sprite->data[0])
    {
    case 0:
        sprite->data[1] = 0;
        sprite->data[2] = 0;
        sprite->oam.objMode = ST_OAM_OBJ_BLEND;
        REG_BLDCNT = 0x3F40;
        REG_BLDALPHA = 0x0010;
        paletteIndex = IndexOfSpritePaletteTag(sprite->template->paletteTag);
        BeginNormalPaletteFade(1 << (paletteIndex + 0x10), 0, 0, 16, RGB(31, 31, 31));
        sprite->data[0]++;
        break;
    case 1:
        if (sprite->data[1]++ > 0)
        {
            sprite->data[1] = 0;
            sprite->data[2]++;
            REG_BLDALPHA = (sprite->data[2] << 8) | (16 - sprite->data[2]);
            if (sprite->data[2] == 16)
                sprite->data[0]++;
        }
        break;
    case 2:
        sprite->invisible = TRUE;
        sprite->data[0]++;
        break;
    default:
        if (!gPaletteFade.active)
        {
            REG_BLDCNT = 0;
            REG_BLDALPHA = 0;
            sprite->data[0] = 0;
            sprite->callback = sub_81405C8;
        }
        break;
    }
}

static void sub_81405C8(struct Sprite *sprite)
{
    if (sprite->data[0] == 0)
    {
        sprite->data[0] = -1;
    }
    else
    {
        FreeSpriteOamMatrix(sprite);
        DestroySprite(sprite);
    }
}

// fakematching. I think the return type of ball_number_to_ball_processing_index()
// is wrong because of the weird required casting.
static void sub_81405F4(struct Sprite *sprite)
{
    u8 ballIndex;
    int ballIndex2; // extra var needed to match

    StartSpriteAnim(sprite, 1);
    StartSpriteAffineAnim(sprite, 0);
    sprite->callback = sub_81406BC;

    ballIndex = ball_number_to_ball_processing_index(gLastUsedItem);
    ballIndex2 = ballIndex;
    if (ballIndex2 > 11)
        goto LABEL;
    if (ballIndex2 < 0)
        goto LABEL;

    AnimateBallOpenParticles(sprite->x, sprite->y - 5, 1, 28, ballIndex);
    sub_8141314(1, gBattleAnimTarget, 14, ballIndex);

    LABEL:
    gSprites[gBattlerSpriteIds[gBattleAnimTarget]].invisible = FALSE;
    StartSpriteAffineAnim(&gSprites[gBattlerSpriteIds[gBattleAnimTarget]], 1);
    AnimateSprite(&gSprites[gBattlerSpriteIds[gBattleAnimTarget]]);
    gSprites[gBattlerSpriteIds[gBattleAnimTarget]].data[1] = 0x1000;
}

static void sub_81406BC(struct Sprite *sprite)
{
    int next = FALSE;

    if (sprite->animEnded)
        sprite->invisible = TRUE;
    
    if (gSprites[gBattlerSpriteIds[gBattleAnimTarget]].affineAnimEnded)
    {
        StartSpriteAffineAnim(&gSprites[gBattlerSpriteIds[gBattleAnimTarget]], 0);
        next = TRUE;
    }
    else
    {
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].data[1] -= 288;
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].y2 = gSprites[gBattlerSpriteIds[gBattleAnimTarget]].data[1] >> 8;   
    }

    if (sprite->animEnded && next)
    {
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].y2 = 0;
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].invisible = ewram17840.unk9_1;
        sprite->data[0] = 0;
        sprite->callback = sub_81405C8;
        gDoingBattleAnim = 0;
        UpdateOamPriorityInAllHealthboxes(1);
    }
}

static void sub_81407B8(struct Sprite *sprite)
{
    int i;

    sprite->x += sprite->x2;
    sprite->y += sprite->y2;
    sprite->y2 = 0;
    sprite->x2 = 0;
    for (i = 0; i < 6; i++)
        sprite->data[i] = 0;

    sprite->callback = sub_81407F4;
}

static void sub_81407F4(struct Sprite *sprite)
{
    s16 var0 = sprite->data[0] + 0x800;
    s16 var1 = sprite->data[1] + 0x680;
    sprite->x2 -= var1 >> 8;
    sprite->y2 += var0 >> 8;
    sprite->data[0] = (sprite->data[0] + 0x800) & 0xFF;
    sprite->data[1] = (sprite->data[1] + 0x680) & 0xFF;

    if (sprite->y + sprite->y2 > 160
     || sprite->x + sprite->x2 < -8)
    {
        sprite->data[0] = 0;
        sprite->callback = sub_81405C8;
        gDoingBattleAnim = 0;
        UpdateOamPriorityInAllHealthboxes(1);
    }
}

u8 AnimateBallOpenParticles(u8 x, u8 y, u8 priority, u8 subpriority, u8 ballIndex)
{
    u8 taskId;

    if (GetSpriteTileStartByTag(gBallOpenParticleSpritesheets[ballIndex].tag) == 0xFFFF)
    {
        LoadCompressedObjectPic(&gBallOpenParticleSpritesheets[ballIndex]);
        LoadCompressedObjectPalette(&gBallOpenParticlePalettes[ballIndex]);
    }

    taskId = CreateTask(gBallOpenParticleAnimationFuncs[ballIndex], 5);
    gTasks[taskId].data[1] = x;
    gTasks[taskId].data[2] = y;
    gTasks[taskId].data[3] = priority;
    gTasks[taskId].data[4] = subpriority;
    gTasks[taskId].data[15] = ballIndex;
    PlaySE(SE_BALL_OPEN);
    if (gMain.inBattle)
        ewram17840.unkA++;

    return taskId;
}

void PokeBallOpenParticleAnimation(u8 taskId)
{
    u8 spriteId;
    u8 x, y;
    u8 priority, subpriority;
    u8 ballIndex;
    u8 var0;

    ballIndex = gTasks[taskId].data[15];
    if (gTasks[taskId].data[0] < 16)
    {
        x = gTasks[taskId].data[1];
        y = gTasks[taskId].data[2];
        priority = gTasks[taskId].data[3];
        subpriority = gTasks[taskId].data[4];

        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = PokeBallOpenParticleAnimation_Step1;
        gSprites[spriteId].oam.priority = priority;

        var0 = (u8)gTasks[taskId].data[0];
        if (var0 >= 8)
            var0 -= 8;

        gSprites[spriteId].data[0] = var0 * 32;
        if (gTasks[taskId].data[0] == 15)
        {
            gSprites[spriteId].data[7] = 1;
            DestroyTask(taskId);
            return;
        }
    }

    gTasks[taskId].data[0]++;
}

static void PokeBallOpenParticleAnimation_Step1(struct Sprite *sprite)
{
    if (sprite->data[1] == 0)
        sprite->callback = PokeBallOpenParticleAnimation_Step2;
    else
        sprite->data[1]--;
}

static void PokeBallOpenParticleAnimation_Step2(struct Sprite *sprite)
{
    sprite->x2 = Sin(sprite->data[0], sprite->data[1]);
    sprite->y2 = Cos(sprite->data[0], sprite->data[1]);
    sprite->data[1] += 2;
    if (sprite->data[1] == 50)
        DestroyBallOpenAnimationParticle(sprite);
}

void TimerBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (i = 0; i < 8; i++)
    {
        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = FanOutBallOpenParticles_Step1;
        gSprites[spriteId].oam.priority = priority;
        gSprites[spriteId].data[0] = i * 32;
        gSprites[spriteId].data[4] = 10;
        gSprites[spriteId].data[5] = 2;
        gSprites[spriteId].data[6] = 1;
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

void DiveBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (i = 0; i < 8; i++)
    {
        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = FanOutBallOpenParticles_Step1;
        gSprites[spriteId].oam.priority = priority;
        gSprites[spriteId].data[0] = i * 32;
        gSprites[spriteId].data[4] = 10;
        gSprites[spriteId].data[5] = 1;
        gSprites[spriteId].data[6] = 2;
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

void SafariBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (i = 0; i < 8; i++)
    {
        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = FanOutBallOpenParticles_Step1;
        gSprites[spriteId].oam.priority = priority;
        gSprites[spriteId].data[0] = i * 32;
        gSprites[spriteId].data[4] = 4;
        gSprites[spriteId].data[5] = 1;
        gSprites[spriteId].data[6] = 1;
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

void UltraBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (i = 0; i < 10; i++)
    {
        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = FanOutBallOpenParticles_Step1;
        gSprites[spriteId].oam.priority = priority;
        gSprites[spriteId].data[0] = i * 25;
        gSprites[spriteId].data[4] = 5;
        gSprites[spriteId].data[5] = 1;
        gSprites[spriteId].data[6] = 1;
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

void GreatBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    if (gTasks[taskId].data[7])
    {
        gTasks[taskId].data[7]--;
    }
    else
    {
        ballIndex = gTasks[taskId].data[15];
        x = gTasks[taskId].data[1];
        y = gTasks[taskId].data[2];
        priority = gTasks[taskId].data[3];
        subpriority = gTasks[taskId].data[4];

        for (i = 0; i < 8; i++)
        {
            spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
            gSprites[spriteId].callback = FanOutBallOpenParticles_Step1;
            gSprites[spriteId].oam.priority = priority;
            gSprites[spriteId].data[0] = i * 32;
            gSprites[spriteId].data[4] = 8;
            gSprites[spriteId].data[5] = 2;
            gSprites[spriteId].data[6] = 2;
        }

        gTasks[taskId].data[7] = 8;
        if (++gTasks[taskId].data[0] == 2)
        {
            gSprites[spriteId].data[7] = 1;
            DestroyTask(taskId);
        }
    }
}

static void FanOutBallOpenParticles_Step1(struct Sprite *sprite)
{
    sprite->x2 = Sin(sprite->data[0], sprite->data[1]);
    sprite->y2 = Cos(sprite->data[0], sprite->data[2]);
    sprite->data[0] = (sprite->data[0] + sprite->data[4]) & 0xFF;
    sprite->data[1] += sprite->data[5];
    sprite->data[2] += sprite->data[6];
    if (++sprite->data[3] == 51)
        DestroyBallOpenAnimationParticle(sprite);
}

void RepeatBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (i = 0; i < 12; i++)
    {
        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = RepeatBallOpenParticleAnimation_Step1;
        gSprites[spriteId].oam.priority = priority;
        gSprites[spriteId].data[0] = i * 21;
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

static void RepeatBallOpenParticleAnimation_Step1(struct Sprite *sprite)
{
    sprite->x2 = Sin(sprite->data[0], sprite->data[1]);
    sprite->y2 = Cos(sprite->data[0], Sin(sprite->data[0], sprite->data[2]));
    sprite->data[0] = (sprite->data[0] + 6) & 0xFF;
    sprite->data[1]++;
    sprite->data[2]++;
    if (++sprite->data[3] == 51)
        DestroyBallOpenAnimationParticle(sprite);
}

void MasterBallOpenParticleAnimation(u8 taskId)
{
    u8 i, j;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (j = 0; j < 2; j++)
    {
        for (i = 0; i < 8; i++)
        {
            spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
            gSprites[spriteId].callback = FanOutBallOpenParticles_Step1;
            gSprites[spriteId].oam.priority = priority;
            gSprites[spriteId].data[0] = i * 32;
            gSprites[spriteId].data[4] = 8;

            if (j == 0)
            {
                gSprites[spriteId].data[5] = 2;
                gSprites[spriteId].data[6] = 1;
            }
            else
            {
                gSprites[spriteId].data[5] = 1;
                gSprites[spriteId].data[6] = 2;
            }
        }
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

void PremierBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballIndex;
    u8 spriteId;

    ballIndex = gTasks[taskId].data[15];
    x = gTasks[taskId].data[1];
    y = gTasks[taskId].data[2];
    priority = gTasks[taskId].data[3];
    subpriority = gTasks[taskId].data[4];

    for (i = 0; i < 8; i++)
    {
        spriteId = CreateSprite(&gSpriteTemplates_840B3B4[ballIndex], x, y, subpriority);
        StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballIndex]);
        gSprites[spriteId].callback = PremierBallOpenParticleAnimation_Step1;
        gSprites[spriteId].oam.priority = priority;
        gSprites[spriteId].data[0] = i * 32;
    }

    gSprites[spriteId].data[7] = 1;
    DestroyTask(taskId);
}

static void PremierBallOpenParticleAnimation_Step1(struct Sprite *sprite)
{
    sprite->x2 = Sin(sprite->data[0], sprite->data[1]);
    sprite->y2 = Cos(sprite->data[0], Sin(sprite->data[0] & 0x3F, sprite->data[2]));
    sprite->data[0] = (sprite->data[0] + 10) & 0xFF;
    sprite->data[1]++;
    sprite->data[2]++;
    if (++sprite->data[3] == 51)
        DestroyBallOpenAnimationParticle(sprite);
}

static void DestroyBallOpenAnimationParticle(struct Sprite *sprite)
{
    int i;
    int temp;

    if (!gMain.inBattle)
    {
        temp = sprite->data[7]; // temp var needed to match
        if (temp == 1)
            DestroySpriteAndFreeResources(sprite);
        else
            DestroySprite(sprite);
    }
    else if (sprite->data[7] == 1)
    {
        if (--ewram17840.unkA == 0)
        {
            for (i = 0; i < 12; i++)
            {
                FreeSpriteTilesByTag(gBallOpenParticleSpritesheets[i].tag);
                FreeSpritePaletteByTag(gBallOpenParticlePalettes[i].tag);
            }

            DestroySprite(sprite);
        }
        else
            DestroySprite(sprite);
    }
    else
        DestroySprite(sprite);
}

u8 sub_8141314(u8 arg0, u8 battler, u32 selectedPalettes, u8 ballIndex)
{
    u8 taskId;

    taskId = CreateTask(sub_81413DC, 5);
    gTasks[taskId].data[15] = ballIndex;
    gTasks[taskId].data[3] = battler;
    gTasks[taskId].data[10] = selectedPalettes;
    gTasks[taskId].data[11] = selectedPalettes >> 16;

    if (!arg0)
    {
        BlendPalette(battler * 16 + 0x100, 16, 0, gUnknown_0840B4D4[ballIndex]);
        gTasks[taskId].data[1] = 1;
    }
    else
    {
        BlendPalette(battler * 16 + 0x100, 16, 16, gUnknown_0840B4D4[ballIndex]);
        gTasks[taskId].data[0] = 16;
        gTasks[taskId].data[1] = -1;
        gTasks[taskId].func = sub_814146C;
    }

    BeginNormalPaletteFade(selectedPalettes, 0, 0, 16, RGB(31, 31, 31));
    return taskId;
}

static void sub_81413DC(u8 taskId)
{
    u8 ballIndex = gTasks[taskId].data[15];

    if (gTasks[taskId].data[2] <= 16)
    {
        BlendPalette(gTasks[taskId].data[3] * 16 + 0x100, 16, gTasks[taskId].data[0], gUnknown_0840B4D4[ballIndex]);
        gTasks[taskId].data[0] += gTasks[taskId].data[1];
        gTasks[taskId].data[2]++;
    }
    else if (!gPaletteFade.active)
    {
        u32 selectedPalettes = (u16)gTasks[taskId].data[10] | ((u16)gTasks[taskId].data[11] << 16);
        BeginNormalPaletteFade(selectedPalettes, 0, 16, 0, RGB(31, 31, 31));
        DestroyTask(taskId);
    }
}

static void sub_814146C(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        u32 selectedPalettes = (u16)gTasks[taskId].data[10] | ((u16)gTasks[taskId].data[11] << 16);
        BeginNormalPaletteFade(selectedPalettes, 0, 16, 0, RGB(31, 31, 31));
        gTasks[taskId].func = sub_81414BC;
    }
}

static void sub_81414BC(u8 taskId)
{
    u8 ballIndex = gTasks[taskId].data[15];

    if (gTasks[taskId].data[2] <= 16)
    {
        BlendPalette(gTasks[taskId].data[3] * 16 + 0x100, 16, gTasks[taskId].data[0], gUnknown_0840B4D4[ballIndex]);
        gTasks[taskId].data[0] += gTasks[taskId].data[1];
        gTasks[taskId].data[2]++;
    }
    else
    {
        DestroyTask(taskId);
    }
}

void sub_814151C(u8 taskId)
{
    u8 spriteId;
    u32 x;
    u32 done;
    
    done = FALSE;
    spriteId = gBattlerSpriteIds[gBattleAnimAttacker];
    switch (gTasks[taskId].data[10])
    {
    case 0:
        gTasks[taskId].data[11] = gBattleAnimArgs[0];
        gTasks[taskId].data[0] += 0x500;
        if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
            gSprites[spriteId].x2 += gTasks[taskId].data[0] >> 8;
        else
            gSprites[spriteId].x2 -= gTasks[taskId].data[0] >> 8;

        gTasks[taskId].data[0] &= 0xFF;
        x = gSprites[spriteId].x + gSprites[spriteId].x2 + 32;
        if (x > 304)
            gTasks[taskId].data[10]++;
        break;
    case 1:
        refresh_graphics_maybe(gBattleAnimAttacker, gTasks[taskId].data[11], spriteId);
        gTasks[taskId].data[10]++;
        break;
    case 2:
        gTasks[taskId].data[0] += 0x500;
        if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
            gSprites[spriteId].x2 -= gTasks[taskId].data[0] >> 8;
        else
            gSprites[spriteId].x2 += gTasks[taskId].data[0] >> 8;

        gTasks[taskId].data[0] &= 0xFF;
        if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
        {
            if (gSprites[spriteId].x2 <= 0)
            {
                gSprites[spriteId].x2 = 0;
                // done = FALSE; // fakematching--can't get the tail merge correct
                goto DONE;
            }
        }
        else
        {
            if (gSprites[spriteId].x2 >= 0)
            {
                gSprites[spriteId].x2 = 0;
                done = TRUE;
            }
        }

        if (done)
        {
            DONE:
            DestroyAnimVisualTask(taskId);
        }
        break;
    }
}

void sub_81416C4(u8 taskId)
{
    u8 spriteId;

    switch (gTasks[taskId].data[15])
    {
    case 0:
        if (GetBattlerPosition_permutated(gBattleAnimAttacker) == 1)
            REG_BLDCNT = 0x3F42;
        else
            REG_BLDCNT = 0x3F44;

        REG_BLDALPHA = 0x0010;
        gTasks[taskId].data[15]++;
        break;
    case 1:
        if (gTasks[taskId].data[1]++ > 1)
        {
            gTasks[taskId].data[1] = 0;
            gTasks[taskId].data[0]++;
            REG_BLDALPHA = (gTasks[taskId].data[0] << 8) | (16 - gTasks[taskId].data[0]);
            if (gTasks[taskId].data[0] == 16)
                gTasks[taskId].data[15]++;
        }
        break;
    case 2:
        spriteId = gBattlerSpriteIds[gBattleAnimAttacker];
        DmaClear32(3, (void *)OBJ_VRAM0 + gSprites[spriteId].oam.tileNum * TILE_SIZE_4BPP, 0x800);
        sub_80324E0(gBattleAnimAttacker);
        DestroyAnimVisualTask(taskId);
        break;
    }
}

void sub_81417D8(u8 taskId)
{
    gBattleAnimArgs[7] = gBattleSpriteInfo[gBattleAnimAttacker].substituteSprite;
    DestroyAnimVisualTask(taskId);
}

void sub_8141808(u8 taskId)
{
    gBattleAnimTarget = gEffectBattler;
    DestroyAnimVisualTask(taskId);
}

void sub_8141828(u8 battler, struct Pokemon *mon)
{
    int isShiny;
    u32 otId, personality;
    u32 shinyValue;
    u8 taskId1, taskId2;

    isShiny = 0;
    ewram17810[battler].unk0_7 = 1;
    otId = GetMonData(mon, MON_DATA_OT_ID);
    personality = GetMonData(mon, MON_DATA_PERSONALITY);

    if (IsAnimBankSpriteVisible(battler))
    {
        shinyValue = HIHALF(otId) ^ LOHALF(otId) ^ HIHALF(personality) ^ LOHALF(personality);
        if (shinyValue < 8)
            isShiny = TRUE;

        if (isShiny)
        {
            if (GetSpriteTileStartByTag(0x27F9) == 0xFFFF)
            {
                LoadCompressedObjectPic(&gBattleAnimPicTable[233]);
                LoadCompressedObjectPalette(&gBattleAnimPaletteTable[233]);
            }

            taskId1 = CreateTask(sub_814191C, 10);
            taskId2 = CreateTask(sub_814191C, 10);
            gTasks[taskId1].data[0] = battler;
            gTasks[taskId2].data[0] = battler;
            gTasks[taskId1].data[1] = 0;
            gTasks[taskId2].data[1] = 1;
            return;
        }
    }

    ewram17810[battler].unk1_0 = 1;
}

static void sub_814191C(u8 taskId)
{
    u8 battler;
    u8 x, y;
    u8 spriteId;
    u16 counter;
    s16 state;
    u8 pan;

    if (gTasks[taskId].data[13] < 60)
    {
        gTasks[taskId].data[13]++;
        return;
    }
    
    if (ewram17840.unkA)
        return;

    counter = gTasks[taskId].data[10]++;
    if (counter & 3)
        return;

    battler = gTasks[taskId].data[0];
    x = GetBattlerSpriteCoord(battler, 0);
    y = GetBattlerSpriteCoord(battler, 1);
    state = gTasks[taskId].data[11];
    if (state == 0)
    {
        spriteId = CreateSprite(&gBattleAnimSpriteTemplate_84024E8, x, y, 5);
    }
    else if (state >= 0 && gTasks[taskId].data[11] < 4)
    {
        spriteId = CreateSprite(&gSpriteTemplate_8402500, x, y, 5);
        gSprites[spriteId].oam.tileNum += 4;
    }
    else
    {
        spriteId = CreateSprite(&gSpriteTemplate_8402500, x, y, 5);
        gSprites[spriteId].oam.tileNum += 5;
    }

    if (gTasks[taskId].data[1] == 0)
    {
        gSprites[spriteId].callback = sub_8141B20;
    }
    else
    {
        gSprites[spriteId].callback = sub_8141B74;
        gSprites[spriteId].x2 = -32;
        gSprites[spriteId].y2 = 32;
        gSprites[spriteId].invisible = TRUE;
        if (gTasks[taskId].data[11] == 0)
        {
            if (GetBattlerSide(battler) == B_SIDE_PLAYER)
                pan = 192;
            else
                pan = 63;

            PlaySE12WithPanning(SE_SHINY, pan);
        }
    }

    gSprites[spriteId].data[0] = taskId;
    gTasks[taskId].data[11]++;
    gTasks[taskId].data[12]++;
    if (gTasks[taskId].data[11] == 5)
        gTasks[taskId].func = sub_8141AD8;
}

static void sub_8141AD8(u8 taskId)
{
    u8 battler;

    if (gTasks[taskId].data[12] == 0)
    {
        if (gTasks[taskId].data[1] == 1)
        {
            battler = gTasks[taskId].data[0];
            ewram17810[battler].unk1_0 = 1;
        }

        DestroyTask(taskId);
    }
}

static void sub_8141B20(struct Sprite *sprite)
{
    sprite->x2 = Sin(sprite->data[1], 24);
    sprite->y2 = Cos(sprite->data[1], 24);
    sprite->data[1] += 12;
    if (sprite->data[1] > 0xFF)
    {
        gTasks[sprite->data[0]].data[12]--;
        FreeSpriteOamMatrix(sprite);
        DestroySprite(sprite);
    }
}

static void sub_8141B74(struct Sprite *sprite)
{
    if (sprite->data[1] < 4)
    {
        sprite->data[1]++;
    }
    else
    {
        sprite->invisible = FALSE;
        sprite->x2 += 5;
        sprite->y2 -= 5;
        if (sprite->x2 > 32)
        {
            gTasks[sprite->data[0]].data[12]--;
            FreeSpriteOamMatrix(sprite);
            DestroySprite(sprite);
        }
    }
}

void sub_8141BD4(u8 taskId)
{
    u8 paletteIndex;

    LoadCompressedObjectPic(&gBattleAnimPicTable[269]);
    LoadCompressedObjectPalette(&gBattleAnimPaletteTable[269]);
    paletteIndex = IndexOfSpritePaletteTag(0x281D); // unused
    DestroyAnimVisualTask(taskId);
}

void sub_8141C08(u8 taskId)
{
    FreeSpriteTilesByTag(0x281D);
    FreeSpritePaletteByTag(0x281D);
    DestroyAnimVisualTask(taskId);
}

static void sub_8141C30(struct Sprite *sprite)
{
    InitAnimSpritePos(sprite, 0);
    sprite->data[0] = 30;
    sprite->data[2] = GetBattlerSpriteCoord(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), 0) + gBattleAnimArgs[2];
    sprite->data[4] = GetBattlerSpriteCoord(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT), 1) + gBattleAnimArgs[3];
    sprite->data[5] = -32;
    InitAnimArcTranslation(sprite);
    StartSpriteAnim(&gSprites[gBattlerSpriteIds[gBattleAnimAttacker]], 1);
    sprite->callback = sub_8141CBC;
}

static void sub_8141CBC(struct Sprite *sprite)
{
    if (gSprites[gBattlerSpriteIds[gBattleAnimAttacker]].animCmdIndex == 1)
        sprite->callback = sub_8141CF4;
}

static void sub_8141CF4(struct Sprite *sprite)
{
    if (TranslateAnimArc(sprite))
    {
        sprite->data[0] = 0;
        sprite->invisible = TRUE;
        sprite->callback = sub_8141D20;
    }
}

static void sub_8141D20(struct Sprite *sprite)
{
    if (gSprites[gBattlerSpriteIds[gBattleAnimAttacker]].animEnded)
    {
        if (++sprite->data[0] > 0)
        {
            StartSpriteAnim(&gSprites[gBattlerSpriteIds[gBattleAnimAttacker]], 0);
            DestroyAnimSprite(sprite);
        }
    }
}

void sub_8141D7C(u8 taskId)
{
    gBattleAnimAttacker = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
    gBattleAnimTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
    DestroyAnimVisualTask(taskId);
}

void sub_8141DAC(u8 taskId)
{
    if (ewram17840.unk0 == 83)
        gBattleAnimArgs[0] = 1;
    else if (ewram17840.unk0 == 250)
        gBattleAnimArgs[0] = 2;
    else if (ewram17840.unk0 == 128)
        gBattleAnimArgs[0] = 3;
    else if (ewram17840.unk0 == 328)
        gBattleAnimArgs[0] = 4;
    else
        gBattleAnimArgs[0] = 0;

    DestroyAnimVisualTask(taskId);
}

void sub_8141E10(u8 taskId)
{
    gBattleAnimAttacker = ewram17840.unk0;
    gBattleAnimTarget = ewram17840.unk0 >> 8;
    DestroyAnimVisualTask(taskId);
}
