code:
#include "global.h"
#include "battle.h"
#include "battle_anim.h"
#include "gpu_regs.h"
#include "palette.h"
#include "constants/rgb.h"
#include "scanline_effect.h"
#include "constants/songs.h"
#include "sound.h"
#include "trig.h"
#include "util.h"

static void AnimConfuseRayBallBounce(struct Sprite *);
static void AnimConfuseRayBallBounce_Step1(struct Sprite *);
static void AnimConfuseRayBallBounce_Step2(struct Sprite *);
static void sub_8111764(struct Sprite *);
static void AnimConfuseRayBallSpiral(struct Sprite *);
static void AnimConfuseRayBallSpiral_Step(struct Sprite *);
static void AnimTask_NightShadeClone_Step1(u8 taskId);
static void AnimTask_NightShadeClone_Step2(u8 taskId);
static void AnimShadowBall(struct Sprite *);
static void AnimShadowBall_Step(struct Sprite *);
static void AnimLick(struct Sprite *);
static void AnimLick_Step(struct Sprite *);
static void AnimTask_NightmareClone_Step(u8 taskId);
static void AnimTask_SpiteTargetShadow_Step1(u8 taskId);
static void AnimTask_SpiteTargetShadow_Step2(u8 taskId);
static void AnimTask_SpiteTargetShadow_Step3(u8 taskId);
static void AnimDestinyBondWhiteShadow(struct Sprite *);
static void AnimDestinyBondWhiteShadow_Step(struct Sprite *);
static void AnimTask_DestinyBondWhiteShadow_Step(u8 taskId);
static void AnimTask_CurseStretchingBlackBg_Step1(u8 taskId);
static void AnimTask_CurseStretchingBlackBg_Step2(u8 taskId);
static void AnimCurseNail(struct Sprite *);
static void AnimCurseNail_Step1(struct Sprite *);
static void AnimCurseNail_Step2(struct Sprite *);
static void AnimCurseNail_End(struct Sprite *);
static void AnimGhostStatusSprite(struct Sprite *);
static void AnimGhostStatusSprite_Step(struct Sprite *);
static void AnimTask_GrudgeFlames_Step(u8 taskId);
static void AnimGrudgeFlame(struct Sprite *);
static void sub_8112F60(struct Sprite *);
static void sub_8112FB8(struct Sprite *);

static const union AffineAnimCmd sAffineAnim_ConfuseRayBallBounce[] =
{
    AFFINEANIMCMD_FRAME(0x1E, 0x1E, 10, 5),
    AFFINEANIMCMD_FRAME(0xFFE2, 0xFFE2, 10, 5),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd *const sAffineAnims_ConfuseRayBallBounce[] =
{
    sAffineAnim_ConfuseRayBallBounce,
};

const struct SpriteTemplate gConfuseRayBallBounceSpriteTemplate =
{
    .tileTag = ANIM_TAG_YELLOW_BALL,
    .paletteTag = ANIM_TAG_YELLOW_BALL,
    .oam = &gOamData_AffineDouble_ObjNormal_16x16,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = sAffineAnims_ConfuseRayBallBounce,
    .callback = AnimConfuseRayBallBounce,
};

const struct SpriteTemplate gConfuseRayBallSpiralSpriteTemplate =
{
    .tileTag = ANIM_TAG_YELLOW_BALL,
    .paletteTag = ANIM_TAG_YELLOW_BALL,
    .oam = &gOamData_AffineOff_ObjBlend_16x16,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimConfuseRayBallSpiral,
};

static const union AffineAnimCmd sAffineAnim_ShadowBall[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, 10, 1),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd *const sAffineAnims_ShadowBall[] =
{
    sAffineAnim_ShadowBall,
};

const struct SpriteTemplate gShadowBallSpriteTemplate =
{
    .tileTag = ANIM_TAG_SHADOW_BALL,
    .paletteTag = ANIM_TAG_SHADOW_BALL,
    .oam = &gOamData_AffineNormal_ObjNormal_32x32,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = sAffineAnims_ShadowBall,
    .callback = AnimShadowBall,
};

static const union AnimCmd sAnim_Lick[] =
{
    ANIMCMD_FRAME(0, 2),
    ANIMCMD_FRAME(8, 2),
    ANIMCMD_FRAME(16, 2),
    ANIMCMD_FRAME(24, 2),
    ANIMCMD_FRAME(32, 2),
    ANIMCMD_END,
};

static const union AnimCmd *const sAnims_Lick[] =
{
    sAnim_Lick,
};

const struct SpriteTemplate gLickSpriteTemplate =
{
    .tileTag = ANIM_TAG_LICK,
    .paletteTag = ANIM_TAG_LICK,
    .oam = &gOamData_AffineOff_ObjNormal_16x32,
    .anims = sAnims_Lick,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimLick,
};

static const union AffineAnimCmd gUnknown_08596DA4[] =
{
    AFFINEANIMCMD_FRAME(0x200, 0x200, 0, 0),
    AFFINEANIMCMD_END,
};

// Unused
static const union AffineAnimCmd *const gUnknown_08596DB4[] =
{
    gUnknown_08596DA4,
};

const struct SpriteTemplate gDestinyBondWhiteShadowSpriteTemplate =
{
    .tileTag = ANIM_TAG_WHITE_SHADOW,
    .paletteTag = ANIM_TAG_WHITE_SHADOW,
    .oam = &gOamData_AffineOff_ObjBlend_64x32,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimDestinyBondWhiteShadow,
};

const struct SpriteTemplate gCurseNailSpriteTemplate =
{
    .tileTag = ANIM_TAG_NAIL,
    .paletteTag = ANIM_TAG_NAIL,
    .oam = &gOamData_AffineOff_ObjBlend_32x16,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimCurseNail,
};

const struct SpriteTemplate gCurseGhostSpriteTemplate =
{
    .tileTag = ANIM_TAG_GHOSTLY_SPIRIT,
    .paletteTag = ANIM_TAG_GHOSTLY_SPIRIT,
    .oam = &gOamData_AffineOff_ObjBlend_32x32,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimGhostStatusSprite,
};

const struct SpriteTemplate gNightmareDevilSpriteTemplate =
{
    .tileTag = ANIM_TAG_DEVIL,
    .paletteTag = ANIM_TAG_DEVIL,
    .oam = &gOamData_AffineOff_ObjBlend_32x32,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimGhostStatusSprite,
};

static const union AnimCmd sAnim_GrudgeFlame[] =
{
    ANIMCMD_FRAME(0, 4),
    ANIMCMD_FRAME(8, 4),
    ANIMCMD_FRAME(16, 4),
    ANIMCMD_FRAME(24, 4),
    ANIMCMD_JUMP(0),
};

static const union AnimCmd *const sAnims_GrudgeFlame[] =
{
    sAnim_GrudgeFlame,
};

const struct SpriteTemplate gGrudgeFlameSpriteTemplate =
{
    .tileTag = ANIM_TAG_PURPLE_FLAME,
    .paletteTag = ANIM_TAG_PURPLE_FLAME,
    .oam = &gOamData_AffineOff_ObjBlend_16x32,
    .anims = sAnims_GrudgeFlame,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimGrudgeFlame,
};

// Unused
const struct SpriteTemplate gUnknown_08596E48 =
{
    .tileTag = 0,
    .paletteTag = 0,
    .oam = &gDummyOamData,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = sub_8112F60,
};

static void AnimConfuseRayBallBounce(struct Sprite *sprite)
{
    InitSpritePosToAnimAttacker(sprite, 1);
    sprite->data[0] = gBattleAnimArgs[2];
    sprite->data[1] = sprite->pos1.x;
    sprite->data[2] = GetBattlerSpriteCoord(gBattleAnimTarget, 2);
    sprite->data[3] = sprite->pos1.y;
    sprite->data[4] = GetBattlerSpriteCoord(gBattleAnimTarget, 3);
    sub_80A6FD4(sprite);
    sprite->callback = AnimConfuseRayBallBounce_Step1;
    sprite->data[6] = 16;
    SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
    SetGpuReg(REG_OFFSET_BLDALPHA, sprite->data[6]);
}

static void AnimConfuseRayBallBounce_Step1(struct Sprite *sprite)
{
    s16 r0;
    s16 r2;
    sub_8111764(sprite);
    if (AnimTranslateLinear(sprite))
    {
        sprite->callback = AnimConfuseRayBallBounce_Step2;
        return;
    }

    sprite->pos2.x += Sin(sprite->data[5], 10);
    sprite->pos2.y += Cos(sprite->data[5], 15);
    r2 = sprite->data[5];
    sprite->data[5] = (sprite->data[5] + 5) & 0xFF;
    r0 = sprite->data[5];
    if (r2 != 0 && r2 <= 196)
        return;
    if (r0 <= 0)
        return;
    PlaySE12WithPanning(SE_M_CONFUSE_RAY, gAnimCustomPanning);
}

static void AnimConfuseRayBallBounce_Step2(struct Sprite *sprite)
{
    s16 r2;
    s16 r0;
    sprite->data[0] = 1;
    AnimTranslateLinear(sprite);
    sprite->pos2.x += Sin(sprite->data[5], 10);
    sprite->pos2.y += Cos(sprite->data[5], 15);

    r2 = sprite->data[5];
    sprite->data[5] = (sprite->data[5] + 5) & 0xFF;
    r0 = sprite->data[5];

    if (r2 == 0 || r2 > 196)
    {
        if (r0 > 0)
            PlaySE(SE_M_CONFUSE_RAY);
    }

    if (sprite->data[6] == 0)
    {
        sprite->invisible = TRUE;
        sprite->callback = DestroyAnimSpriteAndDisableBlend;
    }
    else
        sub_8111764(sprite);
}

static void sub_8111764(struct Sprite *sprite)
{

    s16 r0;
    if (sprite->data[6] > 0xFF)
    {
        if (++sprite->data[6] == 0x10d)
            sprite->data[6] = 0;
        return;
    }

    r0 = sprite->data[7];
    sprite->data[7]++;

    if ((r0 & 0xFF) == 0)
    {
        sprite->data[7] &= 0xff00;
        if ((sprite->data[7] & 0x100) != 0)
            sprite->data[6]++;
        else
            sprite->data[6]--;

        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(sprite->data[6], 16 - sprite->data[6]));
        if (sprite->data[6] == 0 || sprite->data[6] == 16)
            sprite->data[7] ^= 0x100;
        if (sprite->data[6] == 0)
            sprite->data[6] = 0x100;
    }
}

