#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "blend_palette.h"
#include "decompress.h"
#include "ewram.h"
#include "palette.h"
#include "random.h"
#include "rom_8077ABC.h"
#include "sound.h"
#include "task.h"
#include "trig.h"
#include "constants/battle.h"
#include "constants/songs.h"

extern s16 gBattleAnimArgs[];
extern u8 gBattleAnimAttacker;
extern u8 gBattleAnimTarget;
extern u8 gHealthboxSpriteIds[];
extern u8 gBattlersCount;
extern u8 gBattlerSpriteIds[];
extern u8 gBattleTerrain;
extern u16 gBattlerPartyIndexes[];
extern u8 gBattlerTarget;
extern u8 gEffectBattler;
extern u8 gBattlerAttacker;
extern u8 gAnimVisualTaskCount;

extern const u8 gUnknown_08D20A14[];
extern const u8 gUnknown_08D20A30[];
extern const u8 gBattleStatMask1_Tilemap[];
extern const u8 gBattleStatMask2_Tilemap[];
extern const u8 gBattleStatMask_Gfx[];
extern const u16 gBattleStatMask1_Pal[];
extern const u16 gBattleStatMask2_Pal[];
extern const u16 gBattleStatMask3_Pal[];
extern const u16 gBattleStatMask4_Pal[];
extern const u16 gBattleStatMask5_Pal[];
extern const u16 gBattleStatMask6_Pal[];
extern const u16 gBattleStatMask7_Pal[];
extern const u16 gBattleStatMask8_Pal[];

extern void sub_80DA48C(struct Sprite *);

static void AnimConfusionDuck(struct Sprite *sprite);
static void AnimSimplePaletteBlend(struct Sprite *sprite);
static void sub_80E1E2C(struct Sprite *sprite);
static void sub_80E1F3C(struct Sprite *sprite);
static void sub_80E24B8(struct Sprite *sprite);
static void sub_80E27A0(struct Sprite *sprite);
static void sub_80E2838(struct Sprite *sprite);
static void sub_80E2870(struct Sprite *sprite);
static void sub_80E2908(struct Sprite *sprite);
static void sub_80E2978(struct Sprite *sprite);
static void sub_80E29C0(struct Sprite *sprite);
static void sub_80E27E8(struct Sprite *sprite);
static void AnimConfusionDuckStep(struct Sprite *sprite);
static u32 UnpackSelectedBattleAnimPalettes(s16);
static void AnimSimplePaletteBlendStep(struct Sprite *sprite);
static void sub_80E1E80(struct Sprite *sprite);
static void sub_80E1F0C(struct Sprite *sprite);
static void sub_80E1FDC(u8, u8, u8);
static void sub_80E202C(u8 taskId);
static void sub_80E20E4(u8, u8, u8);
static void sub_80E2140(u8 taskId);
static void sub_80E2214(u8 taskId);
static void sub_80E22CC(u8 taskId);
static void sub_80E260C(void);
static void sub_80E255C(struct Sprite *sprite);
static void sub_80E2710(u8 taskId);
static void sub_80E29FC(struct Sprite *sprite);
static void AnimTask_BlendSpriteColor_Step1(u8 taskId, u32 selectedPalettes);
static void AnimTask_BlendSpriteColor_Step2(u8 taskId);
static void sub_80E2DB8(u8 taskId);
static void sub_80E2E10(u8 taskId);
static void sub_80E2EE8(struct Sprite *sprite);
static void sub_80E3194(u8 taskId);
static void sub_80E3338(u8 taskId);
static void sub_80E3704(u8 taskId);
static void sub_80E38F8(u8 taskId);
static void sub_80E39BC(u32, u16);
static void sub_80E3AD0(u8 taskId);
static void sub_80E3E64(u8 taskId);
static void sub_80E4368(u8 taskId);

