#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "rom_8077ABC.h"
#include "sprite.h"
#include "task.h"
#include "trig.h"

#define SPRITE gSprites[TASK.data[0]]

extern s16 gBattleAnimArgs[8];

extern u8 gBattlerSpriteIds[];
extern s32 gAnimMoveDmg;
extern u16 gAnimMovePower;
extern u8 gBattleAnimAttacker;
extern u8 gBattleAnimTarget;

static void AnimTask_ShakeMonStep(u8 taskId);
static void AnimTask_ShakeMon2Step(u8 taskId);
static void AnimTask_ShakeMonInPlaceStep(u8 taskId);
static void AnimTask_ShakeAndSinkMonStep(u8 taskId);
static void sub_80A8488(u8 taskId);
static void DoHorizontalLunge(struct Sprite *sprite);
static void ReverseHorizontalLungeDirection(struct Sprite *sprite);
static void DoVerticalDip(struct Sprite *sprite);
static void ReverseVerticalDipDirection(struct Sprite* sprite);
static void SlideMonToOriginalPos(struct Sprite *sprite);
static void SlideMonToOriginalPosStep(struct Sprite *sprite);
static void SlideMonToOffset(struct Sprite *sprite);
static void sub_80A8818(struct Sprite *sprite);
static void sub_80A88F0(struct Sprite *sprite);
static void AnimTask_WindUpLungePart1(u8 taskId);
static void AnimTask_WindUpLungePart2(u8 taskId);
static void AnimTask_SwayMonStep(u8 taskId);
static void AnimTask_ScaleMonAndRestoreStep(u8 taskId);
static void sub_80A8FD8(u8 taskId);
static void sub_80A913C(u8 taskId);

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

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

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

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

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

// Task to facilitate simple shaking of a pokemon's picture in battle.
// The shaking alternates between the original position and the target position.
// arg 0: anim battler
// arg 1: x pixel offset
// arg 2: y pixel offset
// arg 3: num times to shake
// arg 4: frame delay
void AnimTask_ShakeMon(u8 taskId)
{
    u8 spriteId;
    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
    if (spriteId == 0xff)
    {
        DestroyAnimVisualTask(taskId);
        return;
    }
    gSprites[spriteId].x2 = gBattleAnimArgs[1];
    gSprites[spriteId].y2 = gBattleAnimArgs[2];
    TASK.data[0] = spriteId;
    TASK.data[1] = gBattleAnimArgs[3];
    TASK.data[2] = gBattleAnimArgs[4];
    TASK.data[3] = gBattleAnimArgs[4];
    TASK.data[4] = gBattleAnimArgs[1];
    TASK.data[5] = gBattleAnimArgs[2];
    TASK.func = AnimTask_ShakeMonStep;
    AnimTask_ShakeMonStep(taskId);
}