static void AnimConfuseRayBallSpiral(struct Sprite *sprite)
{
    InitSpritePosToAnimTarget(sprite, TRUE);
    sprite->callback = AnimConfuseRayBallSpiral_Step;
    sprite->callback(sprite);
}

static void AnimConfuseRayBallSpiral_Step(struct Sprite *sprite)
{
    u16 temp1;
    sprite->pos2.x = Sin(sprite->data[0], 32);
    sprite->pos2.y = Cos(sprite->data[0], 8);
    temp1 = sprite->data[0] - 65;
    if (temp1 <= 130)
        sprite->oam.priority = 2;
    else
        sprite->oam.priority = 1;
    sprite->data[0] = (sprite->data[0] + 19) & 0xFF;
    sprite->data[2] += 80;
    sprite->pos2.y += sprite->data[2] >> 8;
    sprite->data[7] += 1;
    if (sprite->data[7] == 61)
        DestroyAnimSprite(sprite);
}

// Creates a large transparent clone of the attacker centered on their position which shrinks to original size
void AnimTask_NightShadeClone(u8 taskId)
{
    u8 spriteId;
    SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0x10));
    spriteId = GetAnimBattlerSpriteId(ANIM_ATTACKER);
    PrepareBattlerSpriteForRotScale(spriteId, ST_OAM_OBJ_BLEND);
    SetSpriteRotScale(spriteId, 128, 128, 0);
    gSprites[spriteId].invisible = FALSE;
    gTasks[taskId].data[0] = 128;
    gTasks[taskId].data[1] = *gBattleAnimArgs;
    gTasks[taskId].data[2] = 0;
    gTasks[taskId].data[3] = 16;
    gTasks[taskId].func = AnimTask_NightShadeClone_Step1;
}

static void AnimTask_NightShadeClone_Step1(u8 taskId)
{
    gTasks[taskId].data[10] += 1;
    if (gTasks[taskId].data[10] == 3)
    {
        gTasks[taskId].data[10] = 0;
        gTasks[taskId].data[2] += 1;
        gTasks[taskId].data[3] -= 1;
        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(gTasks[taskId].data[2], gTasks[taskId].data[3]));
        if (gTasks[taskId].data[2] != 9)
            return;

        gTasks[taskId].func = AnimTask_NightShadeClone_Step2;
    }
}

static void AnimTask_NightShadeClone_Step2(u8 taskId)
{
    u8 spriteId;
    if (gTasks[taskId].data[1] > 0)
    {
        gTasks[taskId].data[1] -= 1;
        return;
    }

    spriteId = GetAnimBattlerSpriteId(ANIM_ATTACKER);
    gTasks[taskId].data[0] += 8;
    if (gTasks[taskId].data[0] <= 0xFF)
    {
        SetSpriteRotScale(spriteId, gTasks[taskId].data[0], gTasks[taskId].data[0], 0);
    }
    else
    {
        ResetSpriteRotScale(spriteId);
        DestroyAnimVisualTask(taskId);
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDALPHA, 0);
    }
}

// Spins a sprite towards the target, pausing in the middle.
// Used in Shadow Ball.
// arg 0: duration step 1 (attacker -> center)
// arg 1: duration step 2 (spin center)
// arg 2: duration step 3 (center -> target)
static void AnimShadowBall(struct Sprite *sprite)
{
    s16 oldPosX = sprite->pos1.x;
    s16 oldPosY = sprite->pos1.y;

    sprite->pos1.x = GetBattlerSpriteCoord(gBattleAnimAttacker, 2);
    sprite->pos1.y = GetBattlerSpriteCoord(gBattleAnimAttacker, 3);
    sprite->data[0] = 0;
    sprite->data[1] = gBattleAnimArgs[0];
    sprite->data[2] = gBattleAnimArgs[1];
    sprite->data[3] = gBattleAnimArgs[2];
    sprite->data[4] = sprite->pos1.x << 4;
    sprite->data[5] = sprite->pos1.y << 4;
    sprite->data[6] = ((oldPosX - sprite->pos1.x) << 4) / (gBattleAnimArgs[0] << 1);
    sprite->data[7] = ((oldPosY - sprite->pos1.y) << 4) / (gBattleAnimArgs[0] << 1);
    sprite->callback = AnimShadowBall_Step;
}

static void AnimShadowBall_Step(struct Sprite *sprite)
{
    switch (sprite->data[0])
    {
    case 0:
        sprite->data[4] += sprite->data[6];
        sprite->data[5] += sprite->data[7];
        sprite->pos1.x = sprite->data[4] >> 4;
        sprite->pos1.y = sprite->data[5] >> 4;
        sprite->data[1] -= 1;
        if (sprite->data[1] > 0)
            break;
        sprite->data[0] += 1;
        break;
    case 1:
        sprite->data[2] -= 1;
        if (sprite->data[2] > 0)
            break;
        sprite->data[1] = GetBattlerSpriteCoord(gBattleAnimTarget, 2);
        sprite->data[2] = GetBattlerSpriteCoord(gBattleAnimTarget, 3);
        sprite->data[4] = sprite->pos1.x << 4;
        sprite->data[5] = sprite->pos1.y << 4;
        sprite->data[6] = ((sprite->data[1] - sprite->pos1.x) << 4) / sprite->data[3];
        sprite->data[7] = ((sprite->data[2] - sprite->pos1.y) << 4) / sprite->data[3];
        sprite->data[0] += 1;
        break;
    case 2:
        sprite->data[4] += sprite->data[6];
        sprite->data[5] += sprite->data[7];
        sprite->pos1.x = sprite->data[4] >> 4;
        sprite->pos1.y = sprite->data[5] >> 4;
        sprite->data[3] -= 1;
        if (sprite->data[3] > 0)
            break;
        sprite->pos1.x = GetBattlerSpriteCoord(gBattleAnimTarget, 2);
        sprite->pos1.y = GetBattlerSpriteCoord(gBattleAnimTarget, 3);
        sprite->data[0] += 1;
        break;
    case 3:
        DestroySpriteAndMatrix(sprite);
        break;
    }
}

static void AnimLick(struct Sprite *sprite)
{
    InitSpritePosToAnimTarget(sprite, TRUE);
    sprite->callback = AnimLick_Step;
}

