#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "battle_controllers.h"
#include "battle_interface.h"
#include "decompress.h"
#include "dma3.h"
#include "gpu_regs.h"
#include "graphics.h"
#include "m4a.h"
#include "main.h"
#include "palette.h"
#include "pokeball.h"
#include "sound.h"
#include "sprite.h"
#include "task.h"
#include "trig.h"
#include "util.h"
#include "constants/rgb.h"
#include "constants/items.h"
#include "constants/songs.h"

// iwram
int gUnknown_030062DC;
u16 gUnknown_030062E0;
u16 gUnknown_030062E4;

static void sub_8170660(u8);
static void sub_8170A38(u8);
static void sub_8170EF0(u8);
static void sub_8171104(struct Sprite *);
static void sub_8171030(u8);
static void sub_81710A8(u8);
static void sub_8171134(struct Sprite *);
static void sub_8171CAC(struct Sprite *);
static void sub_81711E8(struct Sprite *);
static void sub_8171240(struct Sprite *);
static void sub_817138C(struct Sprite *);
static void sub_81713D0(struct Sprite *);
static void sub_81717B4(struct Sprite *);
static void sub_81714D4(struct Sprite *);
static void sub_8171520(struct Sprite *);
static void sub_81717D8(struct Sprite *);
static void sub_8171AE4(struct Sprite *);
static void sub_81717F8(struct Sprite *);
static void sub_81719EC(struct Sprite *);
static void sub_81718D8(struct Sprite *);
static void sub_81719C0(struct Sprite *);
static void sub_8171D60(u8);
static void sub_8171AAC(struct Sprite *);
static void sub_8171BAC(struct Sprite *);
static void sub_8171CE8(struct Sprite *);
static void PokeBallOpenParticleAnimation_Step1(struct Sprite *);
static void PokeBallOpenParticleAnimation_Step2(struct Sprite *);
static void DestroyBallOpenAnimationParticle(struct Sprite *);
static void FanOutBallOpenParticles_Step1(struct Sprite *);
static void RepeatBallOpenParticleAnimation_Step1(struct Sprite *);
static void PremierBallOpenParticleAnimation_Step1(struct Sprite *);
static void sub_8172AB0(u8);
static void sub_8172B40(u8);
static void sub_8172B90(u8);
static void sub_8172FEC(u8);
static void sub_81731FC(struct Sprite *);
static void sub_8173250(struct Sprite *);
static void sub_81731B0(u8);
static void sub_817339C(struct Sprite *);
static void sub_81733D4(struct Sprite *);
static void sub_8173400(struct Sprite *);
static void PokeBallOpenParticleAnimation(u8);
static void GreatBallOpenParticleAnimation(u8);
static void SafariBallOpenParticleAnimation(u8);
static void UltraBallOpenParticleAnimation(u8);
static void MasterBallOpenParticleAnimation(u8);
static void DiveBallOpenParticleAnimation(u8);
static void RepeatBallOpenParticleAnimation(u8);
static void TimerBallOpenParticleAnimation(u8);
static void PremierBallOpenParticleAnimation(u8);
static void sub_817330C(struct Sprite *);

struct BallCaptureSuccessStarData
{
    s8 xOffset;
    s8 yOffset;
    s8 unk2;
};