const union AnimCmd gConfusionDuckSpriteAnim1[] =
{
    ANIMCMD_FRAME(0, 8),
    ANIMCMD_FRAME(4, 8),
    ANIMCMD_FRAME(0, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(8, 8),
    ANIMCMD_JUMP(0),
};

const union AnimCmd gConfusionDuckSpriteAnim2[] =
{
    ANIMCMD_FRAME(0, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(4, 8),
    ANIMCMD_FRAME(0, 8),
    ANIMCMD_FRAME(8, 8),
    ANIMCMD_JUMP(0),
};

const union AnimCmd *const gConfusionDuckSpriteAnimTable[] =
{
    gConfusionDuckSpriteAnim1,
    gConfusionDuckSpriteAnim2,
};

const struct SpriteTemplate gConfusionDuckSpriteTemplate =
{
    .tileTag = ANIM_TAG_DUCK,
    .paletteTag = ANIM_TAG_DUCK,
    .oam = &gOamData_837DF2C,
    .anims = gConfusionDuckSpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimConfusionDuck,
};

const struct SpriteTemplate gSimplePaletteBlendSpriteTemplate =
{
    .tileTag = 0,
    .paletteTag = 0,
    .oam = &gDummyOamData,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimSimplePaletteBlend,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB3DC =
{
    .tileTag = 0,
    .paletteTag = 0,
    .oam = &gDummyOamData,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_80E1E2C,
};

const union AnimCmd gSpriteAnim_83DB3F4[] =
{
    ANIMCMD_FRAME(0, 3),
    ANIMCMD_FRAME(16, 3),
    ANIMCMD_FRAME(32, 3),
    ANIMCMD_FRAME(48, 3),
    ANIMCMD_FRAME(64, 3),
    ANIMCMD_JUMP(0),
};

const union AnimCmd *const gSpriteAnimTable_83DB40C[] =
{
    gSpriteAnim_83DB3F4,
};

const struct SpriteTemplate gSpriteTemplate_83DB410 =
{
    .tileTag = ANIM_TAG_SPARKLE_4,
    .paletteTag = ANIM_TAG_SPARKLE_4,
    .oam = &gOamData_837DF34,
    .anims = gSpriteAnimTable_83DB40C,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_80E1F3C,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB428 =
{
    .tileTag = 0,
    .paletteTag = 0,
    .oam = &gDummyOamData,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_80E24B8,
};

const union AffineAnimCmd gSpriteAffineAnim_83DB440[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 8),
    AFFINEANIMCMD_END,
};

const union AffineAnimCmd gSpriteAffineAnim_83DB450[] =
{
    AFFINEANIMCMD_FRAME(0xD8, 0xD8, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 8),
    AFFINEANIMCMD_END,
};

const union AffineAnimCmd gSpriteAffineAnim_83DB468[] =
{
    AFFINEANIMCMD_FRAME(0xB0, 0xB0, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 8),
    AFFINEANIMCMD_END,
};

const union AffineAnimCmd gSpriteAffineAnim_83DB480[] =
{
    AFFINEANIMCMD_FRAME(0x80, 0x80, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 8),
    AFFINEANIMCMD_END,
};

const union AffineAnimCmd *const gSpriteAffineAnimTable_83DB498[] =
{
    gSpriteAffineAnim_83DB440,
    gSpriteAffineAnim_83DB450,
    gSpriteAffineAnim_83DB468,
    gSpriteAffineAnim_83DB480,
};

const struct SpriteTemplate gBasicHitSplatSpriteTemplate =
{
    .tileTag = ANIM_TAG_IMPACT,
    .paletteTag = ANIM_TAG_IMPACT,
    .oam = &gOamData_837E0B4,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E27A0,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB4C0 =
{
    .tileTag = ANIM_TAG_IMPACT,
    .paletteTag = ANIM_TAG_IMPACT,
    .oam = &gOamData_837E0B4,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E2838,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB4D8 =
{
    .tileTag = ANIM_TAG_WATER_IMPACT,
    .paletteTag = ANIM_TAG_WATER_IMPACT,
    .oam = &gOamData_837E0B4,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E27A0,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB4F0 =
{
    .tileTag = ANIM_TAG_IMPACT,
    .paletteTag = ANIM_TAG_IMPACT,
    .oam = &gOamData_837E0B4,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E2870,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB508 =
{
    .tileTag = ANIM_TAG_IMPACT,
    .paletteTag = ANIM_TAG_IMPACT,
    .oam = &gOamData_837E0B4,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E2908,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB520 =
{
    .tileTag = ANIM_TAG_CROSS_IMPACT,
    .paletteTag = ANIM_TAG_CROSS_IMPACT,
    .oam = &gOamData_837E054,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_80E2978,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB538 =
{
    .tileTag = ANIM_TAG_IMPACT,
    .paletteTag = ANIM_TAG_IMPACT,
    .oam = &gOamData_837DF94,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E29C0,
};

const struct SpriteTemplate gBattleAnimSpriteTemplate_83DB550 =
{
    .tileTag = ANIM_TAG_IMPACT,
    .paletteTag = ANIM_TAG_IMPACT,
    .oam = &gOamData_837E0B4,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gSpriteAffineAnimTable_83DB498,
    .callback = sub_80E27E8,
};

const u16 gUnknown_083DB568 = RGB(31, 31, 31);

// Moves a spinning duck around the mon's head.
// arg 0: initial x pixel offset
// arg 1: initial y pixel offset
// arg 2: initial wave offset
// arg 3: wave period (higher means faster wave)
// arg 4: duration
static void AnimConfusionDuck(struct Sprite *sprite)
{
    sprite->x += gBattleAnimArgs[0];
    sprite->y += gBattleAnimArgs[1];
    sprite->data[0] = gBattleAnimArgs[2];
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
    {
        sprite->data[1] = -gBattleAnimArgs[3];
        sprite->data[4] = 1;
    }
    else
    {
        sprite->data[1] = gBattleAnimArgs[3];
        sprite->data[4] = 0;
        StartSpriteAnim(sprite, 1);
    }

    sprite->data[3] = gBattleAnimArgs[4];
    sprite->callback = AnimConfusionDuckStep;
    sprite->callback(sprite);
}

static void AnimConfusionDuckStep(struct Sprite *sprite)
{
    sprite->x2 = Cos(sprite->data[0], 30);
    sprite->y2 = Sin(sprite->data[0], 10);

    if ((u16)sprite->data[0] < 128)
        sprite->oam.priority = 1;
    else
        sprite->oam.priority = 3;

    sprite->data[0] = (sprite->data[0] + sprite->data[1]) & 0xFF;
    if (++sprite->data[2] == sprite->data[3])
        DestroyAnimSprite(sprite);
}

// Performs a simple color blend on a specified sprite.
// arg 0: palette selector
// arg 1: delay
// arg 2: start blend amount
// arg 3: end blend amount
// arg 4: blend color
static void AnimSimplePaletteBlend(struct Sprite *sprite)
{
    u32 selectedPalettes = UnpackSelectedBattleAnimPalettes(gBattleAnimArgs[0]);
    BeginNormalPaletteFade(selectedPalettes, gBattleAnimArgs[1], gBattleAnimArgs[2], gBattleAnimArgs[3], gBattleAnimArgs[4]);
    sprite->invisible = TRUE;
    sprite->callback = AnimSimplePaletteBlendStep;
}

// Unpacks a bitfield and returns a bitmask of its selected palettes.
// Bits 0-6 of the selector parameter result in the following palettes being selected:
//   0: battle background palettes (BG palettes 1, 2, and 3)
//   1: gBattleAnimAttacker OBJ palette
//   2: gBattleAnimTarget OBJ palette
//   3: gBattleAnimAttacker partner OBJ palette
//   4: gBattleAnimTarget partner OBJ palette
//   5: BG palette 4
//   6: BG palette 5
static u32 UnpackSelectedBattleAnimPalettes(s16 selector)
{
    u8 arg0 = selector & 1;
    u8 arg1 = (selector >> 1) & 1;
    u8 arg2 = (selector >> 2) & 1;
    u8 arg3 = (selector >> 3) & 1;
    u8 arg4 = (selector >> 4) & 1;
    u8 arg5 = (selector >> 5) & 1;
    u8 arg6 = (selector >> 6) & 1;
    return sub_80791A8(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
}

static void AnimSimplePaletteBlendStep(struct Sprite *sprite)
{
    if (!gPaletteFade.active)
        DestroyAnimSprite(sprite);
}

static void sub_80E1E2C(struct Sprite *sprite)
{
    u32 selectedPalettes;

    sprite->data[0] = gBattleAnimArgs[1];
    sprite->data[1] = gBattleAnimArgs[1];
    sprite->data[2] = gBattleAnimArgs[2];
    sprite->data[3] = gBattleAnimArgs[3];
    sprite->data[4] = gBattleAnimArgs[4];
    sprite->data[5] = gBattleAnimArgs[5];
    sprite->data[6] = gBattleAnimArgs[6];
    sprite->data[7] = gBattleAnimArgs[0];

    selectedPalettes = UnpackSelectedBattleAnimPalettes(sprite->data[7]);
    BlendPalettes(selectedPalettes, gBattleAnimArgs[4], gBattleAnimArgs[3]);
    sprite->invisible = TRUE;
    sprite->callback = sub_80E1E80;
}

static void sub_80E1E80(struct Sprite *sprite)
{
    u32 selectedPalettes;

    if (sprite->data[0] > 0)
    {
        sprite->data[0]--;
        return;
    }

    if (gPaletteFade.active)
        return;

    if (sprite->data[2] == 0)
    {
        sprite->callback = sub_80E1F0C;
        return;
    }

    selectedPalettes = UnpackSelectedBattleAnimPalettes(sprite->data[7]);
    if (sprite->data[1] & 0x100)
        BlendPalettes(selectedPalettes, sprite->data[4], sprite->data[3]);
    else
        BlendPalettes(selectedPalettes, sprite->data[6], sprite->data[5]);

    sprite->data[1] ^= 0x100;
    sprite->data[0] = sprite->data[1] & 0xFF;
    sprite->data[2]--;
}

static void sub_80E1F0C(struct Sprite *sprite)
{
    u32 selectedPalettes;

    if (!gPaletteFade.active)
    {
        selectedPalettes = UnpackSelectedBattleAnimPalettes(sprite->data[7]);
        BlendPalettes(selectedPalettes, 0, 0);
        DestroyAnimSprite(sprite);
    }
}

static void sub_80E1F3C(struct Sprite *sprite)
{
    sprite->x += gBattleAnimArgs[0];
    sprite->y += gBattleAnimArgs[1];
    sprite->data[0] = 0;
    sprite->data[1] = 10;
    sprite->data[2] = 8;
    sprite->data[3] = 40;
    sprite->data[4] = 112;
    sprite->data[5] = 0;
    StoreSpriteCallbackInData(sprite, DestroySpriteAndMatrix);
    sprite->callback = sub_8078174;
    sprite->callback(sprite);
}

void sub_80E1F8C(u8 taskId)
{
    gTasks[taskId].data[0] = gBattleAnimArgs[0];
    gTasks[taskId].data[1] = gBattleAnimArgs[1];
    gTasks[taskId].data[2] = gBattleAnimArgs[2];
    gTasks[taskId].data[3] = gBattleAnimArgs[3];
    gTasks[taskId].data[4] = gBattleAnimArgs[4];
    gTasks[taskId].data[5] = gBattleAnimArgs[5];
    gTasks[taskId].data[8] = 0;
    sub_80E1FDC(taskId, 0, gTasks[taskId].data[4]);
    gTasks[taskId].func = sub_80E202C;
}

static void sub_80E1FDC(u8 taskId, u8 initialBlendAmount, u8 targetBlendAmount)
{
    u32 selectedPalettes = UnpackSelectedBattleAnimPalettes(gTasks[taskId].data[0]);
    BeginNormalPaletteFade(
        selectedPalettes,
        gTasks[taskId].data[1],
        initialBlendAmount,
        targetBlendAmount,
        gTasks[taskId].data[5]);

    gTasks[taskId].data[2]--;
    gTasks[taskId].data[8] ^= 1;
}

static void sub_80E202C(u8 taskId)
{
    u8 initialBlendAmount, targetBlendAmount;
    if (!gPaletteFade.active)
    {
        if (gTasks[taskId].data[2] > 0)
        {
            if (gTasks[taskId].data[8] == 0)
            {
                initialBlendAmount = gTasks[taskId].data[3];
                targetBlendAmount = gTasks[taskId].data[4];
            }
            else
            {
                initialBlendAmount = gTasks[taskId].data[4];
                targetBlendAmount = gTasks[taskId].data[3];
            }

            if (gTasks[taskId].data[2] == 1)
                targetBlendAmount = 0;

            sub_80E1FDC(taskId, initialBlendAmount, targetBlendAmount);
        }
        else
        {
            DestroyAnimVisualTask(taskId);
        }
    }
}

void sub_80E2094(u8 taskId)
{
    gTasks[taskId].data[0] = gBattleAnimArgs[0];
    gTasks[taskId].data[1] = gBattleAnimArgs[1];
    gTasks[taskId].data[2] = gBattleAnimArgs[2];
    gTasks[taskId].data[3] = gBattleAnimArgs[3];
    gTasks[taskId].data[4] = gBattleAnimArgs[4];
    gTasks[taskId].data[5] = gBattleAnimArgs[5];
    gTasks[taskId].data[8] = 0;
    sub_80E20E4(taskId, 0, gTasks[taskId].data[4]);
    gTasks[taskId].func = sub_80E2140;
}

static void sub_80E20E4(u8 taskId, u8 initialBlendAmount, u8 targetBlendAmount)
{
    u8 paletteIndex = IndexOfSpritePaletteTag(gTasks[taskId].data[0]);
    BeginNormalPaletteFade(
        1 << (paletteIndex + 16),
        gTasks[taskId].data[1],
        initialBlendAmount,
        targetBlendAmount,
        gTasks[taskId].data[5]);

    gTasks[taskId].data[2]--;
    gTasks[taskId].data[8] ^= 1;
}

static void sub_80E2140(u8 taskId)
{
    u8 initialBlendAmount, targetBlendAmount;
    if (!gPaletteFade.active)
    {
        if (gTasks[taskId].data[2] > 0)
        {
            if (gTasks[taskId].data[8] == 0)
            {
                initialBlendAmount = gTasks[taskId].data[3];
                targetBlendAmount = gTasks[taskId].data[4];
            }
            else
            {
                initialBlendAmount = gTasks[taskId].data[4];
                targetBlendAmount = gTasks[taskId].data[3];
            }

            if (gTasks[taskId].data[2] == 1)
                targetBlendAmount = 0;

            sub_80E20E4(taskId, initialBlendAmount, targetBlendAmount);
        }
        else
        {
            DestroyAnimVisualTask(taskId);
        }
    }
}

void sub_80E21A8(u8 taskId)
{
    u8 paletteIndex;

    gTasks[taskId].data[0] = gBattleAnimArgs[1];
    gTasks[taskId].data[1] = gBattleAnimArgs[1];
    gTasks[taskId].data[2] = gBattleAnimArgs[2];
    gTasks[taskId].data[3] = gBattleAnimArgs[3];
    gTasks[taskId].data[4] = gBattleAnimArgs[4];
    gTasks[taskId].data[5] = gBattleAnimArgs[5];
    gTasks[taskId].data[6] = gBattleAnimArgs[6];
    gTasks[taskId].data[7] = gBattleAnimArgs[0];

    paletteIndex = IndexOfSpritePaletteTag(gBattleAnimArgs[0]);
    BeginNormalPaletteFade(
        1 << (paletteIndex + 16),
        0,
        gBattleAnimArgs[4],
        gBattleAnimArgs[4],
        gBattleAnimArgs[3]);

    gTasks[taskId].func = sub_80E2214;
}

static void sub_80E2214(u8 taskId)
{
    u32 selectedPalettes;

    if (gTasks[taskId].data[0] > 0)
    {
        gTasks[taskId].data[0]--;
        return;
    }

    if (gPaletteFade.active)
        return;

    if (gTasks[taskId].data[2] == 0)
    {
        gTasks[taskId].func = sub_80E22CC;
        return;
    }

    selectedPalettes = 1 << (IndexOfSpritePaletteTag(gTasks[taskId].data[7]) + 16);
    if (gTasks[taskId].data[1] & 0x100)
    {
        BeginNormalPaletteFade(
            selectedPalettes,
            0,
            gTasks[taskId].data[4],
            gTasks[taskId].data[4],
            gTasks[taskId].data[3]);
    }
    else
    {
        BeginNormalPaletteFade(
            selectedPalettes,
            0,
            gTasks[taskId].data[6],
            gTasks[taskId].data[6],
            gTasks[taskId].data[5]);
    }

    gTasks[taskId].data[1] ^= 0x100;
    gTasks[taskId].data[0] = gTasks[taskId].data[1] & 0xFF;
    gTasks[taskId].data[2]--;
}

static void sub_80E22CC(u8 taskId)
{
    u32 selectedPalettes;

    if (!gPaletteFade.active)
    {
        selectedPalettes = 1 << (IndexOfSpritePaletteTag(gTasks[taskId].data[7]) + 16);
        BeginNormalPaletteFade(selectedPalettes, 0, 0, 0, RGB(0, 0, 0));
        DestroyAnimVisualTask(taskId);
    }
}

void sub_80E2324(u8 taskId)
{
    u32 selectedPalettes = 0;
    u8 attackerBattler = gBattleAnimAttacker;
    u8 targetBattler = gBattleAnimTarget;

    if (gBattleAnimArgs[0] & 0x100)
        selectedPalettes = sub_80791A8(1, 0, 0, 0, 0, 0, 0);

    if (gBattleAnimArgs[1] & 0x100)
        selectedPalettes |= (0x10000 << attackerBattler);

    if (gBattleAnimArgs[2] & 0x100)
        selectedPalettes |= (0x10000 << targetBattler);

    InvertPlttBuffer(selectedPalettes);
    DestroyAnimVisualTask(taskId);
}

void unref_sub_80E23A8(u8 taskId)
{
    u8 attackerBattler;
    u8 targetBattler;
    u8 paletteIndex;
    u32 selectedPalettes = 0;

    if (gTasks[taskId].data[0] == 0)
    {
        gTasks[taskId].data[2] = gBattleAnimArgs[0];
        gTasks[taskId].data[3] = gBattleAnimArgs[1];
        gTasks[taskId].data[4] = gBattleAnimArgs[2];
        gTasks[taskId].data[1] = gBattleAnimArgs[3];
        gTasks[taskId].data[5] = gBattleAnimArgs[4];
        gTasks[taskId].data[6] = gBattleAnimArgs[5];
        gTasks[taskId].data[7] = gBattleAnimArgs[6];
    }

    gTasks[taskId].data[0]++;
    attackerBattler = gBattleAnimAttacker;
    targetBattler = gBattleAnimTarget;

    if (gTasks[taskId].data[2] & 0x100)
        selectedPalettes = 0x0000FFFF;

    if (gTasks[taskId].data[2] & 0x1)
    {
        paletteIndex = IndexOfSpritePaletteTag(gSprites[gHealthboxSpriteIds[attackerBattler]].template->paletteTag);
        selectedPalettes |= ((1 << paletteIndex) << 16);
    }
    
    if (gTasks[taskId].data[3] & 0x100)
        selectedPalettes |= ((1 << attackerBattler) << 16);

    if (gTasks[taskId].data[4] & 0x100)
        selectedPalettes |= ((1 << targetBattler) << 16);

    TintPlttBuffer(selectedPalettes, gTasks[taskId].data[5], gTasks[taskId].data[6], gTasks[taskId].data[7]);
    if (gTasks[taskId].data[0] == gTasks[taskId].data[1])
    {
        UnfadePlttBuffer(selectedPalettes);
        DestroyAnimVisualTask(taskId);
    }
}

static void sub_80E24B8(struct Sprite *sprite)
{
    u16 var0;

    sprite->invisible = TRUE;
    sprite->data[0] = -gBattleAnimArgs[0];
    sprite->data[1] = gBattleAnimArgs[1];
    sprite->data[2] = gBattleAnimArgs[1];
    sprite->data[3] = gBattleAnimArgs[2];

    switch (gBattleAnimArgs[3])
    {
    case 0:
        StoreSpriteCallbackInData(sprite, (void *)&gBattle_BG3_X);
        break;
    case 1:
        StoreSpriteCallbackInData(sprite, (void *)&gBattle_BG3_Y);
        break;
    case 2:
        StoreSpriteCallbackInData(sprite, (void *)&gSpriteCoordOffsetX);
        break;
    default:
        StoreSpriteCallbackInData(sprite, (void *)&gSpriteCoordOffsetY);
        break;
    }

    #if MODERN
    sprite->data[4] = *(u16 *)(sprite->data[6] | (sprite->data[7] << 16));
    #else
    sprite->data[4] = *(u32 *)(sprite->data[6] | (sprite->data[7] << 16));
    #endif
    sprite->data[5] = gBattleAnimArgs[3];
    var0 = sprite->data[5] - 2;
    if (var0 < 2)
        sub_80E260C();

    sprite->callback = sub_80E255C;
}

static void sub_80E255C(struct Sprite *sprite)
{
    u8 i;
    u16 var0;

    if (sprite->data[3] > 0)
    {
        sprite->data[3]--;
        if (sprite->data[1] > 0)
        {
            sprite->data[1]--;
        }
        else
        {
            sprite->data[1] = sprite->data[2];
            #if MODERN
            *(u16 *)(sprite->data[6] | (sprite->data[7] << 16)) += sprite->data[0];
            #else
            *(u32 *)(sprite->data[6] | (sprite->data[7] << 16)) += sprite->data[0];
            #endif
            sprite->data[0] = -sprite->data[0];
        }
    }
    else
    {
        #if MODERN
        *(u16 *)(sprite->data[6] | (sprite->data[7] << 16)) = sprite->data[4];
        #else
        *(u32 *)(sprite->data[6] | (sprite->data[7] << 16)) = sprite->data[4];
        #endif
        var0 = sprite->data[5] - 2;
        if (var0 < 2)
        {
            for (i = 0; i < gBattlersCount; i++)
                gSprites[gBattlerSpriteIds[i]].coordOffsetEnabled = 0;
        }

        DestroyAnimSprite(sprite);
    }
}

static void sub_80E260C(void)
{
    gSprites[gBattlerSpriteIds[gBattleAnimAttacker]].coordOffsetEnabled = 0;
    gSprites[gBattlerSpriteIds[gBattleAnimTarget]].coordOffsetEnabled = 0;

    if (gBattleAnimArgs[4] == 2)
    {
        gSprites[gBattlerSpriteIds[gBattleAnimAttacker]].coordOffsetEnabled = 1;
        gSprites[gBattlerSpriteIds[gBattleAnimTarget]].coordOffsetEnabled = 1;
    }
    else
    {
        if (gBattleAnimArgs[4] == 0)
            gSprites[gBattlerSpriteIds[gBattleAnimAttacker]].coordOffsetEnabled = 1;
        else
            gSprites[gBattlerSpriteIds[gBattleAnimTarget]].coordOffsetEnabled = 1;
    }
}

void sub_80E26BC(u8 taskId)
{
    gTasks[taskId].data[0] = gBattleAnimArgs[0];
    gTasks[taskId].data[1] = gBattleAnimArgs[1];
    gTasks[taskId].data[2] = gBattleAnimArgs[2];
    gTasks[taskId].data[3] = gBattleAnimArgs[3];
    gTasks[taskId].data[8] = gBattleAnimArgs[3];
    gBattle_BG3_X = gBattleAnimArgs[0];
    gBattle_BG3_Y = gBattleAnimArgs[1];
    gTasks[taskId].func = sub_80E2710;
    gTasks[taskId].func(taskId);
}

static void sub_80E2710(u8 taskId)
{
    if (gTasks[taskId].data[3] == 0)
    {
        if (gBattle_BG3_X == gTasks[taskId].data[0])
            gBattle_BG3_X = -gTasks[taskId].data[0];
        else
            gBattle_BG3_X = gTasks[taskId].data[0];

        if (gBattle_BG3_Y == -gTasks[taskId].data[1])
            gBattle_BG3_Y = 0;
        else
            gBattle_BG3_Y = -gTasks[taskId].data[1];

        gTasks[taskId].data[3] = gTasks[taskId].data[8];
        if (--gTasks[taskId].data[2] == 0)
        {
            gBattle_BG3_X = 0;
            gBattle_BG3_Y = 0;
            DestroyAnimVisualTask(taskId);
        }
    }
    else
    {
        gTasks[taskId].data[3]--;
    }
}

static void sub_80E27A0(struct Sprite *sprite)
{
    StartSpriteAffineAnim(sprite, gBattleAnimArgs[3]);
    if (gBattleAnimArgs[2] == 0)
        InitAnimSpritePos(sprite, 1);
    else
        sub_8078764(sprite, TRUE);

    sprite->callback = sub_80785E4;
    StoreSpriteCallbackInData(sprite, DestroyAnimSprite);
}

static void sub_80E27E8(struct Sprite *sprite)
{
    StartSpriteAffineAnim(sprite, gBattleAnimArgs[3]);
    if (gBattleAnimArgs[2] == 0)
        InitAnimSpritePos(sprite, 1);
    else
        sub_8078764(sprite, TRUE);

    sprite->data[0] = gBattleAnimArgs[4];
    sprite->callback = sub_80785E4;
    StoreSpriteCallbackInData(sprite, sub_80DA48C);
}

static void sub_80E2838(struct Sprite *sprite)
{
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER && !IsContest())
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];
    
    sub_80E27A0(sprite);
}

static void sub_80E2870(struct Sprite *sprite)
{
    if (gBattleAnimArgs[1] == -1)
        gBattleAnimArgs[1] = Random() & 3;

    StartSpriteAffineAnim(sprite, gBattleAnimArgs[1]);
    if (gBattleAnimArgs[0] == 0)
        InitAnimSpritePos(sprite, 0);
    else
        sub_8078764(sprite, FALSE);

    sprite->x2 += (Random() % 48) - 24;
    sprite->y2 += (Random() % 24) - 12;

    StoreSpriteCallbackInData(sprite, DestroySpriteAndMatrix);
    sprite->callback = sub_80785E4;
}

static void sub_80E2908(struct Sprite *sprite)
{
    sprite->data[0] = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
    sprite->x = gSprites[sprite->data[0]].x + gSprites[sprite->data[0]].x2;
    sprite->y = gSprites[sprite->data[0]].y + gSprites[sprite->data[0]].y2;
    sprite->x2 = gBattleAnimArgs[1];
    sprite->y2 = gBattleAnimArgs[2];
    StartSpriteAffineAnim(sprite, gBattleAnimArgs[3]);
    StoreSpriteCallbackInData(sprite, DestroySpriteAndMatrix);
    sprite->callback = sub_80785E4;
}

static void sub_80E2978(struct Sprite *sprite)
{
    if (gBattleAnimArgs[2] == 0)
        InitAnimSpritePos(sprite, 1);
    else
        sub_8078764(sprite, TRUE);

    sprite->data[0] = gBattleAnimArgs[3];
    StoreSpriteCallbackInData(sprite, DestroyAnimSprite);
    sprite->callback = WaitAnimForDuration;
}

static void sub_80E29C0(struct Sprite *sprite)
{
    StartSpriteAffineAnim(sprite, gBattleAnimArgs[3]);
    if (gBattleAnimArgs[2] == 0)
        InitAnimSpritePos(sprite, 1);
    else
        sub_8078764(sprite, TRUE);

    sprite->callback = sub_80E29FC;
}

static void sub_80E29FC(struct Sprite *sprite)
{
    sprite->invisible ^= 1;
    if (sprite->data[0]++ > 12)
        DestroyAnimSprite(sprite);
}

void sub_80E2A38(u8 taskId)
{
    u32 selectedPalettes = UnpackSelectedBattleAnimPalettes(gBattleAnimArgs[0]);
    selectedPalettes |= sub_80792C0(
        (gBattleAnimArgs[0] >> 7)  & 1,
        (gBattleAnimArgs[0] >> 8)  & 1,
        (gBattleAnimArgs[0] >> 9)  & 1,
        (gBattleAnimArgs[0] >> 10) & 1);

    AnimTask_BlendSpriteColor_Step1(taskId, selectedPalettes);
}

void sub_80E2A7C(u8 taskId)
{
    u8 battler;
    u32 selectedPalettes;
    u8 sp[2];

    sp[1] = 0xFF;
    selectedPalettes = UnpackSelectedBattleAnimPalettes(1);
    switch (gBattleAnimArgs[0])
    {
    case 2:
        selectedPalettes = 0;
        // fall through
    case 0:
        sp[0] = gBattleAnimAttacker;
        break;
    case 3:
        selectedPalettes = 0;
        // fall through
    case 1:
        sp[0] = gBattleAnimTarget;
        break;
    case 4:
        sp[0] = gBattleAnimAttacker;
        sp[1] = gBattleAnimTarget;
        break;
    case 5:
        sp[0] = 0xFF;
        break;
    case 6:
        selectedPalettes = 0;
        sp[0] = gBattleAnimAttacker ^ 2;
        break;
    case 7:
        selectedPalettes = 0;
        sp[0] = gBattleAnimTarget ^ 2;
        break;
    }

    for (battler = 0; battler < 4; battler++)
    {
        if (battler != sp[0] && battler != sp[1] && IsAnimBankSpriteVisible(battler))
        {
            u8 paletteIndex = sub_80793A8(battler);
            selectedPalettes |= (0x10000 << paletteIndex);
        }
    }

    AnimTask_BlendSpriteColor_Step1(taskId, selectedPalettes);
}

void sub_80E2B74(u8 taskId)
{
    u32 selectedPalettes = UnpackSelectedBattleAnimPalettes(gBattleAnimArgs[0]);

    switch (gBattleTerrain)
    {
    case BATTLE_TERRAIN_GRASS:
        gBattleAnimArgs[4] = 0x0B0C;
        break;
    case BATTLE_TERRAIN_LONG_GRASS:
        gBattleAnimArgs[4] = 0x09E0;
        break;
    case BATTLE_TERRAIN_SAND:
        gBattleAnimArgs[4] = 0x2F1E;
        break;
    case BATTLE_TERRAIN_UNDERWATER:
        gBattleAnimArgs[4] = 0x4800;
        break;
    case BATTLE_TERRAIN_WATER:
        gBattleAnimArgs[4] = 0x7ECB;
        break;
    case BATTLE_TERRAIN_POND:
        gBattleAnimArgs[4] = 0x7ECB;
        break;
    case BATTLE_TERRAIN_MOUNTAIN:
        gBattleAnimArgs[4] = 0x2A16;
        break;
    case BATTLE_TERRAIN_CAVE:
        gBattleAnimArgs[4] = 0x0D2E;
        break;
    case BATTLE_TERRAIN_BUILDING:
        gBattleAnimArgs[4] = 0x7FFF;
        break;
    case BATTLE_TERRAIN_PLAIN:
        gBattleAnimArgs[4] = 0x7FFF;
        break;
    }

    AnimTask_BlendSpriteColor_Step1(taskId, selectedPalettes);
}

// Used to add a color mask to a Sprite.
// arg 0: spriteId
// arg 1: ??? unknown
// arg 2: ??? unknown
// arg 3: coefficient
// arg 4: color code
void AnimTask_BlendSpriteColor(u8 taskId)
{
    u8 paletteIndex = IndexOfSpritePaletteTag(gBattleAnimArgs[0]);
    AnimTask_BlendSpriteColor_Step1(taskId, 1 << (paletteIndex + 16));
}

static void AnimTask_BlendSpriteColor_Step1(u8 taskId, u32 selectedPalettes)
{
    gTasks[taskId].data[0] = selectedPalettes;
    gTasks[taskId].data[1] = selectedPalettes >> 16;
    gTasks[taskId].data[2] = gBattleAnimArgs[1];
    gTasks[taskId].data[3] = gBattleAnimArgs[2];
    gTasks[taskId].data[4] = gBattleAnimArgs[3];
    gTasks[taskId].data[5] = gBattleAnimArgs[4];
    gTasks[taskId].data[10] = gBattleAnimArgs[2];
    gTasks[taskId].func = AnimTask_BlendSpriteColor_Step2;
    gTasks[taskId].func(taskId);
}

static void AnimTask_BlendSpriteColor_Step2(u8 taskId)
{
    u32 selectedPalettes;
    u16 singlePaletteMask = 0;

    if (gTasks[taskId].data[9] == gTasks[taskId].data[2])
    {
        gTasks[taskId].data[9] = 0;
        selectedPalettes = gTasks[taskId].data[0] | (gTasks[taskId].data[1] << 16);
        while (selectedPalettes != 0)
        {
            if (selectedPalettes & 1)
                BlendPalette(singlePaletteMask, 16, gTasks[taskId].data[10], gTasks[taskId].data[5]);
            singlePaletteMask += 0x10;
            selectedPalettes >>= 1;
        }

        if (gTasks[taskId].data[10] < gTasks[taskId].data[4])
            gTasks[taskId].data[10]++;
        else if (gTasks[taskId].data[10] > gTasks[taskId].data[4])
            gTasks[taskId].data[10]--;
        else
            DestroyAnimVisualTask(taskId);
    }
    else
    {
        gTasks[taskId].data[9]++;
    }
}

void sub_80E2D78(u8 taskId)
{
    BeginHardwarePaletteFade(
        gBattleAnimArgs[0],
        gBattleAnimArgs[1],
        gBattleAnimArgs[2],
        gBattleAnimArgs[3],
        gBattleAnimArgs[4]);

    gTasks[taskId].func = sub_80E2DB8;
}

static void sub_80E2DB8(u8 taskId)
{
    if (!gPaletteFade.active)
        DestroyAnimVisualTask(taskId);
}

void sub_80E2DD8(u8 taskId)
{
    struct Task *task = &gTasks[taskId];

    task->data[0] = gBattleAnimArgs[0];
    task->data[1] = 0;
    task->data[2] = gBattleAnimArgs[1];
    task->data[3] = gBattleAnimArgs[2];
    task->data[4] = gBattleAnimArgs[3];
    task->data[5] = 0;
    task->func = sub_80E2E10;
}

static void sub_80E2E10(u8 taskId)
{
    struct Task *task = &gTasks[taskId];

    if (task->data[4])
    {
        if (task->data[1])
        {
            task->data[1]--;
        }
        else
        {
            task->data[6] = duplicate_obj_of_side_rel2move_in_transparent_mode(task->data[0]);
            if (task->data[6] >= 0)
            {
                gSprites[task->data[6]].oam.priority = task->data[0] ? 1 : 2;
                gSprites[task->data[6]].data[0] = task->data[3];
                gSprites[task->data[6]].data[1] = taskId;
                gSprites[task->data[6]].data[2] = 5;
                gSprites[task->data[6]].callback = sub_80E2EE8;
                task->data[5]++;
            }

            task->data[4]--;
            task->data[1] = task->data[2];
        }
    }
    else if (task->data[5] == 0)
    {
        DestroyAnimVisualTask(taskId);
    }
}

static void sub_80E2EE8(struct Sprite *sprite)
{
    if (sprite->data[0])
    {
        sprite->data[0]--;
    }
    else
    {
        gTasks[sprite->data[1]].data[sprite->data[2]]--;
        obj_delete_but_dont_free_vram(sprite);
    }
}

void sub_80E2F2C(u8 taskId)
{
    u16 species;
    int spriteId, newSpriteId;
    u16 var0;
    struct Struct_sub_8078914 subStruct;

    var0 = 0;
    gBattle_WIN0H = 0;
    gBattle_WIN0V = 0;
    REG_WININ = 0x3F3F;
    REG_WINOUT = 0x3F3D;
    REG_DISPCNT |= DISPCNT_OBJWIN_ON;
    REG_BLDCNT = 0x3F42;
    REG_BLDALPHA = 0x0C08;
    REG_BG1CNT_BITFIELD.priority = 0;
    REG_BG1CNT_BITFIELD.screenSize = 0;
    REG_BG1CNT_BITFIELD.areaOverflowMode = 1;

    if (!IsContest())
        REG_BG1CNT_BITFIELD.charBaseBlock = 1;

    if (IsDoubleBattle() && !IsContest())
    {
        if (GetBattlerPosition(gBattleAnimAttacker) == B_POSITION_OPPONENT_RIGHT
         || GetBattlerPosition(gBattleAnimAttacker) == B_POSITION_PLAYER_LEFT)
        {
            if (IsAnimBankSpriteVisible(gBattleAnimAttacker ^ 2) == TRUE)
            {
                gSprites[gBattlerSpriteIds[gBattleAnimAttacker ^ 2]].oam.priority -= 1;
                REG_BG1CNT_BITFIELD.priority = 1;
                var0 = 1;
            }
        }
    }

    if (IsContest())
    {
        species = gContestResources__moveAnim.species;
    }
    else
    {
        if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
            species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES);
        else
            species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES);
    }

    spriteId = GetAnimBattlerSpriteId(0);
    newSpriteId = sub_807A4A0(gBattleAnimAttacker, spriteId, species);
    sub_8078914(&subStruct);
    DmaFill32Defvars(3, 0, subStruct.field_4, 0x1000);
    LZDecompressVram(&gUnknown_08D20A30, subStruct.field_4);
    if (IsContest())
        sub_80763FC(subStruct.field_8, (u16 *)subStruct.field_4, 0, 0);
    
    LZDecompressVram(&gUnknown_08D20A14, subStruct.field_0);
    LoadPalette(&gUnknown_083DB568, subStruct.field_8 * 16 + 1, 2);

    gBattle_BG1_X = -gSprites[spriteId].x + 32;
    gBattle_BG1_Y = -gSprites[spriteId].y + 32;
    gTasks[taskId].data[0] = newSpriteId;
    gTasks[taskId].data[6] = var0;
    gTasks[taskId].func = sub_80E3194;
}

static void sub_80E3194(u8 taskId)
{
    struct Struct_sub_8078914 subStruct;
    struct Sprite *sprite;

    gTasks[taskId].data[10] += 4;
    gBattle_BG1_Y -= 4;
    if (gTasks[taskId].data[10] == 64)
    {
        gTasks[taskId].data[10] = 0;
        gBattle_BG1_Y += 64;
        if (++gTasks[taskId].data[11] == 4)
        {
            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;
            sprite = &gSprites[GetAnimBattlerSpriteId(0)]; // unused
            sprite = &gSprites[gTasks[taskId].data[0]];
            DestroySprite(sprite);

            sub_8078914(&subStruct);
            DmaFill32Defvars(3, 0, subStruct.field_4, 0x800);
            if (gTasks[taskId].data[6] == 1)
                gSprites[gBattlerSpriteIds[gBattleAnimAttacker ^ 2]].oam.priority++;

            REG_BG1CNT_BITFIELD.areaOverflowMode = 0;
            do {} while(0); // needed to match. perhaps part of a debug macro
            gBattle_BG1_Y = 0;
            DestroyAnimVisualTask(taskId);
        }
    }
}

void sub_80E32E0(u8 taskId)
{
    u8 i;

    for (i = 0; i < 8; i++)
        gTasks[taskId].data[i] = gBattleAnimArgs[i];

    gTasks[taskId].func = sub_80E3338;
}

static void sub_80E3338(u8 taskId)
{
    int i;
    u8 battler1, battler2;
    u16 species;
    u8 spriteId, spriteId2;
    u16 var0;
    struct Struct_sub_8078914 subStruct;
    s16 taskData[8];

    spriteId2 = 0;
    var0 = 0;

    for (i = 0; i < 8; i++)
    {
        taskData[i] = gTasks[taskId].data[i]; 
        gTasks[taskId].data[i] = 0;
    }

    if (taskData[2] == 0)
        battler1 = gBattleAnimAttacker;
    else
        battler1 = gBattleAnimTarget;

    battler2 = battler1 ^ 2;
    if (IsContest() || (taskData[3] && !IsAnimBankSpriteVisible(battler2)))
        taskData[3] = 0;

    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;
    if (!IsContest())
        REG_BG1CNT_BITFIELD.charBaseBlock = 1;

    if (IsDoubleBattle() && taskData[3] == 0)
    {
        if (GetBattlerPosition(battler1) == B_POSITION_OPPONENT_RIGHT
         || GetBattlerPosition(battler1) == B_POSITION_PLAYER_LEFT)
        {
            if (IsAnimBankSpriteVisible(battler2) == TRUE)
            {
                gSprites[gBattlerSpriteIds[battler2]].oam.priority -= 1;
                REG_BG1CNT_BITFIELD.priority = 1;
                var0 = 1;
            }
        }
    }

    if (IsContest())
    {
        species = gContestResources__moveAnim.species;
    }
    else
    {
        if (GetBattlerSide(battler1) != B_SIDE_PLAYER)
            species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler1]], MON_DATA_SPECIES);
        else
            species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battler1]], MON_DATA_SPECIES);
    }

    spriteId = sub_807A4A0(battler1, gBattlerSpriteIds[battler1], species);
    if (taskData[3])
        spriteId2 = sub_807A4A0(battler2, gBattlerSpriteIds[battler2], species);
    
    sub_8078914(&subStruct);
    if (taskData[0] == 0)
        LZDecompressVram(&gBattleStatMask1_Tilemap, subStruct.field_4);
    else
        LZDecompressVram(&gBattleStatMask2_Tilemap, subStruct.field_4);

    if (IsContest())
        sub_80763FC(subStruct.field_8, (u16 *)subStruct.field_4, 0, 0);

    LZDecompressVram(&gBattleStatMask_Gfx, subStruct.field_0);

    if (taskData[1] == 0)
        LoadCompressedPalette(gBattleStatMask2_Pal, subStruct.field_8 << 4, 32);
    else if (taskData[1] == 1)
        LoadCompressedPalette(gBattleStatMask1_Pal, subStruct.field_8 << 4, 32);
    else if (taskData[1] == 2)
        LoadCompressedPalette(gBattleStatMask3_Pal, subStruct.field_8 << 4, 32);
    else if (taskData[1] == 3)
        LoadCompressedPalette(gBattleStatMask4_Pal, subStruct.field_8 << 4, 32);
    else if (taskData[1] == 4)
        LoadCompressedPalette(gBattleStatMask6_Pal, subStruct.field_8 << 4, 32);
    else if (taskData[1] == 5)
        LoadCompressedPalette(gBattleStatMask7_Pal, subStruct.field_8 << 4, 32);
    else if (taskData[1] == 6)
        LoadCompressedPalette(gBattleStatMask8_Pal, subStruct.field_8 << 4, 32);
    else
        LoadCompressedPalette(gBattleStatMask5_Pal, subStruct.field_8 << 4, 32);

    gBattle_BG1_X = 0;
    gBattle_BG1_Y = 0;

    if (taskData[0] == 1)
    {
        gBattle_BG1_X = 64;
        gTasks[taskId].data[1] = -3;
    }
    else
    {
        gTasks[taskId].data[1] = 3;
    }

    if (taskData[4] == 0)
    {
        gTasks[taskId].data[4] = 10;
        gTasks[taskId].data[5] = 20;
    }
    else
    {
        gTasks[taskId].data[4] = 13;
        gTasks[taskId].data[5] = 30;
    }

    gTasks[taskId].data[0] = spriteId;
    gTasks[taskId].data[2] = taskData[3];
    gTasks[taskId].data[3] = spriteId2;
    gTasks[taskId].data[6] = var0;
    gTasks[taskId].data[7] = gBattlerSpriteIds[battler2];
    gTasks[taskId].func = sub_80E3704;

    if (taskData[0] == 0)
        PlaySE12WithPanning(SE_M_STAT_INCREASE, BattleAnimAdjustPanning2(SOUND_PAN_ATTACKER_NEG));
    else
        PlaySE12WithPanning(SE_M_STAT_DECREASE, BattleAnimAdjustPanning2(SOUND_PAN_ATTACKER_NEG));
}