static void AnimLick_Step(struct Sprite *sprite)
{
    bool8 r5 = FALSE;
    bool8 r6 = FALSE;

    if (sprite->animEnded)
    {
        if (!sprite->invisible)
            sprite->invisible = TRUE;

        switch (sprite->data[0])
        {
        default:
            r6 = TRUE;
            break;
        case 0:
            if (sprite->data[1] == 2)
                r5 = TRUE;
            break;
        case 1:
            if (sprite->data[1] == 4)
                r5 = TRUE;
            break;
        }

        if (r5)
        {
            sprite->invisible ^= 1;
            sprite->data[2]++;
            sprite->data[1] = 0;
            if (sprite->data[2] == 5)
            {
                sprite->data[2] = 0;
                sprite->data[0]++;
            }
        }
        else if (r6)
        {
            DestroyAnimSprite(sprite);
        }
        else
        {
            sprite->data[1]++;
        }
    }
}

// Creates a transparent clone of the target which drifts up and away to the side
void AnimTask_NightmareClone(u8 taskId)
{
    struct Task *task;

    task = &gTasks[taskId];
    task->data[0] = CloneBattlerSpriteWithBlend(ANIM_TARGET);
    if (task->data[0] < 0)
    {
        DestroyAnimVisualTask(taskId);
        return;
    }
    task->data[1] = 0;
    task->data[2] = 15;
    task->data[3] = 2;
    task->data[4] = 0;
    SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[2], task->data[3]));
    gSprites[task->data[0]].data[0] = 80;
    if (GetBattlerSide(gBattleAnimTarget) == B_SIDE_PLAYER)
    {
        gSprites[task->data[0]].data[1] = -144;
        gSprites[task->data[0]].data[2] = 112;
    }
    else
    {
        gSprites[task->data[0]].data[1] = 144;
        gSprites[task->data[0]].data[2] = -112;
    }
    gSprites[task->data[0]].data[3] = 0;
    gSprites[task->data[0]].data[4] = 0;
    StoreSpriteCallbackInData6(&gSprites[task->data[0]], SpriteCallbackDummy);
    gSprites[task->data[0]].callback = TranslateSpriteLinearFixedPoint;
    task->func = AnimTask_NightmareClone_Step;
}

static void AnimTask_NightmareClone_Step(u8 taskId)
{
    struct Task *task;

    task = &gTasks[taskId];
    switch (task->data[4])
    {
    case 0:
        task->data[1] += 1;
        task->data[5] = task->data[1] & 3;
        if (task->data[5] == 1)
            if (task->data[2] > 0)
                task->data[2] -= 1;
        if (task->data[5] == 3)
            if (task->data[3] <= 15)
                task->data[3] += 1;
        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[2], task->data[3]));
        if (task->data[3] != 16 || task->data[2] != 0)
            break;
        if (task->data[1] <= 80)
            break;
        obj_delete_but_dont_free_vram(&gSprites[task->data[0]]);
        task->data[4] = 1;
        break;
    case 1:
        if (++task->data[6] <= 1)
            break;
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDALPHA, 0);
        task->data[4] += 1;
        break;
    case 2:
        DestroyAnimVisualTask(taskId);
        break;
    }
}

// Creates a blended copy of the target that wavers in front of them
void AnimTask_SpiteTargetShadow(u8 taskId)
{
    struct Task *task;

    task = &gTasks[taskId];
    task->data[15] = 0;
    task->func = AnimTask_SpiteTargetShadow_Step1;
    task->func(taskId);
}

static void AnimTask_SpiteTargetShadow_Step1(u8 taskId)
{
    s16 startLine;
    struct Task *task = &gTasks[taskId];
    u8 position = GetBattlerSpriteBGPriorityRank(gBattleAnimTarget);

    switch (task->data[15])
    {
    case 0:
        task->data[14] = AllocSpritePalette(ANIM_TAG_BENT_SPOON);
        if (task->data[14] == 0xFF || task->data[14] == 0xF)
        {
            DestroyAnimVisualTask(taskId);
        }
        else
        {
            task->data[0] = CloneBattlerSpriteWithBlend(ANIM_TARGET);
            if (task->data[0] < 0)
            {
                FreeSpritePaletteByTag(ANIM_TAG_BENT_SPOON);
                DestroyAnimVisualTask(taskId);
            }
            else
            {
                s16 mask2;
                gSprites[task->data[0]].oam.paletteNum = task->data[14];
                gSprites[task->data[0]].oam.objMode = ST_OAM_OBJ_NORMAL;
                gSprites[task->data[0]].oam.priority = 3;
                gSprites[task->data[0]].invisible = (gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].invisible);
                task->data[1] = 0;
                task->data[2] = 0;
                task->data[3] = 16;
                task->data[13] = GetAnimBattlerSpriteId(ANIM_TARGET);
                task->data[4] = (gSprites[task->data[13]].oam.paletteNum + 16) * 16;
                if (position == 1) {
                    u16 mask = DISPCNT_BG1_ON;
                    mask2 = mask;
                }
                else {
                    u16 mask = DISPCNT_BG2_ON;
                    mask2 = mask;
                }
                ClearGpuRegBits(REG_OFFSET_DISPCNT, mask2);
                task->data[15]++;
            }
        }
        break;
    case 1:
        task->data[14] = (task->data[14] + 16) * 16;
        CpuSet(&gPlttBufferUnfaded[task->data[4]], &gPlttBufferFaded[task->data[14]], 0x4000008);
        BlendPalette(task->data[4], 16, 10, RGB(13, 0, 15));
        task->data[15]++;
        break;
    case 2:
        startLine = gSprites[task->data[13]].pos1.y + gSprites[task->data[13]].pos2.y - 32;
        if (startLine < 0)
            startLine = 0;

        if (position == 1)
            task->data[10] = ScanlineEffect_InitWave(startLine, startLine + 64, 2, 6, 0, 4, 1);
        else
            task->data[10] = ScanlineEffect_InitWave(startLine, startLine + 64, 2, 6, 0, 8, 1);

        task->data[15]++;
        break;
    case 3:
        if (position == 1)
            SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL | BLDCNT_TGT1_BG1));
        else
            SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL | BLDCNT_TGT1_BG2));

        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0x10));
        task->data[15]++;
        break;
    case 4:
        if (position == 1)
            SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG1_ON);
        else
            SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);

        task->func = AnimTask_SpiteTargetShadow_Step2;
        task->data[15]++;
        break;
    default:
        task->data[15]++;
        break;
    }
}

static void AnimTask_SpiteTargetShadow_Step2(u8 taskId)
{
    struct Task *task = &gTasks[taskId];
    task->data[1]++;
    task->data[5] = task->data[1] & 1;
    if (task->data[5] == 0)
        task->data[2] = gSineTable[task->data[1]] / 18;

    if (task->data[5] == 1)
        task->data[3] = 16 - (gSineTable[task->data[1]] / 18);

    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[2], task->data[3]));
    if (task->data[1] == 128)
    {
        task->data[15] = 0;
        task->func = AnimTask_SpiteTargetShadow_Step3;
        task->func(taskId);
    }
}

static void AnimTask_SpiteTargetShadow_Step3(u8 taskId)
{
    struct Task *task = &gTasks[taskId];
    u8 rank = GetBattlerSpriteBGPriorityRank(gBattleAnimTarget);

    switch (task->data[15])
    {
    case 0:
        gScanlineEffect.state = 3;
        task->data[14] = GetAnimBattlerSpriteId(ANIM_TARGET);
        if (rank == 1)
            ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG1_ON);
        else
            ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);
        break;
    case 1:
        BlendPalette(task->data[4], 16, 0, RGB(13, 0, 15));
        break;
    case 2:
        gSprites[task->data[14]].invisible = TRUE;
        obj_delete_but_dont_free_vram(&gSprites[task->data[0]]);
        FreeSpritePaletteByTag(ANIM_TAG_BENT_SPOON);
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDALPHA, 0);
        if (rank == 1)
            SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG1_ON);
        else
            SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);

        DestroyAnimVisualTask(taskId);
        break;
    }

    task->data[15]++;
}