static const struct BallCaptureSuccessStarData sBallCaptureSuccessStarData[] =
{
    {
        .xOffset = 10,
        .yOffset = 2,
        .unk2 = -3,
    },
    {
        .xOffset = 15,
        .yOffset = 0,
        .unk2 = -4,
    },
    {
        .xOffset = -10,
        .yOffset = 2,
        .unk2 = -4,
    },
};

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},
};

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 gUnknown_085E5154[] =
{
    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 gUnknown_085E5170[] =
{
    ANIMCMD_FRAME(3, 1),
    ANIMCMD_END,
};

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

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

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

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

const union AnimCmd *const gUnknown_085E519C[] =
{
    gUnknown_085E5154,
    gUnknown_085E5170,
    gUnknown_085E5178,
    gUnknown_085E5180,
    gUnknown_085E5188,
    gUnknown_085E5194,
};

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

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

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

const u16 gUnknown_085E5310[] =
{
    RGB(31, 22, 30),
    RGB(16, 23, 30),
    RGB(23, 30, 20),
    RGB(31, 31, 15),
    RGB(23, 20, 28),
    RGB(21, 31, 25),
    RGB(12, 25, 30),
    RGB(30, 27, 10),
    RGB(31, 24, 16),
    RGB(29, 30, 30),
    RGB(31, 17, 10),
    RGB(31, 9, 10),
    RGB(0, 0, 0),
    RGB(1, 16, 0),
    RGB(3, 0, 1),
    RGB(1, 8, 0),
    RGB(0, 8, 0),
    RGB(3, 8, 1),
    RGB(6, 8, 1),
    RGB(4, 0, 0),
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_85E5338 =
{
    .tileTag = ANIM_TAG_UNUSED_RED_BRICK,
    .paletteTag = ANIM_TAG_UNUSED_RED_BRICK,
    .oam = &gUnknown_0852490C,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_817330C,
};

const union AnimCmd gUnknown_085E5350[] =
{
    ANIMCMD_FRAME(64, 1),
    ANIMCMD_END,
};

const union AnimCmd *const gUnknown_085E5358[] = {
    gUnknown_085E5350,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_085E535C =
{
    .tileTag = ANIM_TAG_ROCKS,
    .paletteTag = ANIM_TAG_ROCKS,
    .oam = &gUnknown_08524914,
    .anims = gUnknown_085E5358,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_817330C,
};

extern const struct SpriteTemplate gUnknown_085CE388;
extern const struct SpriteTemplate gMiniTwinklingStarSpriteTemplate;

void unref_sub_8170478(u8 taskId)
{
    struct BattleAnimBgData unknownStruct;
    u8 healthBoxSpriteId;
    u8 battler;
    u8 spriteId1, spriteId2, spriteId3, spriteId4;

    battler = gBattleAnimAttacker;
    gBattle_WIN0H = 0;
    gBattle_WIN0V = 0;
    SetGpuReg(REG_OFFSET_WININ, WININ_WIN0_BG_ALL | WININ_WIN0_OBJ | WININ_WIN0_CLR | WININ_WIN1_BG_ALL | WININ_WIN1_OBJ | WININ_WIN1_CLR);
    SetGpuReg(REG_OFFSET_WINOUT, WINOUT_WIN01_BG0 | WINOUT_WIN01_BG2 | WINOUT_WIN01_BG3 | WINOUT_WIN01_OBJ | WINOUT_WIN01_CLR | WINOUT_WINOBJ_BG_ALL | WINOUT_WINOBJ_OBJ | WINOUT_WINOBJ_CLR);
    SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_OBJWIN_ON);
    SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG1 | BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL);
    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 16));
    SetAnimBgAttribute(1, BG_ANIM_PRIORITY, 0);
    SetAnimBgAttribute(1, BG_ANIM_SCREEN_SIZE, 0);
    SetAnimBgAttribute(1, BG_ANIM_AREA_OVERFLOW_MODE, 1);
    SetAnimBgAttribute(1, BG_ANIM_CHAR_BASE_BLOCK, 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_80A6B30(&unknownStruct);
    AnimLoadCompressedBgTilemap(unknownStruct.bgId, gUnknown_08C2EA9C);
    AnimLoadCompressedBgGfx(unknownStruct.bgId, gUnknown_08C2EA50, unknownStruct.tilesOffset);
    LoadCompressedPalette(gCureBubblesPal, unknownStruct.paletteId << 4, 32);

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

static void sub_8170660(u8 taskId)
{
    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]++;
            SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(gTasks[taskId].data[12], 16 - 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]--;
            SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(gTasks[taskId].data[12], 16 - gTasks[taskId].data[12]));
            if (gTasks[taskId].data[12] == 0)
            {
                sub_80A477C(0);
                gBattle_WIN0H = 0;
                gBattle_WIN0V = 0;
                SetGpuReg(REG_OFFSET_WININ, WININ_WIN0_BG_ALL | WININ_WIN0_OBJ | WININ_WIN0_CLR | WININ_WIN1_BG_ALL | WININ_WIN1_OBJ | WININ_WIN1_CLR);
                SetGpuReg(REG_OFFSET_WINOUT, WINOUT_WIN01_BG_ALL | WINOUT_WIN01_OBJ | WINOUT_WIN01_CLR | WINOUT_WINOBJ_BG_ALL | WINOUT_WINOBJ_OBJ | WINOUT_WINOBJ_CLR);
                if (!IsContest())
                    SetAnimBgAttribute(1, BG_ANIM_CHAR_BASE_BLOCK, 0);

                SetGpuReg(REG_OFFSET_DISPCNT, GetGpuReg(REG_OFFSET_DISPCNT) ^ DISPCNT_OBJWIN_ON);
                SetGpuReg(REG_OFFSET_BLDCNT, 0);
                SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0));
                DestroySprite(&gSprites[gTasks[taskId].data[0]]);
                DestroySprite(&gSprites[gTasks[taskId].data[2]]);
                SetAnimBgAttribute(1, BG_ANIM_AREA_OVERFLOW_MODE, 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;
    }
}