static void sub_80E3704(u8 taskId)
{
    gBattle_BG1_Y += gTasks[taskId].data[1];

    switch (gTasks[taskId].data[15])
    {
    case 0:
        if (gTasks[taskId].data[11]++ > 0)
        {
            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] == gTasks[taskId].data[4])
                gTasks[taskId].data[15]++;
        }
        break;
    case 1:
        if (++gTasks[taskId].data[10] == gTasks[taskId].data[5])
            gTasks[taskId].data[15]++;
        break;
    case 2:
        if (gTasks[taskId].data[11]++ > 0)
        {
            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]]);
                if (gTasks[taskId].data[2])
                    DestroySprite(&gSprites[gTasks[taskId].data[3]]);

                if (gTasks[taskId].data[6] == 1)
                    gSprites[gTasks[taskId].data[7]].oam.priority++;

                REG_BG1CNT_BITFIELD.areaOverflowMode = 0;
                DestroyAnimVisualTask(taskId);
            }
        }
        break;
    }
}

void sub_80E388C(u8 taskId)
{
    u32 selectedPalettes = sub_80792C0(1, 1, 1, 1);
    sub_80E39BC(selectedPalettes, 0);
    gTasks[taskId].data[14] = selectedPalettes >> 16;

    selectedPalettes = sub_80791A8(1, 0, 0, 0, 0, 0, 0) & 0xFFFF;
    sub_80E39BC(selectedPalettes, 0xFFFF);
    gTasks[taskId].data[15] = selectedPalettes;

    gTasks[taskId].data[0] = 0;
    gTasks[taskId].data[1] = 0;
    gTasks[taskId].func = sub_80E38F8;
}