static void AnimDestinyBondWhiteShadow(struct Sprite *sprite)
{
    s16 battler1X, battler1Y;
    s16 battler2X, battler2Y;
    s16 yDiff;

    if (gBattleAnimArgs[0] == 0)
    {
        battler1X = GetBattlerSpriteCoord(gBattleAnimAttacker, 0);
        battler1Y = GetBattlerSpriteCoord(gBattleAnimAttacker, 1) + 28;
        battler2X = GetBattlerSpriteCoord(gBattleAnimTarget, 0);
        battler2Y = GetBattlerSpriteCoord(gBattleAnimTarget, 1) + 28;
    }
    else
    {
        battler1X = GetBattlerSpriteCoord(gBattleAnimTarget, 0);
        battler1Y = GetBattlerSpriteCoord(gBattleAnimTarget, 1) + 28;
        battler2X = GetBattlerSpriteCoord(gBattleAnimAttacker, 0);
        battler2Y = GetBattlerSpriteCoord(gBattleAnimAttacker, 1) + 28;
    }

    yDiff = battler2Y - battler1Y;
    sprite->data[0] = battler1X * 16;
    sprite->data[1] = battler1Y * 16;
    sprite->data[2] = ((battler2X - battler1X) * 16) / gBattleAnimArgs[1];
    sprite->data[3] = (yDiff * 16) / gBattleAnimArgs[1];
    sprite->data[4] = gBattleAnimArgs[1];
    sprite->data[5] = battler2X;
    sprite->data[6] = battler2Y;
    sprite->data[7] = sprite->data[4] / 2;
    sprite->oam.priority = 2;
    sprite->pos1.x = battler1X;
    sprite->pos1.y = battler1Y;
    sprite->callback = AnimDestinyBondWhiteShadow_Step;
    sprite->invisible = TRUE;
}

static void AnimDestinyBondWhiteShadow_Step(struct Sprite *sprite)
{
    if (sprite->data[4])
    {
        sprite->data[0] += sprite->data[2];
        sprite->data[1] += sprite->data[3];
        sprite->pos1.x = sprite->data[0] >> 4;
        sprite->pos1.y = sprite->data[1] >> 4;
        if (--sprite->data[4] == 0)
            sprite->data[0] = 0;
    }
}

void AnimTask_DestinyBondWhiteShadow(u8 taskId)
{
    struct Task *task;
    s16 battler;
    u8 spriteId;
    s16 baseX, baseY;
    s16 x, y;

    task = &gTasks[taskId];
    SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0x10));
    task->data[5] = 0;
    task->data[6] = 0;
    task->data[7] = 0;
    task->data[8] = 0;
    task->data[9] = 16;
    task->data[10] = gBattleAnimArgs[0];

    baseX = GetBattlerSpriteCoord(gBattleAnimAttacker, 2);
    baseY = GetBattlerSpriteCoordAttr(gBattleAnimAttacker, BATTLER_COORD_ATTR_BOTTOM);
    if (!IsContest())
    {
        for (battler = 0; battler < MAX_BATTLERS_COUNT; battler++)
        {
            if (battler != gBattleAnimAttacker
             && battler != (gBattleAnimAttacker ^ 2)
             && IsBattlerSpriteVisible(battler))
            {
                spriteId = CreateSprite(&gDestinyBondWhiteShadowSpriteTemplate, baseX, baseY, 55);
                if (spriteId != MAX_SPRITES)
                {
                    x = GetBattlerSpriteCoord(battler, 2);
                    y = GetBattlerSpriteCoordAttr(battler, BATTLER_COORD_ATTR_BOTTOM);
                    gSprites[spriteId].data[0] = baseX << 4;
                    gSprites[spriteId].data[1] = baseY << 4;
                    gSprites[spriteId].data[2] = ((x - baseX) << 4) / gBattleAnimArgs[1];
                    gSprites[spriteId].data[3] = ((y - baseY) << 4) / gBattleAnimArgs[1];
                    gSprites[spriteId].data[4] = gBattleAnimArgs[1];
                    gSprites[spriteId].data[5] = x;
                    gSprites[spriteId].data[6] = y;
                    gSprites[spriteId].callback = AnimDestinyBondWhiteShadow_Step;

                    task->data[task->data[12] + 13] = spriteId;
                    task->data[12]++;
                }
            }
        }
    }
    else
    {
        spriteId = CreateSprite(&gDestinyBondWhiteShadowSpriteTemplate, baseX, baseY, 55);
        if (spriteId != MAX_SPRITES)
        {
            x = 48;
            y = 40;
            gSprites[spriteId].data[0] = baseX << 4;
            gSprites[spriteId].data[1] = baseY << 4;
            gSprites[spriteId].data[2] = ((x - baseX) << 4) / gBattleAnimArgs[1];
            gSprites[spriteId].data[3] = ((y - baseY) << 4) / gBattleAnimArgs[1];
            gSprites[spriteId].data[4] = gBattleAnimArgs[1];
            gSprites[spriteId].data[5] = x;
            gSprites[spriteId].data[6] = y;
            gSprites[spriteId].callback = AnimDestinyBondWhiteShadow_Step;

            task->data[13] = spriteId;
            task->data[12] = 1;
        }
    }

    task->func = AnimTask_DestinyBondWhiteShadow_Step;
}

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

    switch (task->data[0])
    {
    case 0:
        if (task->data[6] == 0)
        {
            if (++task->data[5] > 1)
            {
                task->data[5] = 0;
                task->data[7]++;
                if (task->data[7] & 1)
                {
                    if (task->data[8] < 16)
                        task->data[8]++;
                }
                else
                {
                    if (task->data[9])
                        task->data[9]--;
                }

                SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[8], task->data[9]));
                if (task->data[7] >= 24)
                {
                    task->data[7] = 0;
                    task->data[6] = 1;
                }
            }
        }

        if (task->data[10])
            task->data[10]--;
        else if (task->data[6])
            task->data[0]++;
        break;
    case 1:
        if (++task->data[5] > 1)
        {
            task->data[5] = 0;
            task->data[7]++;
            if (task->data[7] & 1)
            {
                if (task->data[8])
                    task->data[8]--;
            }
            else
            {
                if (task->data[9] < 16)
                    task->data[9]++;
            }

            SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[8], task->data[9]));
            if (task->data[8] == 0 && task->data[9] == 16)
            {
                for (i = 0; i < task->data[12]; i++)
                    DestroySprite(&gSprites[task->data[i + 13]]);

                task->data[0]++;
            }
        }
        break;
    case 2:
        if (++task->data[5] > 0)
            task->data[0]++;
        break;
    case 3:
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDALPHA, 0);
        DestroyAnimVisualTask(taskId);
        break;
    }
}

void AnimTask_CurseStretchingBlackBg(u8 taskId)
{
    s16 startX, startY;
    s16 leftDistance, topDistance, bottomDistance, rightDistance;

    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_WINOBJ_BG_ALL | WINOUT_WINOBJ_OBJ | WINOUT_WINOBJ_CLR)));
    SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_TGT1_BG3 | BLDCNT_EFFECT_DARKEN));
    SetGpuReg(REG_OFFSET_BLDY, 0x10);

    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER || IsContest())
        startX = 40;
    else
        startX = 200;

    gBattle_WIN0H = (startX << 8) | startX;
    startY = 40;
    gBattle_WIN0V = (startY << 8) | startY;

    leftDistance = startX;
    rightDistance = 240 - startX;
    topDistance = startY;
    bottomDistance = 72;
    gTasks[taskId].data[1] = leftDistance;
    gTasks[taskId].data[2] = rightDistance;
    gTasks[taskId].data[3] = topDistance;
    gTasks[taskId].data[4] = bottomDistance;
    gTasks[taskId].data[5] = startX;
    gTasks[taskId].data[6] = startY;
    gTasks[taskId].func = AnimTask_CurseStretchingBlackBg_Step1;
}

static void AnimTask_CurseStretchingBlackBg_Step1(u8 taskId)
{
    s16 step;
    s16 leftDistance, rightDistance, topDistance, bottomDistance;
    s16 startX, startY;
    u16 left, right, top, bottom;
    u16 selectedPalettes;

    step = gTasks[taskId].data[0];
    gTasks[taskId].data[0]++;
    leftDistance = gTasks[taskId].data[1];
    rightDistance = gTasks[taskId].data[2];
    topDistance = gTasks[taskId].data[3];
    bottomDistance = gTasks[taskId].data[4];
    startX = gTasks[taskId].data[5];
    startY = gTasks[taskId].data[6];

    if (step < 16)
    {
        left   = startX - (leftDistance   * 0.0625) * step;
        right  = startX + (rightDistance  * 0.0625) * step;
        top    = startY - (topDistance    * 0.0625) * step;
        bottom = startY + (bottomDistance * 0.0625) * step;
    }
    else
    {
        left = 0;
        right = 240;
        top = 0;
        bottom = 112;
        selectedPalettes = sub_80A75AC(1, 0, 0, 0, 0, 0, 0);
        BeginNormalPaletteFade(selectedPalettes, 0, 16, 16, RGB(0, 0, 0));
        gTasks[taskId].func = AnimTask_CurseStretchingBlackBg_Step2;
    }

    gBattle_WIN0H = (left << 8) | right;
    gBattle_WIN0V = (top  << 8) | bottom;
}