static void sub_8170834(u8 *paletteId1, u8 *paletteId2, u8 battler)
{
    u8 healthBoxSpriteId;
    u8 spriteId1, spriteId2;
    u16 offset1, offset2;

    healthBoxSpriteId = gHealthboxSpriteIds[battler];
    spriteId1 = gSprites[healthBoxSpriteId].oam.affineParam;
    spriteId2 = gSprites[healthBoxSpriteId].data[5];
    *paletteId1 = AllocSpritePalette(0xD709);
    *paletteId2 = AllocSpritePalette(0xD70A);

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

    gSprites[healthBoxSpriteId].oam.paletteNum = *paletteId1;
    gSprites[spriteId1].oam.paletteNum = *paletteId1;
    gSprites[spriteId2].oam.paletteNum = *paletteId2;
}

void sub_8170920(u8 taskId)
{
    u8 paletteId1, paletteId2;
    sub_8170834(&paletteId1, &paletteId2, gBattleAnimAttacker);
    DestroyAnimVisualTask(taskId);
}

static void sub_817094C(u8 battler)
{
    u8 healthBoxSpriteId;
    u8 spriteId1, spriteId2;
    u8 paletteId1, paletteId2;

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

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

void sub_81709EC(u8 taskId)
{
    sub_817094C(gBattleAnimAttacker);
    DestroyAnimVisualTask(taskId);
}

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

static void sub_8170A38(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_8170B04(u8 taskId)
{
    u8 spriteId;

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

void sub_8170BB0(u8 taskId)
{
    u8 spriteId;
    u16 ball;
    u8 ballId;
    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);

    ballId = ItemIdToBallId(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, ballId);
        selectedPalettes = sub_80A75AC(1, 0, 0, 0, 0, 0, 0);
        gTasks[taskId].data[11] = LaunchBallFadeMonTask(0, gBattleAnimAttacker, selectedPalettes, ballId);
        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_8170CFC(u8 taskId)
{
    u8 ballId = ItemIdToBallId(gLastUsedItem);
    LoadBallGfx(ballId);
    DestroyAnimVisualTask(taskId);
}

void sub_8170D24(u8 taskId)
{
    u8 ballId = ItemIdToBallId(gLastUsedItem);
    FreeBallGfx(ballId);
    DestroyAnimVisualTask(taskId);
}

void AnimTask_IsBallBlockedByTrainer(u8 taskId)
{
    if (gBattleSpritesDataPtr->animationData->ballThrowCaseId == BALL_TRAINER_BLOCK)
        gBattleAnimArgs[7] = -1;
    else
        gBattleAnimArgs[7] = 0;

    DestroyAnimVisualTask(taskId);
}

u8 ItemIdToBallId(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_8170E04(u8 taskId)
{
    u8 ballId;
    u8 spriteId;

    ballId = ItemIdToBallId(gLastUsedItem);
    spriteId = CreateSprite(&gBallSpriteTemplates[ballId], 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_8171104;
    gBattleSpritesDataPtr->animationData->field_9_x2 = gSprites[gBattlerSpriteIds[gBattleAnimTarget]].invisible;
    gTasks[taskId].data[0] = spriteId;
    gTasks[taskId].func = sub_8170EF0;
}

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

void sub_8170F2C(u8 taskId)
{
    int x, y;
    u8 ballId;
    u8 subpriority;
    u8 spriteId;

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

    ballId = ItemIdToBallId(gLastUsedItem);
    subpriority = GetBattlerSpriteSubpriority(GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)) + 1;
    spriteId = CreateSprite(&gBallSpriteTemplates[ballId], 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;
    gSprites[gBattlerSpriteIds[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)]].callback = sub_8039E84;
    gTasks[taskId].data[0] = spriteId;
    gTasks[taskId].func = sub_8171030;
}

static void sub_8171030(u8 taskId)
{
    if (gSprites[gBattlerSpriteIds[GetBattlerAtPosition(B_POSITION_PLAYER_LEFT)]].animCmdIndex == 1)
    {
        PlaySE12WithPanning(SE_NAGERU, 0);
        gSprites[gTasks[taskId].data[0]].callback = sub_8171104;
        CreateTask(sub_81710A8, 10);
        gTasks[taskId].func = sub_8170EF0;
    }
}

static void sub_81710A8(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_8171104(struct Sprite *sprite)
{
    u16 temp = sprite->data[1];
    u16 temp2 = sprite->data[2];
    sprite->data[1] = sprite->pos1.x;
    sprite->data[2] = temp;
    sprite->data[3] = sprite->pos1.y;
    sprite->data[4] = temp2;
    sprite->data[5] = -40;
    InitAnimArcTranslation(sprite);
    sprite->callback = sub_8171134;
}

static void sub_8171134(struct Sprite *sprite)
{
    int i;
    u8 ballId;
    int ballId2; // extra var needed to match

    if (TranslateAnimHorizontalArc(sprite))
    {
        if (gBattleSpritesDataPtr->animationData->ballThrowCaseId == BALL_TRAINER_BLOCK)
        {
            sprite->callback = sub_8171CAC;
        }
        else
        {
            StartSpriteAnim(sprite, 1);
            sprite->pos1.x += sprite->pos2.x;
            sprite->pos1.y += sprite->pos2.y;
            sprite->pos2.x = 0;
            sprite->pos2.y = 0;

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

            sprite->data[5] = 0;
            sprite->callback = sub_81711E8;
            ballId = ItemIdToBallId(gLastUsedItem);
            ballId2 = ballId;
            if (ballId2 > 11)
                return;
            if (ballId2 < 0)
                return;

            AnimateBallOpenParticles(sprite->pos1.x, sprite->pos1.y - 5, 1, 28, ballId);
            LaunchBallFadeMonTask(0, gBattleAnimTarget, 14, ballId);
        }
    }
}

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

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

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

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

    switch (gTasks[taskId].data[0])
    {
    case 0:
        PrepareBattlerSpriteForRotScale(spriteId, ST_OAM_OBJ_NORMAL);
        gTasks[taskId].data[10] = 256;
        gUnknown_030062DC = 28;
        gUnknown_030062E4 = (gSprites[spriteId].pos1.y + gSprites[spriteId].pos2.y) - (sprite->pos1.y + sprite->pos2.y);
        gUnknown_030062E0 = (u32)(gUnknown_030062E4 * 256) / 28;
        gTasks[taskId].data[2] = gUnknown_030062E0;
        gTasks[taskId].data[0]++;
        break;
    case 1:
        gTasks[taskId].data[10] += 0x20;
        SetSpriteRotScale(spriteId, gTasks[taskId].data[10], gTasks[taskId].data[10], 0);
        gTasks[taskId].data[3] += gTasks[taskId].data[2];
        gSprites[spriteId].pos2.y = -gTasks[taskId].data[3] >> 8;
        if (gTasks[taskId].data[10] >= 0x480)
            gTasks[taskId].data[0]++;
        break;
    case 2:
        ResetSpriteRotScale(spriteId);
        gSprites[spriteId].invisible = 1;
        gTasks[taskId].data[0]++;
        break;
    default:
        if (gTasks[taskId].data[1] > 10)
        {
            DestroyTask(taskId);
            StartSpriteAnim(sprite, 2);
            sprite->data[5] = 0;
            sprite->callback = sub_817138C;
        }
        break;
    }
}

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

    if (sprite->animEnded)
    {
        sprite->data[3] = 0;
        sprite->data[4] = 40;
        sprite->data[5] = 0;
        angle = 0;
        sprite->pos1.y += Cos(angle, 40);
        sprite->pos2.y = -Cos(angle, sprite->data[4]);
        sprite->callback = sub_81713D0;
    }
}

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

    lastBounce = 0;

    switch (sprite->data[3] & 0xFF)
    {
    case 0:
        sprite->pos2.y = -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_KON);
                break;
            case 2:
                PlaySE(SE_KON2);
                break;
            case 3:
                PlaySE(SE_KON3);
                break;
            default:
                PlaySE(SE_KON4);
                break;
            }
        }
        break;
    case 1:
        sprite->pos2.y = -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->pos1.y += Cos(64, 40);
        sprite->pos2.y = 0;
        if (gBattleSpritesDataPtr->animationData->ballThrowCaseId == BALL_NO_SHAKES)
        {
            sprite->data[5] = 0;
            sprite->callback = sub_81717B4;
        }
        else
        {
            sprite->callback = sub_81714D4;
            sprite->data[4] = 1;
            sprite->data[5] = 0;
        }
    }
}