static void sub_80E38F8(u8 taskId)
{
    u16 i;
    struct Task *task = &gTasks[taskId];

    switch (task->data[0])
    {
    case 0:
        if (++task->data[1] > 6)
        {
            task->data[1] = 0;
            task->data[2] = 16;
            task->data[0]++;
        }
        break;
    case 1:
        if (++task->data[1] > 1)
        {
            task->data[1] = 0;
            task->data[2]--;

            for (i = 0; i < 16; i++)
            {
                if ((task->data[15] >> i) & 1)
                {
                    u16 paletteOffset = i * 16;
                    BlendPalette(paletteOffset, 16, task->data[2], 0xFFFF);
                }

                if ((task->data[14] >> i) & 1)
                {
                    u16 paletteOffset = i * 16 + 0x100;
                    BlendPalette(paletteOffset, 16, task->data[2], 0);
                }
            }

            if (task->data[2] == 0)
                task->data[0]++;
        }
        break;
    case 2:
        DestroyAnimVisualTask(taskId);
        break;
    }
}

static void sub_80E39BC(u32 selectedPalettes, u16 color)
{
    u16 i;

    for (i = 0; i < 32; i++)
    {
        if (selectedPalettes & 1)
        {
            u16 curOffset = i * 16;
            u16 paletteOffset = curOffset;
            while (curOffset < paletteOffset + 16)
            {
                gPlttBufferFaded[curOffset] = color;
                curOffset++;
            }
        }

        selectedPalettes >>= 1;
    }
}