static void AnimTask_CurseStretchingBlackBg_Step2(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        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)));
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDY, 0);
        DestroyAnimVisualTask(taskId);
    }
}

static void AnimCurseNail(struct Sprite *sprite)
{
    s16 xDelta;
    s16 xDelta2;

    InitSpritePosToAnimAttacker(sprite, 1);
    if (GetBattlerSide(gBattleAnimAttacker) == B_SIDE_PLAYER)
    {
        xDelta = 24;
        xDelta2 = -2;
        sprite->oam.matrixNum = ST_OAM_HFLIP;
    }
    else
    {
        xDelta = -24;
        xDelta2 = 2;
    }

    sprite->pos1.x += xDelta;
    sprite->data[1] = xDelta2;
    sprite->data[0] = 60;
    sprite->callback = AnimCurseNail_Step1;
}

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

    if (sprite->data[0] > 0)
    {
        sprite->data[0]--;
    }
    else
    {
        sprite->pos2.x += sprite->data[1];
        var0 = sprite->pos2.x + 7;
        if (var0 > 14)
        {
            sprite->pos1.x += sprite->pos2.x;
            sprite->pos2.x = 0;
            sprite->oam.tileNum += 8;
            if (++sprite->data[2] == 3)
            {
                sprite->data[0] = 30;
                sprite->callback = WaitAnimForDuration;
                StoreSpriteCallbackInData6(sprite, AnimCurseNail_Step2);
            }
            else
            {
                sprite->data[0] = 40;
            }
        }
    }
}

static void AnimCurseNail_Step2(struct Sprite *sprite)
{
    if (sprite->data[0] == 0)
    {
        SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(16, 0));
        sprite->data[0]++;
        sprite->data[1] = 0;
        sprite->data[2] = 0;
    }
    else if (sprite->data[1] < 2)
    {
        sprite->data[1]++;
    }
    else
    {
        sprite->data[1] = 0;
        sprite->data[2]++;
        SetGpuReg(REG_OFFSET_BLDALPHA, (16 - sprite->data[2]) | (sprite->data[2] << 8));
        if (sprite->data[2] == 16)
        {
            sprite->invisible = TRUE;
            sprite->callback = AnimCurseNail_End;
        }
    }
}

static void AnimCurseNail_End(struct Sprite *sprite)
{
    SetGpuReg(REG_OFFSET_BLDCNT, 0);
    SetGpuReg(REG_OFFSET_BLDALPHA, 0);
    gBattle_WIN0H = 0;
    gBattle_WIN0V = 0;
    DestroyAnimSprite(sprite);
}

static void AnimGhostStatusSprite(struct Sprite *sprite)
{
    u16 coeffB;
    u16 coeffA;

    sprite->pos2.x = Sin(sprite->data[0], 12);
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
        sprite->pos2.x = -sprite->pos2.x;

    sprite->data[0] = (sprite->data[0] + 6) & 0xFF;
    sprite->data[1] += 0x100;
    sprite->pos2.y = -(sprite->data[1] >> 8);

    sprite->data[7]++;
    if (sprite->data[7] == 1)
    {
        sprite->data[6] = 0x050B;
        SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
        SetGpuReg(REG_OFFSET_BLDALPHA, sprite->data[6]);
    }
    else if (sprite->data[7] > 30)
    {
        sprite->data[2]++;
        coeffB = sprite->data[6] >> 8;
        coeffA = sprite->data[6] & 0xFF;

        if (++coeffB > 16)
            coeffB = 16;
        --coeffA;
        if ((s16)coeffA < 0)
            coeffA = 0;

        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(coeffA, coeffB));
        sprite->data[6] = BLDALPHA_BLEND(coeffA, coeffB);
        if (coeffB == 16 && coeffA == 0)
        {
            sprite->invisible = TRUE;
            sprite->callback = AnimGhostStatusSprite_Step;
        }
    }
}

static void AnimGhostStatusSprite_Step(struct Sprite *sprite)
{
    SetGpuReg(REG_OFFSET_BLDCNT, 0);
    SetGpuReg(REG_OFFSET_BLDALPHA, 0);
    DestroyAnimSprite(sprite);
}

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

    task->data[0] = 0;
    task->data[1] = 16;
    task->data[9] = GetBattlerSpriteCoord(gBattleAnimAttacker, 2);
    task->data[10] = GetBattlerYCoordWithElevation(gBattleAnimAttacker);
    task->data[11] = (GetBattlerSpriteCoordAttr(gBattleAnimAttacker, BATTLER_COORD_ATTR_WIDTH) / 2) + 8;
    task->data[7] = 0;
    task->data[5] = GetBattlerSpriteBGPriority(gBattleAnimAttacker);
    task->data[6] = GetBattlerSpriteSubpriority(gBattleAnimAttacker) - 2;
    task->data[3] = 0;
    task->data[4] = 16;
    SetGpuReg(REG_OFFSET_BLDCNT, (BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL));
    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 0x10));
    task->data[8] = 0;
    task->func = AnimTask_GrudgeFlames_Step;
}

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

    switch (task->data[0])
    {
    case 0:
        for (i = 0; i < 6; i++)
        {
            spriteId = CreateSprite(&gGrudgeFlameSpriteTemplate, task->data[9], task->data[10], task->data[6]);
            if (spriteId != MAX_SPRITES)
            {
                gSprites[spriteId].data[0] = taskId;
                gSprites[spriteId].data[1] = GetBattlerSide(gBattleAnimAttacker) == B_SIDE_PLAYER;

                gSprites[spriteId].data[2] = (i * 42) & 0xFF;
                gSprites[spriteId].data[3] = task->data[11];
                gSprites[spriteId].data[5] = i * 6;
                task->data[7]++;
            }
        }

        task->data[0]++;
        break;
    case 1:
        if (++task->data[1] & 1)
        {
            if (task->data[3] < 14)
                task->data[3]++;
        }
        else
        {
            if (task->data[4] > 4)
                task->data[4]--;
        }

        if (task->data[3] == 14 && task->data[4] == 4)
        {
            task->data[1] = 0;
            task->data[0]++;
        }

        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[3], task->data[4]));
        break;
    case 2:
        if (++task->data[1] > 30)
        {
            task->data[1] = 0;
            task->data[0]++;
        }
        break;
    case 3:
        if (++task->data[1] & 1)
        {
            if (task->data[3] > 0)
                task->data[3]--;
        }
        else
        {
            if (task->data[4] < 16)
                task->data[4]++;
        }

        if (task->data[3] == 0 && task->data[4] == 16)
        {
            task->data[8] = 1;
            task->data[0]++;
        }

        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(task->data[3], task->data[4]));
        break;
    case 4:
        if (task->data[7] == 0)
            task->data[0]++;
        break;
    case 5:
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDALPHA, 0);
        DestroyAnimVisualTask(taskId);
        break;
    }
}

static void AnimGrudgeFlame(struct Sprite *sprite)
{
    u16 index;

    if (sprite->data[1] == 0)
        sprite->data[2] += 2;
    else
        sprite->data[2] -= 2;

    sprite->data[2] &= 0xFF;
    sprite->pos2.x = Sin(sprite->data[2], sprite->data[3]);

    index = sprite->data[2] - 65;
    if (index < 127)
        sprite->oam.priority = gTasks[sprite->data[0]].data[5] + 1;
    else
        sprite->oam.priority = gTasks[sprite->data[0]].data[5];

    sprite->data[5]++;
    sprite->data[6] = (sprite->data[5] * 8) & 0xFF;
    sprite->pos2.y = Sin(sprite->data[6], 7);
    if (gTasks[sprite->data[0]].data[8])
    {
        gTasks[sprite->data[0]].data[7]--;
        DestroySprite(sprite);
    }
}

static void sub_8112F60(struct Sprite *sprite)
{
    sprite->invisible = TRUE;
    sprite->data[5] = gBattlerSpriteIds[gBattleAnimAttacker];
    sprite->data[0] = 128;
    sprite->data[1] = 10;
    sprite->data[2] = gBattleAnimArgs[0];
    sprite->data[3] = gBattleAnimArgs[1];
    sprite->callback = sub_8112FB8;

    gSprites[sprite->data[5]].pos1.y += 8;
}