static void sub_81714D4(struct Sprite *sprite)
{
    if (++sprite->data[3] == 31)
    {
        sprite->data[3] = 0;
        sprite->affineAnimPaused = 1;
        StartSpriteAffineAnim(sprite, 1);
        gBattleSpritesDataPtr->animationData->field_C = 0;
        sprite->callback = sub_8171520;
        PlaySE(SE_BOWA);
    }
}

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

    switch (sprite->data[3] & 0xFF)
    {
    case 0:
        if (gBattleSpritesDataPtr->animationData->field_C > 0xFF)
        {
            sprite->pos2.x += sprite->data[4];
            gBattleSpritesDataPtr->animationData->field_C &= 0xFF;
        }
        else
        {
            gBattleSpritesDataPtr->animationData->field_C += 0xB0;
        }

        sprite->data[5]++;
        sprite->affineAnimPaused = 0;
        var0 = sprite->data[5] + 7;
        if (var0 > 14)
        {
            gBattleSpritesDataPtr->animationData->field_C = 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 (gBattleSpritesDataPtr->animationData->field_C > 0xFF)
        {
            sprite->pos2.x += sprite->data[4];
            gBattleSpritesDataPtr->animationData->field_C &= 0xFF;
        }
        else
        {
            gBattleSpritesDataPtr->animationData->field_C += 0xB0;
        }

        sprite->data[5]++;
        sprite->affineAnimPaused = 0;
        var0 = sprite->data[5] + 12;
        if (var0 > 24)
        {
            gBattleSpritesDataPtr->animationData->field_C = 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 (gBattleSpritesDataPtr->animationData->field_C > 0xFF)
        {
            sprite->pos2.x += sprite->data[4];
            gBattleSpritesDataPtr->animationData->field_C &= 0xFF;
        }
        else
        {
            gBattleSpritesDataPtr->animationData->field_C += 0xB0;
        }

        sprite->data[5]++;
        sprite->affineAnimPaused = 0;
        var0 = sprite->data[5] + 4;
        if (var0 > 8)
        {
            gBattleSpritesDataPtr->animationData->field_C = 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 == gBattleSpritesDataPtr->animationData->ballThrowCaseId)
        {
            sprite->affineAnimPaused = 1;
            sprite->callback = sub_81717B4;
        }
        else
        {
            if (gBattleSpritesDataPtr->animationData->ballThrowCaseId == BALL_3_SHAKES_SUCCESS && state == 3)
            {
                sprite->callback = sub_81717D8;
                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_BOWA);
        }
        break;
    }
}

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

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

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

    sprite->data[4]++;
    if (sprite->data[4] == 40)
    {
        PlaySE(SE_RG_GETTING);
        BlendPalettes(0x10000 << sprite->oam.paletteNum, 6, RGB(0, 0, 0));
        sub_81719EC(sprite);
    }
    else if (sprite->data[4] == 60)
    {
        BeginNormalPaletteFade(0x10000 << sprite->oam.paletteNum, 2, 6, 0, RGB(0, 0, 0));
    }
    else if (sprite->data[4] == 95)
    {
        gDoingBattleAnim = 0;
        UpdateOamPriorityInAllHealthboxes(1);
        m4aMPlayAllStop();
        PlaySE(MUS_RG_FAN6);
    }
    else if (sprite->data[4] == 315)
    {
        FreeOamMatrix(gSprites[gBattlerSpriteIds[*battler]].oam.matrixNum);
        DestroySprite(&gSprites[gBattlerSpriteIds[*battler]]);
        sprite->data[0] = 0;
        sprite->callback = sub_81718D8;
    }
}