void sub_80E3A08(u8 taskId)
{
    u32 i;
    int j;
    u32 selectedPalettes = 0;

    for (i = 0; i < 4; i++)
    {
        if (gBattleAnimAttacker != i)
            selectedPalettes |= 1 << (i + 16);
    }

    for (j = 5; j != 0; j--)
        gBattleAnimArgs[j] = gBattleAnimArgs[j - 1];

    AnimTask_BlendSpriteColor_Step1(taskId, selectedPalettes);
}

void sub_80E3A58(u8 taskId)
{
    u8 newTaskId;

    sub_80789D4(0);
    newTaskId = CreateTask(sub_80E3AD0, 5);
    if (gBattleAnimArgs[2] && GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
    {
        gBattleAnimArgs[0] = -gBattleAnimArgs[0];
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];
    }

    gTasks[newTaskId].data[1] = gBattleAnimArgs[0];
    gTasks[newTaskId].data[2] = gBattleAnimArgs[1];
    gTasks[newTaskId].data[3] = gBattleAnimArgs[3];
    gTasks[newTaskId].data[0]++;
    DestroyAnimVisualTask(taskId);
}

static void sub_80E3AD0(u8 taskId)
{
    gTasks[taskId].data[10] += gTasks[taskId].data[1];
    gTasks[taskId].data[11] += gTasks[taskId].data[2];
    gBattle_BG3_X += gTasks[taskId].data[10] >> 8;
    gBattle_BG3_Y += gTasks[taskId].data[11] >> 8;
    gTasks[taskId].data[10] &= 0xFF;
    gTasks[taskId].data[11] &= 0xFF;

    if (gBattleAnimArgs[7] == gTasks[taskId].data[3])
    {
        gBattle_BG3_X = 0;
        gBattle_BG3_Y = 0;
        sub_80789D4(1);
        DestroyTask(taskId);
    }
}