static void AnimTask_ShakeMonStep(u8 taskId)
{
    if (TASK.data[3] == 0)
    {
        if (SPRITE.x2 == 0)
        {
            SPRITE.x2 = TASK.data[4];
        }
        else
        {
            SPRITE.x2 = 0;
        }
        if (SPRITE.y2 == 0)
        {
            SPRITE.y2 = TASK.data[5];
        }
        else
        {
            SPRITE.y2 = 0;
        }
        TASK.data[3] = TASK.data[2];
        if (--TASK.data[1] == 0)
        {
            SPRITE.x2 = 0;
            SPRITE.y2 = 0;
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
    else
    {
        TASK.data[3]--;
    }
}

// Task to facilitate simple shaking of a pokemon's picture in battle.
// The shaking alternates between the positive and negative versions of the specified pixel offsets.
// arg 0: anim battler
// arg 1: x pixel offset
// arg 2: y pixel offset
// arg 3: num times to shake
// arg 4: frame delay
void AnimTask_ShakeMon2(u8 taskId)
{
    u8 sprite;
    bool8 destroy;
    u8 side;
    destroy = FALSE;
    if (gBattleAnimArgs[0] < 4)
    {
        sprite = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
        if (sprite == 0xff)
        {
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
    else if (gBattleAnimArgs[0] != 8)
    {
        switch (gBattleAnimArgs[0])
        {
        case 4:
            side = GetBattlerAtPosition(0);
            break;
        case 5:
            side = GetBattlerAtPosition(2);
            break;
        case 6:
            side = GetBattlerAtPosition(1);
            break;
        case 7:
        default:
            side = GetBattlerAtPosition(3);
            break;
        }

        if (IsAnimBankSpriteVisible(side) == FALSE)
            destroy = TRUE;

        sprite = gBattlerSpriteIds[side];
    }
    else
    {
        sprite = gBattlerSpriteIds[gBattleAnimAttacker];
    }

    if (destroy)
    {
        DestroyAnimVisualTask(taskId);
        return;
    }

    gSprites[sprite].x2 = gBattleAnimArgs[1];
    gSprites[sprite].y2 = gBattleAnimArgs[2];
    TASK.data[0] = sprite;
    TASK.data[1] = gBattleAnimArgs[3];
    TASK.data[2] = gBattleAnimArgs[4];
    TASK.data[3] = gBattleAnimArgs[4];
    TASK.data[4] = gBattleAnimArgs[1];
    TASK.data[5] = gBattleAnimArgs[2];
    TASK.func = AnimTask_ShakeMon2Step;
    TASK.func(taskId);
}

static void AnimTask_ShakeMon2Step(u8 taskId)
{
    if (TASK.data[3] == 0)
    {
        if (SPRITE.x2 == TASK.data[4])
            SPRITE.x2 = -TASK.data[4];
        else
            SPRITE.x2 = TASK.data[4];

        if (SPRITE.y2 == TASK.data[5])
            SPRITE.y2 = -TASK.data[5];
        else
            SPRITE.y2 = TASK.data[5];

        TASK.data[3] = TASK.data[2];
        if (--TASK.data[1] == 0)
        {
            SPRITE.x2 = 0;
            SPRITE.y2 = 0;
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
    else
    {
        TASK.data[3]--;
    }
}

// Task to facilitate simple shaking of a pokemon's picture in battle.
// The shaking alternates between the positive and negative versions of the specified pixel offsets
// with respect to the current location of the mon's picture.
// arg 0: battler
// arg 1: x offset
// arg 2: y offset
// arg 3: num shakes
// arg 4: delay
void AnimTask_ShakeMonInPlace(u8 taskId)
{
    u8 spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
    if (spriteId == 0xff)
    {
        DestroyAnimVisualTask(taskId);
        return;
    }

    gSprites[spriteId].x2 += gBattleAnimArgs[1];
    gSprites[spriteId].y2 += gBattleAnimArgs[2];
    TASK.data[0] = spriteId;
    TASK.data[1] = 0;
    TASK.data[2] = gBattleAnimArgs[3];
    TASK.data[3] = 0;
    TASK.data[4] = gBattleAnimArgs[4];
    TASK.data[5] = gBattleAnimArgs[1] * 2;
    TASK.data[6] = gBattleAnimArgs[2] * 2;
    TASK.func = AnimTask_ShakeMonInPlaceStep;
    TASK.func(taskId);
}

static void AnimTask_ShakeMonInPlaceStep(u8 taskId)
{
    if (TASK.data[3] == 0)
    {
        if (TASK.data[1] & 1)
        {
            SPRITE.x2 += TASK.data[5];
            SPRITE.y2 += TASK.data[6];
        }
        else
        {
            SPRITE.x2 -= TASK.data[5];
            SPRITE.y2 -= TASK.data[6];
        }
        TASK.data[3] = TASK.data[4];
        if (++TASK.data[1] >= TASK.data[2])
        {
            if (TASK.data[1] & 1)
            {
                SPRITE.x2 += TASK.data[5] / 2;
                SPRITE.y2 += TASK.data[6] / 2;
            }
            else
            {
                SPRITE.x2 -= TASK.data[5] / 2;
                SPRITE.y2 -= TASK.data[6] / 2;
            }
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
    else
    {
        TASK.data[3]--;
    }
}

// Shakes a mon bg horizontally and moves it downward linearly.
// arg 0: battler
// arg 1: x offset
// arg 2: frame delay between each movement
// arg 3: downward speed (subpixel)
// arg 4: duration
void AnimTask_ShakeAndSinkMon(u8 taskId)
{
    u8 sprite = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
    gSprites[sprite].x2 = gBattleAnimArgs[1];
    TASK.data[0] = sprite;
    TASK.data[1] = gBattleAnimArgs[1];
    TASK.data[2] = gBattleAnimArgs[2];
    TASK.data[3] = gBattleAnimArgs[3];
    TASK.data[4] = gBattleAnimArgs[4];
    TASK.func = AnimTask_ShakeAndSinkMonStep;
    TASK.func(taskId);
}

static void AnimTask_ShakeAndSinkMonStep(u8 taskId)
{
    s16 x;
    u8 sprite;
    sprite = TASK.data[0];
    x = TASK.data[1];
    if (TASK.data[2] == TASK.data[8]++)
    {
        TASK.data[8] = 0;
        if (gSprites[sprite].x2 == x)
            x = -x;

        gSprites[sprite].x2 += x;
    }

    TASK.data[1] = x;
    TASK.data[9] += TASK.data[3];
    gSprites[sprite].y2 = TASK.data[9] >> 8;
    if (--TASK.data[4] == 0)
    {
        DestroyAnimVisualTask(taskId);
        return;
    }
}

// Moves a mon bg picture along an elliptical path that begins
// and ends at the mon's origin location.
// arg 0: battler
// arg 1: ellipse width
// arg 2: ellipse height
// arg 3: num loops
// arg 4: speed (valid values are 0-5)
void AnimTask_TranslateMonElliptical(u8 taskId)
{
    u8 i;
    u8 spriteId;
    u8 wavePeriod;

    wavePeriod = 1;
    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
    if (gBattleAnimArgs[4] > 5)
        gBattleAnimArgs[4] = 5;

    for (i = 0; i < gBattleAnimArgs[4]; i++)
    {
        wavePeriod <<= 1;
    }

    TASK.data[0] = spriteId;
    TASK.data[1] = gBattleAnimArgs[1];
    TASK.data[2] = gBattleAnimArgs[2];
    TASK.data[3] = gBattleAnimArgs[3];
    TASK.data[4] = wavePeriod;
    TASK.func = sub_80A8488;
    TASK.func(taskId);
}

static void sub_80A8488(u8 taskId)
{
    u8 spriteId = TASK.data[0];
    gSprites[spriteId].x2 = Sin(TASK.data[5], TASK.data[1]);
    gSprites[spriteId].y2 = -Cos(TASK.data[5], TASK.data[2]);
    gSprites[spriteId].y2 += TASK.data[2];
    TASK.data[5] += TASK.data[4];
    TASK.data[5] &= 0xff;

    if (TASK.data[5] == 0)
        TASK.data[3]--;

    if (TASK.data[3] == 0)
    {
        gSprites[spriteId].x2 = 0;
        gSprites[spriteId].y2 = 0;
        DestroyAnimVisualTask(taskId);
        return;
    }
}

// Moves a mon bg picture along an elliptical path that begins
// and ends at the mon's origin location. Reverses the direction
// of the path if it's not on the player's side of the battle.
// arg 0: battler
// arg 1: ellipse width
// arg 2: ellipse height
// arg 3: num loops
// arg 4: speed (valid values are 0-5)
void AnimTask_TranslateMonEllipticalRespectSide(u8 taskId)
{
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];

    AnimTask_TranslateMonElliptical(taskId);
}

// Performs a simple horizontal lunge, where the mon moves
// horizontally, and then moves back in the opposite direction.
// arg 0: duration of single lunge direction
// arg 1: x pixel delta that is applied each frame
static void DoHorizontalLunge(struct Sprite *sprite)
{
    sprite->invisible = TRUE;
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
        sprite->data[1] = -gBattleAnimArgs[1];
    else
        sprite->data[1] = gBattleAnimArgs[1];

    sprite->data[0] = gBattleAnimArgs[0];
    sprite->data[2] = 0;
    sprite->data[3] = gBattlerSpriteIds[gBattleAnimAttacker];
    sprite->data[4] = gBattleAnimArgs[0];
    StoreSpriteCallbackInData(sprite, ReverseHorizontalLungeDirection);
    sprite->callback = TranslateMonBGUntil;
}

static void ReverseHorizontalLungeDirection(struct Sprite *sprite)
{
    sprite->data[0] = sprite->data[4];
    sprite->data[1] = -sprite->data[1];
    sprite->callback = TranslateMonBGUntil;
    StoreSpriteCallbackInData(sprite, DestroyAnimSprite);
}

// Performs a simple vertical dipping motion, where moves vertically, and then
// moves back in the opposite direction.
// arg 0: duration of single dip direction
// arg 1: y pixel delta that is applied each frame
// arg 2: battler
static void DoVerticalDip(struct Sprite *sprite)
{
    u8 spriteId;
    sprite->invisible = TRUE;
    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[2]);
    sprite->data[0] = gBattleAnimArgs[0];
    sprite->data[1] = 0;
    sprite->data[2] = gBattleAnimArgs[1];
    sprite->data[3] = spriteId;
    sprite->data[4] = gBattleAnimArgs[0];
    StoreSpriteCallbackInData(sprite, ReverseVerticalDipDirection);
    sprite->callback = TranslateMonBGUntil;
}

static void ReverseVerticalDipDirection(struct Sprite *sprite)
{
    sprite->data[0] = sprite->data[4];
    sprite->data[2] = -sprite->data[2];
    sprite->callback = TranslateMonBGUntil;
    StoreSpriteCallbackInData(sprite, DestroyAnimSprite);
}

// Linearly slides a mon's bg picture back to its original sprite position.
// The sprite parameter is a dummy sprite used for facilitating the movement with its callback.
// arg 0: 1 = target or 0 = attacker
// arg 1: direction (0 = horizontal and vertical, 1 = horizontal only, 2 = vertical only)
// arg 2: duration
static void SlideMonToOriginalPos(struct Sprite *sprite)
{
    int something;
    int monSpriteId;
    if (!gBattleAnimArgs[0])
        monSpriteId = gBattlerSpriteIds[gBattleAnimAttacker];
    else
        monSpriteId = gBattlerSpriteIds[gBattleAnimTarget];

    sprite->data[0] = gBattleAnimArgs[2];
    sprite->data[1] = gSprites[monSpriteId].x + gSprites[monSpriteId].x2;
    sprite->data[2] = gSprites[monSpriteId].x;
    sprite->data[3] = gSprites[monSpriteId].y + gSprites[monSpriteId].y2;
    sprite->data[4] = gSprites[monSpriteId].y;
    something = 0;
    InitSpriteDataForLinearTranslation(sprite);
    sprite->data[3] = something;
    sprite->data[4] = something;
    sprite->data[5] = gSprites[monSpriteId].x2;
    sprite->data[6] = gSprites[monSpriteId].y2;
    sprite->invisible = TRUE;

    if (gBattleAnimArgs[1] == 1)
        sprite->data[2] = something;
    else if (gBattleAnimArgs[1] == 2)
        sprite->data[1] = something;

    sprite->data[7] = gBattleAnimArgs[1];
    sprite->data[7] |= monSpriteId << 8;
    sprite->callback = SlideMonToOriginalPosStep;
}

static void SlideMonToOriginalPosStep(struct Sprite *sprite)
{
    s8 monSpriteId;
    u8 lo;
    struct Sprite *monSprite;

    lo = sprite->data[7] & 0xff;
    monSpriteId = sprite->data[7] >> 8;
    monSprite = &gSprites[monSpriteId];
    if (sprite->data[0] == 0)
    {
        if (lo < 2)
            monSprite->x2 = 0;

        if (lo == 2 || lo == 0)
            monSprite->y2 = 0;

        DestroyAnimSprite(sprite);
    }
    else
    {
        sprite->data[0]--;
        sprite->data[3] += sprite->data[1];
        sprite->data[4] += sprite->data[2];
        monSprite->x2 = (s8)(sprite->data[3] >> 8) + sprite->data[5];
        monSprite->y2 = (s8)(sprite->data[4] >> 8) + sprite->data[6];
    }
}

// Linearly translates a mon to a target offset. The horizontal offset
// is mirrored for the opponent's pokemon, and the vertical offset
// is only mirrored if arg 3 is set to 1.
// arg 0: 0 = attacker, 1 = target
// arg 1: target x pixel offset
// arg 2: target y pixel offset
// arg 3: mirror vertical translation for opposite battle side
// arg 4: duration
static void SlideMonToOffset(struct Sprite *sprite)
{
    u8 battler;
    u8 monSpriteId;
    if (!gBattleAnimArgs[0])
        battler = gBattleAnimAttacker;
    else
        battler = gBattleAnimTarget;

    monSpriteId = gBattlerSpriteIds[battler];
    if (GetBattlerSide(battler) != B_SIDE_PLAYER)
    {
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];
        if (gBattleAnimArgs[3] == 1)
        {
            gBattleAnimArgs[2] = -gBattleAnimArgs[2];
        }
    }

    sprite->data[0] = gBattleAnimArgs[4];
    sprite->data[1] = gSprites[monSpriteId].x;
    sprite->data[2] = gSprites[monSpriteId].x + gBattleAnimArgs[1];
    sprite->data[3] = gSprites[monSpriteId].y;
    sprite->data[4] = gSprites[monSpriteId].y + gBattleAnimArgs[2];
    InitSpriteDataForLinearTranslation(sprite);
    sprite->data[3] = 0;
    sprite->data[4] = 0;
    sprite->data[5] = monSpriteId;
    sprite->invisible = TRUE;
    StoreSpriteCallbackInData(sprite, DestroyAnimSprite);
    sprite->callback = TranslateMonBGSubPixelUntil;
}

static void sub_80A8818(struct Sprite *sprite)
{
    u8 spriteId;
    u8 v1;
    sprite->invisible = TRUE;
    if (!gBattleAnimArgs[0])
    {
        v1 = gBattleAnimAttacker;
    }
    else
    {
        v1 = gBattleAnimTarget;
    }
    spriteId = gBattlerSpriteIds[v1];
    if (GetBattlerSide(v1))
    {
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];
        if (gBattleAnimArgs[3] == 1)
        {
            gBattleAnimArgs[2] = -gBattleAnimArgs[2];
        }
    }
    sprite->data[0] = gBattleAnimArgs[4];
    sprite->data[1] = gSprites[spriteId].x + gSprites[spriteId].x2;
    sprite->data[2] = sprite->data[1] + gBattleAnimArgs[1];
    sprite->data[3] = gSprites[spriteId].y + gSprites[spriteId].y2;
    sprite->data[4] = sprite->data[3] + gBattleAnimArgs[2];
    InitSpriteDataForLinearTranslation(sprite);
    sprite->data[3] = gSprites[spriteId].x2 << 8;
    sprite->data[4] = gSprites[spriteId].y2 << 8;
    sprite->data[5] = spriteId;
    sprite->data[6] = gBattleAnimArgs[5];
    if (!gBattleAnimArgs[5])
    {
        StoreSpriteCallbackInData(sprite, DestroyAnimSprite);
    }
    else
    {
        StoreSpriteCallbackInData(sprite, sub_80A88F0);
    }
    sprite->callback = TranslateMonBGSubPixelUntil;
}


static void sub_80A88F0(struct Sprite *sprite)
{
    gSprites[sprite->data[5]].x2 = 0;
    gSprites[sprite->data[5]].y2 = 0;
    DestroyAnimSprite(sprite);
}

// Task to facilitate a two-part translation animation, in which the sprite
// is first translated in an arc to one position.  Then, it "lunges" to a target
// x offset.  Used in TAKE_DOWN, for example.
// arg 0: anim bank
// arg 1: horizontal speed (subpixel)
// arg 2: wave amplitude
// arg 3: first duration
// arg 4: delay before starting lunge
// arg 5: target x offset for lunge
// arg 6: lunge duration
void AnimTask_WindUpLunge(u8 taskId)
{
    s16 wavePeriod = 0x8000 / gBattleAnimArgs[3];
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
    {
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];
        gBattleAnimArgs[5] = -gBattleAnimArgs[5];
    }
    TASK.data[0] = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
    TASK.data[1] = (gBattleAnimArgs[1] << 8) / gBattleAnimArgs[3];
    TASK.data[2] = gBattleAnimArgs[2];
    TASK.data[3] = gBattleAnimArgs[3];
    TASK.data[4] = gBattleAnimArgs[4];
    TASK.data[5] = (gBattleAnimArgs[5] << 8) / gBattleAnimArgs[6];
    TASK.data[6] = gBattleAnimArgs[6];
    TASK.data[7] = wavePeriod;
    TASK.func = AnimTask_WindUpLungePart1;
}

static void AnimTask_WindUpLungePart1(u8 taskId)
{
    u8 spriteId;
    spriteId = TASK.data[0];
    TASK.data[11] += TASK.data[1];
    gSprites[spriteId].x2 = TASK.data[11] >> 8;
    gSprites[spriteId].y2 = Sin((u8)(TASK.data[10] >> 8), TASK.data[2]);
    TASK.data[10] += TASK.data[7];
    if (--TASK.data[3] == 0)
    {
        TASK.func = AnimTask_WindUpLungePart2;
    }
}

static void AnimTask_WindUpLungePart2(u8 taskId)
{
    u8 spriteId;
    if (TASK.data[4] > 0)
    {
        TASK.data[4]--;
    }
    else
    {
        spriteId = TASK.data[0];
        TASK.data[12] += TASK.data[5];
        gSprites[spriteId].x2 = (TASK.data[12] >> 8) + (TASK.data[11] >> 8);
        if (--TASK.data[6] == 0)
        {
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
}

static void sub_80A8B3C(u8 taskId);

void sub_80A8A80(u8 taskId)
{
    u8 spriteId;
    switch (gBattleAnimArgs[0])
    {
    case 0:
    case 1:
        spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
        break;
    case 2:
        if (!IsAnimBankSpriteVisible(gBattleAnimAttacker ^ 2))
        {
            DestroyAnimVisualTask(taskId);
            return;
        }
        spriteId = gBattlerSpriteIds[gBattleAnimAttacker ^ 2];
        break;
    case 3:
        if (!IsAnimBankSpriteVisible(gBattleAnimTarget ^ 2))
        {
            DestroyAnimVisualTask(taskId);
            return;
        }
        spriteId = gBattlerSpriteIds[gBattleAnimTarget ^ 2];
        break;
    default:
        DestroyAnimVisualTask(taskId);
        return;
    }
    TASK.data[0] = spriteId;
    if (GetBattlerSide(gBattleAnimTarget))
    {
        TASK.data[1] = gBattleAnimArgs[1];
    }
    else
    {
        TASK.data[1] = -gBattleAnimArgs[1];
    }
    TASK.func = sub_80A8B3C;
}

static void sub_80A8B3C(u8 taskId)
{
    u8 spriteId = TASK.data[0];
    gSprites[spriteId].x2 += TASK.data[1];
    if (gSprites[spriteId].x2 + gSprites[spriteId].x + 0x20 > 0x130u)
    {
        DestroyAnimVisualTask(taskId);
        return;
    }
}

// Task that facilitates translating the mon bg picture back and forth
// in a swaying motion (uses Sine wave). It can sway either horizontally
// or vertically, but not both.
// arg 0: direction (0 = horizontal, 1 = vertical)
// arg 1: wave amplitude
// arg 2: wave period
// arg 3: num sways
// arg 4: which mon (0 = attacker, 1`= target)
void AnimTask_SwayMon(u8 taskId)
{
    u8 spriteId;
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
        gBattleAnimArgs[1] = -gBattleAnimArgs[1];

    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[4]);
    TASK.data[0] = gBattleAnimArgs[0];
    TASK.data[1] = gBattleAnimArgs[1];
    TASK.data[2] = gBattleAnimArgs[2];
    TASK.data[3] = gBattleAnimArgs[3];
    TASK.data[4] = spriteId;

    if (gBattleAnimArgs[4] == 0)
        TASK.data[5] = gBattleAnimAttacker;
    else
        TASK.data[5] = gBattleAnimTarget;

    TASK.data[12] = 1;
    TASK.func = AnimTask_SwayMonStep;
}

static void AnimTask_SwayMonStep(u8 taskId)
{
    s16 sineValue;
    u8 spriteId;
    int waveIndex;
    u16 sineIndex;

    spriteId = TASK.data[4];
    sineIndex = TASK.data[10] + TASK.data[2];
    TASK.data[10] = sineIndex;
    waveIndex = sineIndex >> 8;
    sineValue = Sin(waveIndex, TASK.data[1]);

    if (TASK.data[0] == 0)
    {
        gSprites[spriteId].x2 = sineValue;
    }
    else
    {
        if (GetBattlerSide(TASK.data[5]) == B_SIDE_PLAYER)
        {
            gSprites[spriteId].y2 = (sineValue >= 0) ? sineValue : -sineValue;
        }
        else
        {
            gSprites[spriteId].y2 = (sineValue >= 0) ? -sineValue : sineValue;
        }
    }

    if (((waveIndex >= 0x80u) && (TASK.data[11] == 0) && (TASK.data[12] == 1))
        || ((waveIndex < 0x7fu) && (TASK.data[11] == 1) && (TASK.data[12] == 0)))
    {
        TASK.data[11] ^= 1;
        TASK.data[12] ^= 1;
        if (--TASK.data[3] == 0)
        {
            gSprites[spriteId].x2 = 0;
            gSprites[spriteId].y2 = 0;
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
}

// Scales a mon's sprite, and then scales back to its original dimensions.
// arg 0: x scale delta
// arg 1: y scale delta
// arg 2: duration
// arg 3: anim bank
// arg 4: sprite object mode
void AnimTask_ScaleMonAndRestore(u8 taskId)
{
    u8 spriteId;
    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[3]);
    PrepareBattlerSpriteForRotScale(spriteId, gBattleAnimArgs[4]);
    TASK.data[0] = gBattleAnimArgs[0];
    TASK.data[1] = gBattleAnimArgs[1];
    TASK.data[2] = gBattleAnimArgs[2];
    TASK.data[3] = gBattleAnimArgs[2];
    TASK.data[4] = spriteId;
    TASK.data[10] = 0x100;
    TASK.data[11] = 0x100;
    TASK.func = AnimTask_ScaleMonAndRestoreStep;
}

static void AnimTask_ScaleMonAndRestoreStep(u8 taskId)
{
    u8 spriteId;
    TASK.data[10] += TASK.data[0];
    TASK.data[11] += TASK.data[1];
    spriteId = TASK.data[4];
    obj_id_set_rotscale(spriteId, TASK.data[10], TASK.data[11], 0);
    if (--TASK.data[2] == 0)
    {
        if (TASK.data[3] > 0)
        {
            TASK.data[0] = -TASK.data[0];
            TASK.data[1] = -TASK.data[1];
            TASK.data[2] = TASK.data[3];
            TASK.data[3] = 0;
        }
        else
        {
            sub_8078F40(spriteId);
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
}

void sub_80A8E04(u8 taskId)
{
    u8 spriteId;
    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[2]);
    PrepareBattlerSpriteForRotScale(spriteId, 0);
    TASK.data[1] = 0;
    TASK.data[2] = gBattleAnimArgs[0];
    if (gBattleAnimArgs[3] != 1)
    {
        TASK.data[3] = 0;
    }
    else
    {
        TASK.data[3] = gBattleAnimArgs[0] * gBattleAnimArgs[1];
    }
    TASK.data[4] = gBattleAnimArgs[1];
    TASK.data[5] = spriteId;
    TASK.data[6] = gBattleAnimArgs[3];
    if (IsContest())
    {
        TASK.data[7] = 1;
    }
    else
    {
        if (gBattleAnimArgs[2] == 0)
        {
            TASK.data[7] = !GetBattlerSide(gBattleAnimAttacker);
        }
        else
        {
            TASK.data[7] = !GetBattlerSide(gBattleAnimTarget);
        }
    }
    if (TASK.data[7])
    {
        if (!IsContest())
        {
            TASK.data[3] *= -1;
            TASK.data[4] *= -1;
        }
    }
    TASK.func = sub_80A8FD8;
}

void sub_80A8EFC(u8 taskId)
{
    u8 spriteId;
    spriteId = GetAnimBattlerSpriteId(gBattleAnimArgs[2]);
    PrepareBattlerSpriteForRotScale(spriteId, 0);
    TASK.data[1] = 0;
    TASK.data[2] = gBattleAnimArgs[0];
    if (gBattleAnimArgs[2] == 0)
    {
        if (GetBattlerSide(gBattleAnimAttacker))
        {
            gBattleAnimArgs[1] = -gBattleAnimArgs[1];
        }
    }
    else
    {
        if (GetBattlerSide(gBattleAnimTarget))
        {
            gBattleAnimArgs[1] = -gBattleAnimArgs[1];
        }
    }
    if (gBattleAnimArgs[3] != 1)
    {
        TASK.data[3] = 0;
    }
    else
    {
        TASK.data[3] = gBattleAnimArgs[0] * gBattleAnimArgs[1];
    }
    TASK.data[4] = gBattleAnimArgs[1];
    TASK.data[5] = spriteId;
    TASK.data[6] = gBattleAnimArgs[3];
    TASK.data[7] = 1;
    TASK.data[3] *= -1;
    TASK.data[4] *= -1;
    TASK.func = sub_80A8FD8;
}

static void sub_80A8FD8(u8 taskId)
{
    TASK.data[3] += TASK.data[4];
    obj_id_set_rotscale(TASK.data[5], 0x100, 0x100, TASK.data[3]);
    if (TASK.data[7])
    {
        sub_8078F9C(TASK.data[5]);
    }
    if (++TASK.data[1] >= TASK.data[2])
    {
        switch (TASK.data[6])
        {
        case 1:
            sub_8078F40(TASK.data[5]);
        case 0:
        default:
            DestroyAnimVisualTask(taskId);
            return;
        case 2:
            TASK.data[1] = 0;
            TASK.data[4] *= -1;
            TASK.data[6] = 1;
            break;
        }
    }
}

void sub_80A9058(u8 taskId)
{
    if (!gBattleAnimArgs[0])
    {
        TASK.data[15] = gAnimMovePower / 12;
        if (TASK.data[15] < 1)
        {
            TASK.data[15] = 1;
        }
        if (TASK.data[15] > 16)
        {
            TASK.data[15] = 16;
        }
    }
    else
    {
        TASK.data[15] = gAnimMoveDmg / 12;
        if (TASK.data[15] < 1)
        {
            TASK.data[15] = 1;
        }
        if (TASK.data[15] > 16)
        {
            TASK.data[15] = 16;
        }
    }
    TASK.data[14] = TASK.data[15] / 2;
    TASK.data[13] = TASK.data[14] + (TASK.data[15] & 1);
    TASK.data[12] = 0;
    TASK.data[10] = gBattleAnimArgs[3];
    TASK.data[11] = gBattleAnimArgs[4];
    TASK.data[7] = GetAnimBattlerSpriteId(1);
    TASK.data[8] = gSprites[TASK.data[7]].x2;
    TASK.data[9] = gSprites[TASK.data[7]].y2;
    TASK.data[0] = 0;
    TASK.data[1] = gBattleAnimArgs[1];
    TASK.data[2] = gBattleAnimArgs[2];
    TASK.func = sub_80A913C;
}

static void sub_80A913C(u8 taskId)
{
    struct Task *task = &gTasks[taskId];
    if (++task->data[0] > task->data[1])
    {
        task->data[0] = 0;
        task->data[12] = (task->data[12] + 1) & 1;
        if (task->data[10])
        {
            if (task->data[12])
            {
                gSprites[task->data[7]].x2 = task->data[8] + task->data[13];
            }
            else
            {
                gSprites[task->data[7]].x2 = task->data[8] - task->data[14];
            }
        }
        if (task->data[11])
        {
            if (task->data[12])
            {
                gSprites[task->data[7]].y2 = task->data[15];
            }
            else
            {
                gSprites[task->data[7]].y2 = 0;
            }
        }
        if (!--task->data[2])
        {
            gSprites[task->data[7]].x2 = 0;
            gSprites[task->data[7]].y2 = 0;
            DestroyAnimVisualTask(taskId);
            return;
        }
    }
}