static void sub_81718D8(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;
        SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL);
        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(16, 0));
        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]++;
            SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(16 - sprite->data[2], sprite->data[2]));
            if (sprite->data[2] == 16)
                sprite->data[0]++;
        }
        break;
    case 2:
        sprite->invisible = 1;
        sprite->data[0]++;
        break;
    default:
        if (!gPaletteFade.active)
        {
            SetGpuReg(REG_OFFSET_BLDCNT, 0);
            SetGpuReg(REG_OFFSET_BLDALPHA, 0);
            sprite->data[0] = 0;
            sprite->callback = sub_81719C0;
        }
        break;
    }
}

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

static void sub_81719EC(struct Sprite *sprite)
{
    u32 i;
    u8 subpriority;

    if (sprite->subpriority)
    {
        subpriority = sprite->subpriority - 1;
    }
    else
    {
        subpriority = 0;
        sprite->subpriority = 1;
    }

    sub_8171D60(4);
    for (i = 0; i < 3; i++)
    {
        u8 spriteId = CreateSprite(&gUnknown_085E51F0[4], sprite->pos1.x, sprite->pos1.y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            gSprites[spriteId].data[0] = 24;
            gSprites[spriteId].data[2] = sprite->pos1.x + sBallCaptureSuccessStarData[i].xOffset;
            gSprites[spriteId].data[4] = sprite->pos1.y + sBallCaptureSuccessStarData[i].yOffset;
            gSprites[spriteId].data[5] = sBallCaptureSuccessStarData[i].unk2;
            InitAnimArcTranslation(&gSprites[spriteId]);
            gSprites[spriteId].callback = sub_8171AAC;
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[4]);
        }
    }
}