void sub_80E3B4C(u8 taskId)
{
    gBattleAnimArgs[7] = GetBattlerSide(gBattleAnimAttacker);
    DestroyAnimVisualTask(taskId);
}

void sub_80E3B78(u8 taskId)
{
    gBattleAnimArgs[7] = GetBattlerSide(gBattleAnimTarget);
    DestroyAnimVisualTask(taskId);
}

void sub_80E3BA4(u8 taskId)
{
    gBattleAnimArgs[7] = (gBattleAnimAttacker ^ 2) == gBattleAnimTarget;
    DestroyAnimVisualTask(taskId);
}

void sub_80E3BDC(u8 taskId)
{
    u16 i;

    for (i = 0; i < 4; i++)
    {
        if (i != gBattleAnimAttacker && IsAnimBankSpriteVisible(i))
            gSprites[gBattlerSpriteIds[i]].invisible = gBattleAnimArgs[0];
    }

    DestroyAnimVisualTask(taskId);
}

void sub_80E3C4C(u8 taskId, int unused, u16 arg2, u8 battler1, u8 arg4, u8 arg5, u8 arg6, u8 arg7, const u8 *arg8, const u8 *arg9, const u16 *palette)
{
    u16 species;
    u8 spriteId, spriteId2;
    struct Struct_sub_8078914 subStruct;
    u8 battler2;

    spriteId2 = 0;
    battler2 = battler1 ^ 2;

    if (IsContest() || (arg4 && !IsAnimBankSpriteVisible(battler2)))
        arg4 = 0;

    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;
    if (!IsContest())
        REG_BG1CNT_BITFIELD.charBaseBlock = 1;

    if (IsContest())
    {
        species = gContestResources__moveAnim.species;
    }
    else
    {
        if (GetBattlerSide(battler1) != B_SIDE_PLAYER)
            species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler1]], MON_DATA_SPECIES);
        else
            species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battler1]], MON_DATA_SPECIES);
    }

    spriteId = sub_807A4A0(battler1, gBattlerSpriteIds[battler1], species);
    if (arg4)
        spriteId2 = sub_807A4A0(battler2, gBattlerSpriteIds[battler2], species);

    sub_8078914(&subStruct);
    LZDecompressVram(arg9, subStruct.field_4);
    if (IsContest())
        sub_80763FC(subStruct.field_8, (u16 *)subStruct.field_4, 0, 0);

    LZDecompressVram(arg8, subStruct.field_0);
    LoadCompressedPalette(palette, subStruct.field_8 << 4, 32);
    gBattle_BG1_X = 0;
    gBattle_BG1_Y = 0;
    gTasks[taskId].data[1] = arg2;
    gTasks[taskId].data[4] = arg5;
    gTasks[taskId].data[5] = arg7;
    gTasks[taskId].data[6] = arg6;
    gTasks[taskId].data[0] = spriteId;
    gTasks[taskId].data[2] = arg4;
    gTasks[taskId].data[3] = spriteId2;
    gTasks[taskId].func = sub_80E3E64;
}