static void sub_8112FB8(struct Sprite *sprite)
{
    if (sprite->data[3])
    {
        sprite->data[3]--;
        gSprites[sprite->data[5]].pos2.x = Sin(sprite->data[0], sprite->data[1]);
        gSprites[sprite->data[5]].pos2.y = Cos(sprite->data[0], sprite->data[1]);
        sprite->data[0] += sprite->data[2];
        if (sprite->data[0] > 255)
            sprite->data[0] -= 256;
    }
    else
    {
        gSprites[sprite->data[5]].pos2.x = 0;
        gSprites[sprite->data[5]].pos2.y = 0;
        gSprites[sprite->data[5]].pos1.y -= 8;
        sprite->callback = DestroySpriteAndMatrix;
    }
}


code:
#include "global.h"
#include "battle_anim.h"
#include "random.h"
#include "scanline_effect.h"
#include "task.h"
#include "trig.h"
#include "constants/rgb.h"

static void AnimBonemerangProjectile(struct Sprite *);
static void AnimBoneHitProjectile(struct Sprite *);
static void AnimDirtScatter(struct Sprite *);
static void AnimMudSportDirt(struct Sprite *);
static void AnimDirtPlumeParticle(struct Sprite *);
static void AnimDirtPlumeParticle_Step(struct Sprite *);
static void AnimDigDirtMound(struct Sprite *);
static void AnimBonemerangProjectile_Step(struct Sprite *);
static void AnimBonemerangProjectile_End(struct Sprite *);
static void AnimMudSportDirtRising(struct Sprite *);
static void AnimMudSportDirtFalling(struct Sprite *);
static void AnimTask_DigBounceMovement(u8);
static void AnimTask_DigEndBounceMovementSetInvisible(u8);
static void AnimTask_DigSetVisibleUnderground(u8);
static void AnimTask_DigRiseUpFromHole(u8);
static void sub_81150E0(u8, s16, s16);
static void AnimTask_ShakeTerrain(u8);
static void AnimTask_ShakeBattlers(u8);
static void SetBattlersXOffsetForShake(struct Task *);
static void sub_81156D0(u8);

static const union AffineAnimCmd sAffineAnim_Bonemerang[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, 15, 1),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd sAffineAnim_SpinningBone[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, 20, 1),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd *const sAffineAnims_Bonemerang[] =
{
    sAffineAnim_Bonemerang,
};

static const union AffineAnimCmd *const sAffineAnims_SpinningBone[] =
{
    sAffineAnim_SpinningBone,
};

const struct SpriteTemplate gBonemerangSpriteTemplate =
{
    .tileTag = ANIM_TAG_BONE,
    .paletteTag = ANIM_TAG_BONE,
    .oam = &gOamData_AffineNormal_ObjNormal_32x32,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = sAffineAnims_Bonemerang,
    .callback = AnimBonemerangProjectile,
};

const struct SpriteTemplate gSpinningBoneSpriteTemplate =
{
    .tileTag = ANIM_TAG_BONE,
    .paletteTag = ANIM_TAG_BONE,
    .oam = &gOamData_AffineNormal_ObjNormal_32x32,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = sAffineAnims_SpinningBone,
    .callback = AnimBoneHitProjectile,
};

const struct SpriteTemplate gSandAttackDirtSpriteTemplate =
{
    .tileTag = ANIM_TAG_MUD_SAND,
    .paletteTag = ANIM_TAG_MUD_SAND,
    .oam = &gOamData_AffineOff_ObjNormal_8x8,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimDirtScatter,
};

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

static const union AnimCmd *const sAnims_MudSlapMud[] =
{
    sAnim_MudSlapMud,
};

const struct SpriteTemplate gMudSlapMudSpriteTemplate =
{
    .tileTag = ANIM_TAG_MUD_SAND,
    .paletteTag = ANIM_TAG_MUD_SAND,
    .oam = &gOamData_AffineOff_ObjNormal_16x16,
    .anims = sAnims_MudSlapMud,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimDirtScatter,
};

const struct SpriteTemplate gMudsportMudSpriteTemplate =
{
    .tileTag = ANIM_TAG_MUD_SAND,
    .paletteTag = ANIM_TAG_MUD_SAND,
    .oam = &gOamData_AffineOff_ObjNormal_16x16,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimMudSportDirt,
};

const struct SpriteTemplate gDirtPlumeSpriteTemplate =
{
    .tileTag = ANIM_TAG_MUD_SAND,
    .paletteTag = ANIM_TAG_MUD_SAND,
    .oam = &gOamData_AffineOff_ObjNormal_8x8,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimDirtPlumeParticle,
};

const struct SpriteTemplate gDirtMoundSpriteTemplate =
{
    .tileTag = ANIM_TAG_DIRT_MOUND,
    .paletteTag = ANIM_TAG_DIRT_MOUND,
    .oam = &gOamData_AffineOff_ObjNormal_32x16,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = AnimDigDirtMound,
};

// Moves a bone projectile towards the target mon, which moves like
// a boomerang. After hitting the target mon, it comes back to the user.
static void AnimBonemerangProjectile(struct Sprite *sprite)
{
    sprite->pos1.x = GetBattlerSpriteCoord(gBattleAnimAttacker, 2);
    sprite->pos1.y = GetBattlerSpriteCoord(gBattleAnimAttacker, 3);
    sprite->data[0] = 20;
    sprite->data[2] = GetBattlerSpriteCoord(gBattleAnimTarget, 2);
    sprite->data[4] = GetBattlerSpriteCoord(gBattleAnimTarget, 3);
    sprite->data[5] = -40;
    InitAnimArcTranslation(sprite);
    sprite->callback = AnimBonemerangProjectile_Step;
}

static void AnimBonemerangProjectile_Step(struct Sprite *sprite)
{
    if (TranslateAnimHorizontalArc(sprite))
    {
        sprite->pos1.x += sprite->pos2.x;
        sprite->pos1.y += sprite->pos2.y;
        sprite->pos2.y = 0;
        sprite->pos2.x = 0;
        sprite->data[0] = 20;
        sprite->data[2] = GetBattlerSpriteCoord(gBattleAnimAttacker, 2);
        sprite->data[4] = GetBattlerSpriteCoord(gBattleAnimAttacker, 3);
        sprite->data[5] = 40;
        InitAnimArcTranslation(sprite);
        sprite->callback = AnimBonemerangProjectile_End;
    }
}

static void AnimBonemerangProjectile_End(struct Sprite *sprite)
{
    if (TranslateAnimHorizontalArc(sprite))
        DestroyAnimSprite(sprite);
}

// Moves a bone projectile towards the target mon, starting right next to
// the target mon.
// arg 0: initial x pixel offset
// arg 1: initial y pixel offset
// arg 2: target x pixel offset
// arg 3: target y pixel offset
// arg 4: duration
static void AnimBoneHitProjectile(struct Sprite *sprite)
{
    InitSpritePosToAnimTarget(sprite, TRUE);
    if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER)
        gBattleAnimArgs[2] = -gBattleAnimArgs[2];

    sprite->data[0] = gBattleAnimArgs[4];
    sprite->data[2] = GetBattlerSpriteCoord(gBattleAnimTarget, 2) + gBattleAnimArgs[2];
    sprite->data[4] = GetBattlerSpriteCoord(gBattleAnimTarget, 3) + gBattleAnimArgs[3];
    sprite->callback = StartAnimLinearTranslation;
    StoreSpriteCallbackInData6(sprite, DestroyAnimSprite);
}

// Moves a small dirt projectile towards the target mon.
// arg 0: initial x pixel offset
// arg 1: initial y pixel offset
// arg 2: duration
// arg 3: target x pixel offset
// arg 4: target y pixel offset
static void AnimDirtScatter(struct Sprite *sprite)
{
    u8 targetXPos, targetYPos;
    s16 xOffset, yOffset;

    InitSpritePosToAnimAttacker(sprite, 1);

    targetXPos = GetBattlerSpriteCoord2(gBattleAnimTarget, 2);
    targetYPos = GetBattlerSpriteCoord2(gBattleAnimTarget, 3);

    xOffset = Random2() & 0x1F;
    yOffset = Random2() & 0x1F;
    if (xOffset > 16)
        xOffset = 16 - xOffset;
    if (yOffset > 16)
        yOffset = 16 - yOffset;

    sprite->data[0] = gBattleAnimArgs[2];
    sprite->data[2] = targetXPos + xOffset;
    sprite->data[4] = targetYPos + yOffset;
    sprite->callback = StartAnimLinearTranslation;
    StoreSpriteCallbackInData6(sprite, DestroySpriteAndMatrix);
}