static void sub_8171AAC(struct Sprite *sprite)
{
    sprite->invisible = !sprite->invisible;
    if (TranslateAnimHorizontalArc(sprite))
        DestroySprite(sprite);
}

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

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

    ballId = ItemIdToBallId(gLastUsedItem);
    ballId2 = ballId;
    if (ballId2 > 11)
        goto LABEL;
    if (ballId2 < 0)
        goto LABEL;

    AnimateBallOpenParticles(sprite->pos1.x, sprite->pos1.y - 5, 1, 28, ballId);
    LaunchBallFadeMonTask(1, gBattleAnimTarget, 14, ballId);

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

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

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

    if (sprite->animEnded && next)
    {
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].pos2.y = 0;
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].invisible = gBattleSpritesDataPtr->animationData->field_9_x2;
        sprite->data[0] = 0;
        sprite->callback = sub_81719C0;
        gDoingBattleAnim = 0;
        UpdateOamPriorityInAllHealthboxes(1);
    }
}

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

    sprite->pos1.x += sprite->pos2.x;
    sprite->pos1.y += sprite->pos2.y;
    sprite->pos2.y = 0;
    sprite->pos2.x = 0;
    for (i = 0; i < 6; i++)
        sprite->data[i] = 0;

    sprite->callback = sub_8171CE8;
}

static void sub_8171CE8(struct Sprite *sprite)
{
    s16 var0 = sprite->data[0] + 0x800;
    s16 var1 = sprite->data[1] + 0x680;
    sprite->pos2.x -= var1 >> 8;
    sprite->pos2.y += var0 >> 8;
    sprite->data[0] = (sprite->data[0] + 0x800) & 0xFF;
    sprite->data[1] = (sprite->data[1] + 0x680) & 0xFF;

    if (sprite->pos1.y + sprite->pos2.y > 160
     || sprite->pos1.x + sprite->pos2.x < -8)
    {
        sprite->data[0] = 0;
        sprite->callback = sub_81719C0;
        gDoingBattleAnim = 0;
        UpdateOamPriorityInAllHealthboxes(1);
    }
}

static void sub_8171D60(u8 ballId)
{
    u8 taskId;

    if (GetSpriteTileStartByTag(gBallOpenParticleSpritesheets[ballId].tag) == 0xFFFF)
    {
        LoadCompressedSpriteSheetUsingHeap(&gBallOpenParticleSpritesheets[ballId]);
        LoadCompressedSpritePaletteUsingHeap(&gBallOpenParticlePalettes[ballId]);
    }
}

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

    sub_8171D60(ballId);
    taskId = CreateTask(gBallOpenParticleAnimationFuncs[ballId], 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] = ballId;
    PlaySE(SE_BOWA2);

    return taskId;
}

void sub_8171E20(void)
{
    if (gMain.inBattle)
        gBattleSpritesDataPtr->animationData->field_A++;
}

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

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            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)
        {
            if (!gMain.inBattle)
                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->pos2.x = Sin(sprite->data[0], sprite->data[1]);
    sprite->pos2.y = Cos(sprite->data[0], sprite->data[1]);
    sprite->data[1] += 2;
    if (sprite->data[1] == 50)
        DestroyBallOpenAnimationParticle(sprite);
}

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

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            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;
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

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

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            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;
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

// Also used for Net Ball
static void SafariBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballId;
    u8 spriteId;

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            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;
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

// Also used for Nest Ball
static void UltraBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballId;
    u8 spriteId;

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            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;
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

// Also used for Luxury Ball
static void GreatBallOpenParticleAnimation(u8 taskId)
{
    u8 i;
    u8 x, y, priority, subpriority, ballId;
    u8 spriteId;

    if (gTasks[taskId].data[7])
    {
        gTasks[taskId].data[7]--;
    }
    else
    {
        ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
            if (spriteId != MAX_SPRITES)
            {
                sub_8171E20();
                StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
                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)
        {
            if (!gMain.inBattle)
                gSprites[spriteId].data[7] = 1;

            DestroyTask(taskId);
        }
    }
}

static void FanOutBallOpenParticles_Step1(struct Sprite *sprite)
{
    sprite->pos2.x = Sin(sprite->data[0], sprite->data[1]);
    sprite->pos2.y = 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);
}

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

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            gSprites[spriteId].callback = RepeatBallOpenParticleAnimation_Step1;
            gSprites[spriteId].oam.priority = priority;
            gSprites[spriteId].data[0] = i * 21;
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

static void RepeatBallOpenParticleAnimation_Step1(struct Sprite *sprite)
{
    sprite->pos2.x = Sin(sprite->data[0], sprite->data[1]);
    sprite->pos2.y = 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);
}

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

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
            if (spriteId != MAX_SPRITES)
            {
                sub_8171E20();
                StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
                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;
                }
            }
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

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

    ballId = 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(&gUnknown_085E51F0[ballId], x, y, subpriority);
        if (spriteId != MAX_SPRITES)
        {
            sub_8171E20();
            StartSpriteAnim(&gSprites[spriteId], gBallOpenParticleAnimNums[ballId]);
            gSprites[spriteId].callback = PremierBallOpenParticleAnimation_Step1;
            gSprites[spriteId].oam.priority = priority;
            gSprites[spriteId].data[0] = i * 32;
        }
    }

    if (!gMain.inBattle)
        gSprites[spriteId].data[7] = 1;

    DestroyTask(taskId);
}