static void sub_80E3E64(u8 taskId)
{
    gTasks[taskId].data[13] += gTasks[taskId].data[1] < 0 ? -gTasks[taskId].data[1] : gTasks[taskId].data[1];
    if (gTasks[taskId].data[1] < 0)
        gBattle_BG1_Y -= gTasks[taskId].data[13] >> 8;
    else
        gBattle_BG1_Y += gTasks[taskId].data[13] >> 8;

    gTasks[taskId].data[13] &= 0xFF;
    switch (gTasks[taskId].data[15])
    {
    case 0:
        if (gTasks[taskId].data[11]++ >= gTasks[taskId].data[6])
        {
            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] == gTasks[taskId].data[4])
                gTasks[taskId].data[15]++;
        }
        break;
    case 1:
        if (++gTasks[taskId].data[10] == gTasks[taskId].data[5])
            gTasks[taskId].data[15]++;
        break;
    case 2:
        if (gTasks[taskId].data[11]++ >= gTasks[taskId].data[6])
        {
            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]]);
                if (gTasks[taskId].data[2])
                    DestroySprite(&gSprites[gTasks[taskId].data[3]]);

                REG_BG1CNT_BITFIELD.areaOverflowMode = 0;
                DestroyAnimVisualTask(taskId);
            }
        }
        break;
    }
}