// Moves a particle of dirt in the Mud Sport animation.
// The dirt can either be rising upward, or falling down.
// arg 0: 0 = dirt is rising into the air, 1 = dirt is falling down
// arg 1: initial x pixel offset
// arg 2: initial y pixel offset
static void AnimMudSportDirt(struct Sprite *sprite)
{
    sprite->oam.tileNum++;
    if (gBattleAnimArgs[0] == 0)
    {
        sprite->pos1.x = GetBattlerSpriteCoord(gBattleAnimAttacker, 2) + gBattleAnimArgs[1];
        sprite->pos1.y = GetBattlerSpriteCoord(gBattleAnimAttacker, 3) + gBattleAnimArgs[2];
        sprite->data[0] = gBattleAnimArgs[1] > 0 ? 1 : -1;
        sprite->callback = AnimMudSportDirtRising;
    }
    else
    {
        sprite->pos1.x = gBattleAnimArgs[1];
        sprite->pos1.y = gBattleAnimArgs[2];
        sprite->pos2.y = -gBattleAnimArgs[2];
        sprite->callback = AnimMudSportDirtFalling;
    }
}

static void AnimMudSportDirtRising(struct Sprite *sprite)
{
    if (++sprite->data[1] > 1)
    {
        sprite->data[1] = 0;
        sprite->pos1.x += sprite->data[0];
    }

    sprite->pos1.y -= 4;
    if (sprite->pos1.y < -4)
        DestroyAnimSprite(sprite);
}

static void AnimMudSportDirtFalling(struct Sprite *sprite)
{
    switch (sprite->data[0])
    {
    case 0:
        sprite->pos2.y += 4;
        if (sprite->pos2.y >= 0)
        {
            sprite->pos2.y = 0;
            sprite->data[0]++;
        }
        break;
    case 1:
        if (++sprite->data[1] > 0)
        {
            sprite->data[1] = 0;
            sprite->invisible ^= 1;
            if (++sprite->data[2] == 10)
                DestroyAnimSprite(sprite);
        }
        break;
    }
}

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

    if (gBattleAnimArgs[0] == FALSE)
        task->func = AnimTask_DigBounceMovement;
    else
        task->func = AnimTask_DigEndBounceMovementSetInvisible;

    task->func(taskId);
}

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

    switch (task->data[0])
    {
    case 0:
        task->data[10] = GetAnimBattlerSpriteId(ANIM_ATTACKER);
        task->data[11] = GetBattlerSpriteBGPriorityRank(gBattleAnimAttacker);
        if (task->data[11] == 1)
        {
            task->data[12] = gBattle_BG1_X;
            task->data[13] = gBattle_BG1_Y;
        }
        else
        {
            task->data[12] = gBattle_BG2_X;
            task->data[13] = gBattle_BG2_Y;
        }

        var0 = GetBattlerYCoordWithElevation(gBattleAnimAttacker);
        task->data[14] = var0 - 32;
        task->data[15] = var0 + 32;
        if (task->data[14] < 0)
            task->data[14] = 0;

        gSprites[task->data[10]].invisible = TRUE;
        task->data[0]++;
        break;
    case 1:
        sub_81150E0(task->data[11], task->data[14], task->data[15]);
        task->data[0]++;
        break;
    case 2:
        task->data[2] = (task->data[2] + 6) & 0x7F;
        if (++task->data[4] > 2)
        {
            task->data[4] = 0;
            task->data[3]++;
        }

        task->data[5] = task->data[3] + (gSineTable[task->data[2]] >> 4);
        if (task->data[11] == 1)
            gBattle_BG1_Y = task->data[13] - task->data[5];
        else
            gBattle_BG2_Y = task->data[13] - task->data[5];

        if (task->data[5] > 63)
        {
            task->data[5] = 120 - task->data[14];
            if (task->data[11] == 1)
                gBattle_BG1_Y = task->data[13] - task->data[5];
            else
                gBattle_BG2_Y = task->data[13] - task->data[5];

            gSprites[task->data[10]].pos2.x = 272 - gSprites[task->data[10]].pos1.x;
            task->data[0]++;
        }
        break;
    case 3:
        gScanlineEffect.state = 3;
        task->data[0]++;
        break;
    case 4:
        DestroyAnimVisualTask(taskId);
        gSprites[task->data[10]].invisible = TRUE;
        break;
    }
}

static void AnimTask_DigEndBounceMovementSetInvisible(u8 taskId)
{
    u8 spriteId = GetAnimBattlerSpriteId(ANIM_ATTACKER);
    gSprites[spriteId].invisible = TRUE;
    gSprites[spriteId].pos2.x = 0;
    gSprites[spriteId].pos2.y = 0;

    if (GetBattlerSpriteBGPriorityRank(gBattleAnimAttacker) == 1)
        gBattle_BG1_Y = 0;
    else
        gBattle_BG2_Y = 0;

    DestroyAnimVisualTask(taskId);
}

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

    if (gBattleAnimArgs[0] == FALSE)
        task->func = AnimTask_DigSetVisibleUnderground;
    else
        task->func = AnimTask_DigRiseUpFromHole;

    task->func(taskId);
}

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

    switch (task->data[0])
    {
    case 0:
        task->data[10] = GetAnimBattlerSpriteId(ANIM_ATTACKER);
        gSprites[task->data[10]].invisible = FALSE;
        gSprites[task->data[10]].pos2.x = 0;
        gSprites[task->data[10]].pos2.y = 160 - gSprites[task->data[10]].pos1.y;
        task->data[0]++;
        break;
    case 1:
        DestroyAnimVisualTask(taskId);
    }
}

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

    switch (task->data[0])
    {
    case 0:
        task->data[10] = GetAnimBattlerSpriteId(ANIM_ATTACKER);
        task->data[11] = GetBattlerSpriteBGPriorityRank(gBattleAnimAttacker);
        if (task->data[11] == 1)
            task->data[12] = gBattle_BG1_X;
        else
            task->data[12] = gBattle_BG2_X;

        var0 =  GetBattlerYCoordWithElevation(gBattleAnimAttacker);
        task->data[14] = var0 - 32;
        task->data[15] = var0 + 32;
        task->data[0]++;
        break;
    case 1:
        sub_81150E0(task->data[11], 0, task->data[15]);
        task->data[0]++;
        break;
    case 2:
        gSprites[task->data[10]].pos2.y = 96;
        task->data[0]++;
        break;
    case 3:
        gSprites[task->data[10]].pos2.y -= 8;
        if (gSprites[task->data[10]].pos2.y == 0)
        {
            gScanlineEffect.state = 3;
            task->data[0]++;
        }
        break;
    case 4:
        DestroyAnimVisualTask(taskId);
        break;
    }
}

static void sub_81150E0(u8 useBG1, s16 y, s16 endY)
{
    s16 bgX;
    struct ScanlineEffectParams scanlineParams;

    if (useBG1 == 1)
    {
        bgX = gBattle_BG1_X;
        scanlineParams.dmaDest = &REG_BG1HOFS;
    }
    else
    {
        bgX = gBattle_BG2_X;
        scanlineParams.dmaDest = &REG_BG2HOFS;
    }

    if (y < 0)
        y = 0;

    while (y < endY)
    {
        gScanlineEffectRegBuffers[0][y] = bgX;
        gScanlineEffectRegBuffers[1][y] = bgX;
        y++;
    }

    while (y < 160)
    {
        gScanlineEffectRegBuffers[0][y] = bgX + 240;
        gScanlineEffectRegBuffers[1][y] = bgX + 240;
        y++;
    }

    scanlineParams.dmaControl = SCANLINE_EFFECT_DMACNT_16BIT;
    scanlineParams.initState = 1;
    scanlineParams.unused9 = 0;
    ScanlineEffect_SetParams(scanlineParams);
}