static void PremierBallOpenParticleAnimation_Step1(struct Sprite *sprite)
{
    sprite->pos2.x = Sin(sprite->data[0], sprite->data[1]);
    sprite->pos2.y = 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, j;
    int temp;

    if (!gMain.inBattle)
    {
        temp = sprite->data[7]; // temp var needed to match
        if (temp == 1)
            DestroySpriteAndFreeResources(sprite);
        else
            DestroySprite(sprite);
    }
    else
    {
        gBattleSpritesDataPtr->animationData->field_A--;
        if (gBattleSpritesDataPtr->animationData->field_A == 0)
        {
            for (i = 0; i < 12; i++)
            {
                if (FuncIsActiveTask(gBallOpenParticleAnimationFuncs[i]) == TRUE)
                    break;
            }

            if (i == 12)
            {
                for (j = 0; j < 12; j++)
                {
                    FreeSpriteTilesByTag(gBallOpenParticleSpritesheets[j].tag);
                    FreeSpritePaletteByTag(gBallOpenParticlePalettes[j].tag);
                }
            }

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

u8 LaunchBallFadeMonTask(u8 unfadeLater, u8 battler, u32 selectedPalettes, u8 ballId)
{
    u8 taskId;

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

    if (!unfadeLater)
    {
        BlendPalette(battler * 16 + 0x100, 16, 0, gUnknown_085E5310[ballId]);
        gTasks[taskId].data[1] = 1;
    }
    else
    {
        BlendPalette(battler * 16 + 0x100, 16, 16, gUnknown_085E5310[ballId]);
        gTasks[taskId].data[0] = 16;
        gTasks[taskId].data[1] = -1;
        gTasks[taskId].func = sub_8172B40;
    }

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

static void sub_8172AB0(u8 taskId)
{
    u8 ballId = gTasks[taskId].data[15];

    if (gTasks[taskId].data[2] <= 16)
    {
        BlendPalette(gTasks[taskId].data[3] * 16 + 0x100, 16, gTasks[taskId].data[0], gUnknown_085E5310[ballId]);
        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_8172B40(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_8172B90;
    }
}

static void sub_8172B90(u8 taskId)
{
    u8 ballId = gTasks[taskId].data[15];

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

void sub_8172BF0(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].pos2.x += gTasks[taskId].data[0] >> 8;
        else
            gSprites[spriteId].pos2.x -= gTasks[taskId].data[0] >> 8;

        gTasks[taskId].data[0] &= 0xFF;
        x = gSprites[spriteId].pos1.x + gSprites[spriteId].pos2.x + 32;
        if (x > 304)
            gTasks[taskId].data[10]++;
        break;
    case 1:
        LoadBattleMonGfxAndAnimate(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].pos2.x -= gTasks[taskId].data[0] >> 8;
        else
            gSprites[spriteId].pos2.x += gTasks[taskId].data[0] >> 8;

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

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

void sub_8172D98(u8 taskId)
{
    u8 spriteId;

    switch (gTasks[taskId].data[15])
    {
    case 0:
        if (GetBattlerSpriteBGPriorityRank(gBattleAnimAttacker) == B_POSITION_OPPONENT_LEFT)
            SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG1 | BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL);
        else
            SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG2 | BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL);

        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(16, 0));
        gTasks[taskId].data[15]++;
        break;
    case 1:
        if (gTasks[taskId].data[1]++ > 1)
        {
            gTasks[taskId].data[1] = 0;
            gTasks[taskId].data[0]++;
            SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(16 - gTasks[taskId].data[0], gTasks[taskId].data[0]));
            if (gTasks[taskId].data[0] == 16)
                gTasks[taskId].data[15]++;
        }
        break;
    case 2:
        spriteId = gBattlerSpriteIds[gBattleAnimAttacker];
        RequestDma3Fill(0, (void *)OBJ_VRAM0 + gSprites[spriteId].oam.tileNum * TILE_SIZE_4BPP, 0x800, 1);
        ClearBehindSubstituteBit(gBattleAnimAttacker);
        DestroyAnimVisualTask(taskId);
        break;
    }
}

void sub_8172E9C(u8 taskId)
{
    gBattleAnimArgs[7] = gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].behindSubstitute;
    DestroyAnimVisualTask(taskId);
}

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

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

    isShiny = 0;
    gBattleSpritesDataPtr->healthBoxesData[battler].flag_x80 = 1;
    otId = GetMonData(mon, MON_DATA_OT_ID);
    personality = GetMonData(mon, MON_DATA_PERSONALITY);

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

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

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

    gBattleSpritesDataPtr->healthBoxesData[battler].field_1_x1 = 1;
}