void AnimTask_GetBattleTerrain(u8 taskId)
{
    gBattleAnimArgs[0] = gBattleTerrain;
    DestroyAnimVisualTask(taskId);
}

void sub_80E4028(u8 taskId)
{
    u32 selectedPalettes;
    u8 *dest;
    int offset;
    int i = 0;

    if (gBattleAnimArgs[0] == 0)
    {
        selectedPalettes = sub_80791A8(1, 0, 0, 0, 0, 0, 0);
        while ((selectedPalettes & 1) == 0)
        {
            selectedPalettes >>= 1;
            i++;
        }
    }
    else if (gBattleAnimArgs[0] == 1)
    {
        i = gBattleAnimAttacker + 16;
    }
    else if (gBattleAnimArgs[0] == 2)
    {
        i = gBattleAnimTarget + 16;
    }

    offset = gBattleAnimArgs[1] * 32;
    dest = IsContest() ? &((u8 *)eBattleAnimPalBackup_Contest)[offset] : &((u8 *)eBattleAnimPalBackup_Battle)[offset];
    // This doesn't match when u16* is used.
    memcpy(dest, &((u8 *)gPlttBufferUnfaded)[i * 32], 32);
    DestroyAnimVisualTask(taskId);
}


void sub_80E40D0(u8 taskId)
{
    u32 selectedPalettes;
    u8 *src;
    u8 *dest;
    int offset;
    int i = 0;

    if (gBattleAnimArgs[0] == 0)
    {
        selectedPalettes = sub_80791A8(1, 0, 0, 0, 0, 0, 0);
        while ((selectedPalettes & 1) == 0)
        {
            selectedPalettes >>= 1;
            i++;
        }
    }
    else if (gBattleAnimArgs[0] == 1)
    {
        i = gBattleAnimAttacker + 16;
    }
    else if (gBattleAnimArgs[0] == 2)
    {
        i = gBattleAnimTarget + 16;
    }

    dest = &((u8 *)gPlttBufferUnfaded)[i * 32];
    offset = gBattleAnimArgs[1] * 32;
    src = IsContest() ? &((u8 *)eBattleAnimPalBackup_Contest)[offset] : &((u8 *)eBattleAnimPalBackup_Battle)[offset];
    // This doesn't match when u16* is used.
    memcpy(dest, src, 32);
    DestroyAnimVisualTask(taskId);
}

void sub_80E4178(u8 taskId)
{
    u32 selectedPalettes;
    int i = 0;

    if (gBattleAnimArgs[0] == 0)
    {
        selectedPalettes = sub_80791A8(1, 0, 0, 0, 0, 0, 0);
        while ((selectedPalettes & 1) == 0)
        {
            selectedPalettes >>= 1;
            i++;
        }
    }
    else if (gBattleAnimArgs[0] == 1)
    {
        i = gBattleAnimAttacker + 16;
    }
    else if (gBattleAnimArgs[0] == 2)
    {
        i = gBattleAnimTarget + 16;
    }

    memcpy(&gPlttBufferUnfaded[i * 16], &gPlttBufferFaded[i * 16], 32);
    DestroyAnimVisualTask(taskId);
}

void sub_80E4200(u8 taskId)
{
    if (IsContest())
        gBattleAnimArgs[7] = 1;
    else
        gBattleAnimArgs[7] = 0;

    DestroyAnimVisualTask(taskId);
}

void sub_80E4234(u8 taskId)
{
    gBattleAnimAttacker = gBattlerTarget;
    gBattleAnimTarget = gEffectBattler;
    DestroyAnimVisualTask(taskId);
}

void sub_80E4264(u8 taskId)
{
    if (GetBattlerSide(gBattleAnimAttacker) == GetBattlerSide(gBattleAnimTarget))
        gBattleAnimArgs[7] = 1;
    else
        gBattleAnimArgs[7] = 0;

    DestroyAnimVisualTask(taskId);
}

void sub_80E42B0(u8 taskId)
{
    gBattleAnimTarget = gBattlerTarget;
    DestroyAnimVisualTask(taskId);
}

void sub_80E42D0(u8 taskId)
{
    gBattleAnimAttacker = gBattlerAttacker;
    gBattleAnimTarget = gEffectBattler;
    DestroyAnimVisualTask(taskId);
}

void sub_80E4300(u8 taskId)
{
    if (IsContest())
    {
        DestroyAnimVisualTask(taskId);
    }
    else
    {
        gTasks[taskId].data[0] = gBattleSpriteInfo[gBattleAnimAttacker].invisible;
        gBattleSpriteInfo[gBattleAnimAttacker].invisible = TRUE;
        gTasks[taskId].func = sub_80E4368;
        gAnimVisualTaskCount--;
    }
}

static void sub_80E4368(u8 taskId)
{
    if (gBattleAnimArgs[7] == 0x1000)
    {
        gBattleSpriteInfo[gBattleAnimAttacker].invisible = gTasks[taskId].data[0] & 1;
        DestroyTask(taskId);
    }
}