// Moves a particle of dirt in a plume of dirt. Used in Fissure and Dig.
// arg 0: which mon (0 = attacker, 1 = target)
// arg 1: which side of mon (0 = left, 1 = right)
// arg 2: target x offset
// arg 3: target y offset
// arg 4: wave amplitude
// arg 5: duration
void AnimDirtPlumeParticle(struct Sprite *sprite)
{
    s8 battler;
    s16 xOffset;

    if (gBattleAnimArgs[0] == 0)
        battler = gBattleAnimAttacker;
    else
        battler = gBattleAnimTarget;

    xOffset = 24;
    if (gBattleAnimArgs[1] == 1)
    {
        xOffset *= -1;
        gBattleAnimArgs[2] *= -1;
    }

    sprite->pos1.x = GetBattlerSpriteCoord(battler, 2) + xOffset;
    sprite->pos1.y = GetBattlerYCoordWithElevation(battler) + 30;
    sprite->data[0] = gBattleAnimArgs[5];
    sprite->data[2] = sprite->pos1.x + gBattleAnimArgs[2];
    sprite->data[4] = sprite->pos1.y + gBattleAnimArgs[3];
    sprite->data[5] = gBattleAnimArgs[4];
    InitAnimArcTranslation(sprite);
    sprite->callback = AnimDirtPlumeParticle_Step;
}

static void AnimDirtPlumeParticle_Step(struct Sprite *sprite)
{
    if (TranslateAnimHorizontalArc(sprite))
        DestroyAnimSprite(sprite);
}

// Displays the dirt mound seen in the move Dig for set duration.
// The dirt mound image is too large for a single sprite, so two
// sprites are lined up next to each other.
// arg 0: which mon (0 = attacker, 1 = target)
// arg 1: oam tile num (0 = left half of image, 1 = right half of image)
// arg 2: duration
static void AnimDigDirtMound(struct Sprite *sprite)
{
    s8 battler;

    if (gBattleAnimArgs[0] == 0)
        battler = gBattleAnimAttacker;
    else
        battler = gBattleAnimTarget;

    sprite->pos1.x = GetBattlerSpriteCoord(battler, 0) - 16 + (gBattleAnimArgs[1] * 32);
    sprite->pos1.y = GetBattlerYCoordWithElevation(battler) + 32;
    sprite->oam.tileNum += gBattleAnimArgs[1] * 8;
    StoreSpriteCallbackInData6(sprite, DestroyAnimSprite);
    sprite->data[0] = gBattleAnimArgs[2];
    sprite->callback = WaitAnimForDuration;
}

#define tState               data[0]
#define tDelay               data[1]
#define tTimer               data[2]
#define tMaxTime             data[3]
#define tbattlerSpriteIds(i) data[9 + (i)]
#define tNumBattlers         data[13] // AnimTask_ShakeBattlers
#define tInitialX            data[13] // AnimTask_ShakeTerrain
#define tHorizOffset         data[14]
#define tInitHorizOffset     data[15]

// Shakes battler(s) or the battle terrain back and forth horizontally. Used by e.g. Earthquake, Eruption
// arg0: What to shake. 0-3 for any specific battler, MAX_BATTLERS_COUNT for all battlers, MAX_BATTLERS_COUNT + 1 for the terrain
// arg1: Shake intensity, used to calculate horizontal pixel offset (if 0, use move power instead)
// arg2: Length of time to shake for
void AnimTask_HorizontalShake(u8 taskId)
{
    u16 i;
    struct Task *task = &gTasks[taskId];

    if (gBattleAnimArgs[1] != 0)
        task->tHorizOffset = task->tInitHorizOffset = gBattleAnimArgs[1] + 3;
    else
        task->tHorizOffset = task->tInitHorizOffset = (gAnimMovePower / 10) + 3;

    task->tMaxTime = gBattleAnimArgs[2];
    switch (gBattleAnimArgs[0])
    {
    case MAX_BATTLERS_COUNT + 1: // Shake terrain
        task->tInitialX = gBattle_BG3_X;
        task->func = AnimTask_ShakeTerrain;
        break;
    case MAX_BATTLERS_COUNT: // Shake all battlers
        task->tNumBattlers = 0;
        for (i = 0; i < MAX_BATTLERS_COUNT; i++)
        {
            if (IsBattlerSpriteVisible(i))
            {
                task->tbattlerSpriteIds(task->tNumBattlers) = gBattlerSpriteIds[i];
                task->tNumBattlers++;
            }
        }
        task->func = AnimTask_ShakeBattlers;
        break;
    default: // Shake specific battler
        task->tbattlerSpriteIds(0) = GetAnimBattlerSpriteId(gBattleAnimArgs[0]);
        if (task->tbattlerSpriteIds(0) == 0xFF)
        {
            DestroyAnimVisualTask(taskId);
        }
        else
        {
            task->tNumBattlers = 1;
            task->func = AnimTask_ShakeBattlers;
        }
        break;
    }
}

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

    switch (task->tState)
    {
    case 0:
        if (++task->tDelay > 1)
        {
            task->tDelay = 0;
            if ((task->tTimer & 1) == 0)
                gBattle_BG3_X = task->tInitialX + task->tInitHorizOffset;
            else
                gBattle_BG3_X = task->tInitialX - task->tInitHorizOffset;

            if (++task->tTimer == task->tMaxTime)
            {
                task->tTimer = 0;
                task->tHorizOffset--;
                task->tState++;
            }
        }
        break;
    case 1:
        if (++task->tDelay > 1)
        {
            task->tDelay = 0;
            if ((task->tTimer & 1) == 0)
                gBattle_BG3_X = task->tInitialX + task->tHorizOffset;
            else
                gBattle_BG3_X = task->tInitialX - task->tHorizOffset;

            if (++task->tTimer == 4)
            {
                task->tTimer = 0;
                if (--task->tHorizOffset == 0)
                    task->tState++;
            }
        }
        break;
    case 2:
        gBattle_BG3_X = task->tInitialX;
        DestroyAnimVisualTask(taskId);
        break;
    }
}

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

    switch (task->tState)
    {
    case 0:
        if (++task->tDelay > 1)
        {
            task->tDelay = 0;
            SetBattlersXOffsetForShake(task);
            if (++task->tTimer == task->tMaxTime)
            {
                task->tTimer = 0;
                task->tHorizOffset--;
                task->tState++;
            }
        }
        break;
    case 1:
        if (++task->tDelay > 1)
        {
            task->tDelay = 0;
            SetBattlersXOffsetForShake(task);
            if (++task->tTimer == 4)
            {
                task->tTimer = 0;
                if (--task->tHorizOffset == 0)
                    task->tState++;
            }
        }
        break;
    case 2:
        for (i = 0; i < task->tNumBattlers; i++)
            gSprites[task->tbattlerSpriteIds(i)].pos2.x = 0;

        DestroyAnimVisualTask(taskId);
        break;
    }
}

static void SetBattlersXOffsetForShake(struct Task *task)
{
    u16 i;
    u16 xOffset;

    if ((task->tTimer & 1) == 0)
        xOffset = (task->tHorizOffset / 2) + (task->tHorizOffset & 1);
    else
        xOffset = -(task->tHorizOffset / 2);

    for (i = 0; i < task->tNumBattlers; i++)
    {
        gSprites[task->tbattlerSpriteIds(i)].pos2.x = xOffset;
    }
}

#undef tState
#undef tDelay
#undef tTimer
#undef tMaxTime
#undef tbattlerSpriteIds
#undef tNumBattlers
#undef tInitialX
#undef tHorizOffset
#undef tInitHorizOffset

void AnimTask_IsPowerOver99(u8 taskId)
{
    gBattleAnimArgs[15] = gAnimMovePower > 99;
    DestroyAnimVisualTask(taskId);
}

void AnimTask_PositionFissureBgOnBattler(u8 taskId)
{
    struct Task *newTask;
    u8 battler = (gBattleAnimArgs[0] & ANIM_TARGET) ? gBattleAnimTarget : gBattleAnimAttacker;

    if (gBattleAnimArgs[0] > ANIM_TARGET)
        battler ^= 2;

    newTask = &gTasks[CreateTask(sub_81156D0, gBattleAnimArgs[1])];
    newTask->data[1] = (32 - GetBattlerSpriteCoord(battler, 2)) & 0x1FF;
    newTask->data[2] = (64 - GetBattlerSpriteCoord(battler, 3)) & 0xFF;
    gBattle_BG3_X = newTask->data[1];
    gBattle_BG3_Y = newTask->data[2];
    newTask->data[3] = gBattleAnimArgs[2];
    DestroyAnimVisualTask(taskId);
}

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

    if (gBattleAnimArgs[7] == task->data[3])
    {
        gBattle_BG3_X = 0;
        gBattle_BG3_Y = 0;
        DestroyTask(taskId);
    }
    else
    {
        gBattle_BG3_X = task->data[1];
        gBattle_BG3_Y = task->data[2];
    }
}

repo_name: Lunos/pokeemerald

license: mit