static void sub_8172FEC(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 (gBattleSpritesDataPtr->animationData->field_A)
        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(&gUnknown_085CE388, x, y, 5);
    }
    else if (state >= 0 && gTasks[taskId].data[11] < 4)
    {
        spriteId = CreateSprite(&gMiniTwinklingStarSpriteTemplate, x, y, 5);
        gSprites[spriteId].oam.tileNum += 4;
    }
    else
    {
        spriteId = CreateSprite(&gMiniTwinklingStarSpriteTemplate, x, y, 5);
        gSprites[spriteId].oam.tileNum += 5;
    }

    if (gTasks[taskId].data[1] == 0)
    {
        gSprites[spriteId].callback = sub_81731FC;
    }
    else
    {
        gSprites[spriteId].callback = sub_8173250;
        gSprites[spriteId].pos2.x = -32;
        gSprites[spriteId].pos2.y = 32;
        gSprites[spriteId].invisible = 1;
        if (gTasks[taskId].data[11] == 0)
        {
            if (GetBattlerSide(battler) == B_SIDE_PLAYER)
                pan = 192;
            else
                pan = 63;

            PlaySE12WithPanning(SE_REAPOKE, pan);
        }
    }

    gSprites[spriteId].data[0] = taskId;
    gTasks[taskId].data[11]++;
    if (spriteId != MAX_SPRITES)
        gTasks[taskId].data[12]++;

    if (gTasks[taskId].data[11] == 5)
        gTasks[taskId].func = sub_81731B0;
}

static void sub_81731B0(u8 taskId)
{
    u8 battler;

    if (gTasks[taskId].data[12] == 0)
    {
        if (gTasks[taskId].data[1] == 1)
        {
            battler = gTasks[taskId].data[0];
            gBattleSpritesDataPtr->healthBoxesData[battler].field_1_x1 = 1;
        }

        DestroyTask(taskId);
    }
}

static void sub_81731FC(struct Sprite *sprite)
{
    sprite->pos2.x = Sin(sprite->data[1], 24);
    sprite->pos2.y = 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_8173250(struct Sprite *sprite)
{
    if (sprite->data[1] < 4)
    {
        sprite->data[1]++;
    }
    else
    {
        sprite->invisible = 0;
        sprite->pos2.x += 5;
        sprite->pos2.y -= 5;
        if (sprite->pos2.x > 32)
        {
            gTasks[sprite->data[0]].data[12]--;
            FreeSpriteOamMatrix(sprite);
            DestroySprite(sprite);
        }
    }
}

void sub_81732B0(u8 taskId)
{
    u8 paletteIndex;

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

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

static void sub_817330C(struct Sprite *sprite)
{
    InitSpritePosToAnimAttacker(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);
    gSprites[gBattlerSpriteIds[gBattleAnimAttacker]].callback = sub_8039E84;
    sprite->callback = sub_817339C;
}

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

static void sub_81733D4(struct Sprite *sprite)
{
    if (TranslateAnimHorizontalArc(sprite))
    {
        sprite->data[0] = 0;
        sprite->invisible = 1;
        sprite->callback = sub_8173400;
    }
}

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

void sub_817345C(u8 taskId)
{
    switch (gBattleAnimArgs[0])
    {
    case 0:
        gBattleAnimAttacker = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
        gBattleAnimTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
        break;
    case 1:
        gBattleAnimAttacker = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
        gBattleAnimTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT);
        break;
    }

    DestroyAnimVisualTask(taskId);
}

void AnimTask_GetTrappedMoveAnimId(u8 taskId)
{
    if (gBattleSpritesDataPtr->animationData->animArg == 83)
        gBattleAnimArgs[0] = 1;
    else if (gBattleSpritesDataPtr->animationData->animArg == 250)
        gBattleAnimArgs[0] = 2;
    else if (gBattleSpritesDataPtr->animationData->animArg == 128)
        gBattleAnimArgs[0] = 3;
    else if (gBattleSpritesDataPtr->animationData->animArg == 328)
        gBattleAnimArgs[0] = 4;
    else
        gBattleAnimArgs[0] = 0;

    DestroyAnimVisualTask(taskId);
}

void sub_817351C(u8 taskId)
{
    gBattleAnimAttacker = gBattleSpritesDataPtr->animationData->animArg;
    gBattleAnimTarget = gBattleSpritesDataPtr->animationData->animArg >> 8;
    DestroyAnimVisualTask(taskId);
}
