#include "global.h"
#include "gpu_regs.h"
#include "palette.h"
#include "script.h"
#include "sound.h"
#include "task.h"
#include "strings.h"
#include "party_menu.h"
#include "fieldmap.h"
#include "field_effect.h"
#include "field_camera.h"
#include "field_player_avatar.h"
#include "fldeff.h"
#include "fldeff_misc.h"
#include "secret_base.h"
#include "event_data.h"
#include "event_scripts.h"
#include "event_object_movement.h"
#include "metatile_behavior.h"
#include "string_util.h"
#include "constants/field_effects.h"
#include "constants/metatile_behaviors.h"
#include "constants/songs.h"
#include "constants/vars.h"

extern struct MapPosition gPlayerFacingPosition;

static void sub_80F9C90(u8);
static void sub_80F9DFC(u8);

static void Task_SecretBasePCTurnOn(u8);

static void Task_PopSecretBaseBalloon(u8);
static void DoBalloonSoundEffect(s16);

static void Task_WateringBerryTreeAnim_1(u8);
static void Task_WateringBerryTreeAnim_2(u8);
static void Task_WateringBerryTreeAnim_3(u8);

static void sub_80F9C44(TaskFunc, u16, u16, u8);

static void FieldCallback_SecretBaseCave(void);
static void CaveEntranceSpriteCallback1(struct Sprite *);
static void CaveEntranceSpriteCallback2(struct Sprite *);
static void CaveEntranceSpriteCallbackEnd(struct Sprite *);
static void StartSecretBaseCaveFieldEffect(void);

static void FieldCallback_SecretBaseTree(void);
static void TreeEntranceSpriteCallback1(struct Sprite *);
static void TreeEntranceSpriteCallback2(struct Sprite *);
static void TreeEntranceSpriteCallbackEnd(struct Sprite *);
static void StartSecretBaseTreeFieldEffect(void);

static void FieldCallback_SecretBaseShrub(void);
static void ShrubEntranceSpriteCallback1(struct Sprite *);
static void ShrubEntranceSpriteCallback2(struct Sprite *);
static void ShrubEntranceSpriteCallbackEnd(struct Sprite *);
static void StartSecretBaseShrubFieldEffect(void);

static void SpriteCB_SandPillar_0(struct Sprite *);
static void SpriteCB_SandPillar_1(struct Sprite *);
static void SpriteCB_SandPillar_2(struct Sprite *);

static const u8 gSpriteImage_858D978[] = INCBIN_U8("graphics/unknown/858E588/0.4bpp");
static const u8 gSpriteImage_858D9F8[] = INCBIN_U8("graphics/unknown/858E588/1.4bpp");
static const u8 gSpriteImage_858DA78[] = INCBIN_U8("graphics/unknown/858E588/2.4bpp");
static const u8 gSpriteImage_858DAF8[] = INCBIN_U8("graphics/unknown/858E588/3.4bpp");
static const u8 gSpriteImage_858DB78[] = INCBIN_U8("graphics/unknown/858E588/4.4bpp");

static const u8 gUnusedEmptySpace_858DBF8[32] = {0};

static const u16 gFieldEffectObjectPalette7[] = INCBIN_U16("graphics/event_objects/palettes/field_effect_object_palette_07.gbapal");
static const u8 gSpriteImage_858DC38[] = INCBIN_U8("graphics/unknown/858E5D8/0.4bpp");
static const u8 gSpriteImage_858DCB8[] = INCBIN_U8("graphics/unknown/858E5D8/1.4bpp");
static const u8 gSpriteImage_858DD38[] = INCBIN_U8("graphics/unknown/858E5D8/2.4bpp");
static const u8 gSpriteImage_858DDB8[] = INCBIN_U8("graphics/unknown/858E5D8/3.4bpp");
static const u8 gSpriteImage_858DE38[] = INCBIN_U8("graphics/unknown/858E5D8/4.4bpp");
static const u8 gSpriteImage_858DEB8[] = INCBIN_U8("graphics/unknown/858E5B0/0.4bpp");
static const u8 gSpriteImage_858DF38[] = INCBIN_U8("graphics/unknown/858E5B0/1.4bpp");
static const u8 gSpriteImage_858DFB8[] = INCBIN_U8("graphics/unknown/858E5B0/2.4bpp");
static const u8 gSpriteImage_858E038[] = INCBIN_U8("graphics/unknown/858E5B0/3.4bpp");
static const u8 gSpriteImage_858E0B8[] = INCBIN_U8("graphics/unknown/858E5B0/4.4bpp");
static const u8 gSpriteImage_858E138[] = INCBIN_U8("graphics/unknown/858E5B0/5.4bpp");
static const u16 gFieldEffectObjectPalette8[] = INCBIN_U16("graphics/event_objects/palettes/field_effect_object_palette_08.gbapal");
static const u8 gSpriteImage_858E1D8[] = INCBIN_U8("graphics/unknown/858E674/0.4bpp");
static const u8 gSpriteImage_858E2D8[] = INCBIN_U8("graphics/unknown/858E674/1.4bpp");
static const u8 gSpriteImage_858E3D8[] = INCBIN_U8("graphics/unknown/858E674/2.4bpp");

static const struct OamData gOamData_858E4D8 =
{
    .y = 0,
    .x = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .bpp = ST_OAM_4BPP,
    .shape = ST_OAM_SQUARE,
    .size = 1,
    .priority = 2,
};

static const union AnimCmd gSpriteAnim_858E4E0[] =
{
    ANIMCMD_FRAME(0, 8),
    ANIMCMD_FRAME(1, 8),
    ANIMCMD_FRAME(2, 8),
    ANIMCMD_FRAME(3, 8),
    ANIMCMD_FRAME(4, 8),
    ANIMCMD_END,
};

static const union AnimCmd gSpriteAnim_858E4F8[] =
{
    ANIMCMD_FRAME(0, 8),
    ANIMCMD_FRAME(1, 8),
    ANIMCMD_FRAME(2, 8),
    ANIMCMD_FRAME(3, 8),
    ANIMCMD_FRAME(4, 8),
    ANIMCMD_END,
};

static const union AnimCmd gSpriteAnim_858E510[] =
{
    ANIMCMD_FRAME(4, 8),
    ANIMCMD_FRAME(3, 8),
    ANIMCMD_FRAME(2, 8),
    ANIMCMD_FRAME(1, 8),
    ANIMCMD_FRAME(0, 8),
    ANIMCMD_END,
};

static const union AnimCmd gSpriteAnim_858E528[] =
{
    ANIMCMD_FRAME(0, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(1, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(2, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(3, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(4, 8, .hFlip = TRUE),
    ANIMCMD_END,
};

static const union AnimCmd gSpriteAnim_858E540[] =
{
    ANIMCMD_FRAME(4, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(3, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(2, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(1, 8, .hFlip = TRUE),
    ANIMCMD_FRAME(0, 8, .hFlip = TRUE),
    ANIMCMD_END,
};

static const union AnimCmd gSpriteAnim_858E558[] =
{
    ANIMCMD_FRAME(0, 8),
    ANIMCMD_FRAME(1, 8),
    ANIMCMD_FRAME(2, 8),
    ANIMCMD_FRAME(3, 8),
    ANIMCMD_FRAME(4, 8),
    ANIMCMD_END,
};

static const union AnimCmd *const gSpriteAnimTable_858E570[] =
{
    gSpriteAnim_858E4E0,
};

static const union AnimCmd *const gSpriteAnimTable_858E574[] =
{
    gSpriteAnim_858E4F8,
    gSpriteAnim_858E510,
    gSpriteAnim_858E528,
    gSpriteAnim_858E540,
};

static const union AnimCmd *const gSpriteAnimTable_858E584[] =
{
    gSpriteAnim_858E558,
};

static const struct SpriteFrameImage gUnknown_858E588[] =
{
    {gSpriteImage_858D978, 0x80},
    {gSpriteImage_858D9F8, 0x80},
    {gSpriteImage_858DA78, 0x80},
    {gSpriteImage_858DAF8, 0x80},
    {gSpriteImage_858DB78, 0x80},
};

static const struct SpriteFrameImage gUnknown_858E5B0[] =
{
    {gSpriteImage_858DEB8, 0x80},
    {gSpriteImage_858DF38, 0x80},
    {gSpriteImage_858DFB8, 0x80},
    {gSpriteImage_858E038, 0x80},
    {gSpriteImage_858E0B8, 0x80},
};

static const struct SpriteFrameImage gUnknown_858E5D8[] =
{
    {gSpriteImage_858DC38, 0x80},
    {gSpriteImage_858DCB8, 0x80},
    {gSpriteImage_858DD38, 0x80},
    {gSpriteImage_858DDB8, 0x80},
    {gSpriteImage_858DE38, 0x80},
};

static const struct SpriteTemplate gUnknown_0858E600 =
{
    .tileTag = 0xFFFF,
    .paletteTag = 0x1003,
    .oam = &gOamData_858E4D8,
    .anims = gSpriteAnimTable_858E570,
    .images = gUnknown_858E588,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = CaveEntranceSpriteCallback1,
};

static const struct SpriteTemplate gUnknown_0858E618 =
{
    .tileTag = 0xFFFF,
    .paletteTag = 0x1008,
    .oam = &gOamData_858E4D8,
    .anims = gSpriteAnimTable_858E574,
    .images = gUnknown_858E5B0,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = TreeEntranceSpriteCallback1,
};

static const struct SpriteTemplate gUnknown_0858E630 =
{
    .tileTag = 0xFFFF,
    .paletteTag = 0x1008,
    .oam = &gOamData_858E4D8,
    .anims = gSpriteAnimTable_858E584,
    .images = gUnknown_858E5D8,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = ShrubEntranceSpriteCallback1,
};

const struct SpritePalette gFieldEffectObjectPaletteInfo7 = {gFieldEffectObjectPalette7, 0x1003};
const struct SpritePalette gFieldEffectObjectPaletteInfo8 = {gFieldEffectObjectPalette8, 0x1008};

static const struct OamData gOamData_858E658 =
{
    .x = 0,
    .y = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .bpp = ST_OAM_4BPP,
    .shape = ST_OAM_V_RECTANGLE,
    .size = 2,
    .priority = 2,
};

static const union AnimCmd gSpriteAnim_858E660[] =
{
    ANIMCMD_FRAME(0, 6),
    ANIMCMD_FRAME(1, 6),
    ANIMCMD_FRAME(2, 6),
    ANIMCMD_END,
};

static const union AnimCmd *const gSpriteAnimTable_858E670[] =
{
    gSpriteAnim_858E660,
};

static const struct SpriteFrameImage gUnknown_0858E674[] =
{
    {gSpriteImage_858E1D8, 0x100},
    {gSpriteImage_858E2D8, 0x100},
    {gSpriteImage_858E3D8, 0x100},
};

static const struct SpriteTemplate gUnknown_0858E68C =
{
    .tileTag = 0xFFFF,
    .paletteTag = 0x100E,
    .oam = &gOamData_858E658,
    .anims = gSpriteAnimTable_858E670,
    .images = gUnknown_0858E674,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_SandPillar_0,
};

// This uses one of the secret base palettes, so there is no "field_effect_object_palette_09.pal" file.
const struct SpritePalette gFieldEffectObjectPaletteInfo9 = {gTilesetPalettes_SecretBase[5], 0x100E};

static const u8 gSpriteImage_858E6AC[] = INCBIN_U8("graphics/unknown/858E84C/0.4bpp");
static const u8 gSpriteImage_858E72C[] = INCBIN_U8("graphics/unknown/858E84C/1.4bpp");
static const u8 gSpriteImage_858E7AC[] = INCBIN_U8("graphics/unknown/858E84C/2.4bpp");
static const u16 gUnknown_0858E82C[] = INCBIN_U16("graphics/unknown/unknown_58E82C.gbapal");

static const struct SpriteFrameImage gUnknown_0858E84C[] =
{
    {gSpriteImage_858E6AC, sizeof(gSpriteImage_858E6AC)},
    {gSpriteImage_858E72C, sizeof(gSpriteImage_858E72C)},
    {gSpriteImage_858E7AC, sizeof(gSpriteImage_858E7AC)},
};

static const struct SpritePalette gUnknown_0858E864 = {gUnknown_0858E82C, 0x1000};

static const union AnimCmd gSpriteAnim_858E86C[] =
{
    ANIMCMD_FRAME(0, 30),
    ANIMCMD_FRAME(1, 30),
    ANIMCMD_FRAME(2, 30),
    ANIMCMD_JUMP(0),
};

static const union AnimCmd *const gSpriteAnimTable_858E87C[] =
{
    gSpriteAnim_858E86C,
};

static const struct SpriteTemplate gUnknown_0858E880 =
{
    .tileTag = 0xFFFF,
    .paletteTag = 0x1000,
    .oam = &gEventObjectBaseOam_32x8,
    .anims = gSpriteAnimTable_858E87C,
    .images = gUnknown_0858E84C,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy,
};

void sub_80F9BCC(u16 a0, u16 a1, u8 a2)
{
    sub_80F9C44(sub_80F9C90, a0, a1, a2);
}

void sub_80F9BF4(u16 a0, u16 a1, u8 a2)
{
    sub_80F9C44(sub_80F9DFC, a0, a1, a2);
}

bool8 sub_80F9C1C(void)
{
    return FuncIsActiveTask(sub_80F9C90);
}

bool8 sub_80F9C30(void)
{
    return FuncIsActiveTask(sub_80F9DFC);
}

static void sub_80F9C44(void (*taskfunc) (u8), u16 a1, u16 a2, u8 a3)
{
    u8 taskId = CreateTask(taskfunc, a3);

    gTasks[taskId].data[0] = 0;
    gTasks[taskId].data[1] = a1 == 0 ? 16 : a1;
    gTasks[taskId].data[2] = a1 == 0 ? 20 : a1;
    gTasks[taskId].func(taskId);
}

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

    switch (task->data[0])
    {
    case 0:
        task->data[3] = 0x78;
        task->data[4] = 0x78;
        task->data[5] = 0x50;
        task->data[6] = 0x51;

        SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(task->data[3], task->data[4]));
        SetGpuReg(REG_OFFSET_WIN0V, WIN_RANGE(task->data[5], task->data[6]));
        SetGpuReg(REG_OFFSET_WININ, 0x3F);
        SetGpuReg(REG_OFFSET_WINOUT, 0);

        break;
    case 1:
        task->data[7] = GetGpuReg(REG_OFFSET_BLDCNT);
        task->data[8] = GetGpuReg(REG_OFFSET_BLDY);

        SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_ALL | BLDCNT_EFFECT_LIGHTEN);
        SetGpuReg(REG_OFFSET_BLDY, 0x10);

        break;
    case 2:
        task->data[3] -= task->data[1];
        task->data[4] += task->data[1];

        if (task->data[3] < 1 || task->data[4] > 0xEF)
        {
            task->data[3] = 0;
            task->data[4] = 0xF0;
            SetGpuReg(REG_OFFSET_BLDY, 0);
            SetGpuReg(REG_OFFSET_BLDCNT, task->data[7]);
            BlendPalettes(0xFFFFFFFF, 0, 0);
            gPlttBufferFaded[0] = 0;
        }
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(task->data[3], task->data[4]));

        if (task->data[3] != 0)
            return;
        break;
    case 3:
        task->data[5] -= task->data[2];
        task->data[6] += task->data[2];

        if (task->data[5] < 1 || task->data[6] > 0x9F)
        {
            task->data[5] = 0;
            task->data[6] = 0xA0;
            ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        }
        SetGpuReg(REG_OFFSET_WIN0V, WIN_RANGE(task->data[5], task->data[6]));

        if (task->data[5] != 0)
            return;
        break;
    default:
        SetGpuReg(REG_OFFSET_BLDCNT, task->data[7]);
        DestroyTask(taskId);
        return;
    }
    task->data[0]++;
}

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

    int temp = task->data[0];

    switch (temp)
    {
    case 0:
        gPlttBufferFaded[0] = temp;
        break;
    case 1:
        task->data[3] = 0;
        task->data[4] = 0xF0;
        task->data[5] = 0;
        task->data[6] = 0xA0;

        SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(task->data[3], task->data[4]));
        SetGpuReg(REG_OFFSET_WIN0V, WIN_RANGE(task->data[5], task->data[6]));
        SetGpuReg(REG_OFFSET_WININ, 0x3F);
        SetGpuReg(REG_OFFSET_WINOUT, 0);
        break;
    case 2:
        task->data[5] += task->data[2];
        task->data[6] -= task->data[2];

        if (task->data[5] > 0x4F || task->data[6] < 0x52)
        {
            task->data[5] = 0x50;
            task->data[6] = 0x51;
            SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_ALL | BLDCNT_EFFECT_LIGHTEN);
            SetGpuReg(REG_OFFSET_BLDY, 0x10);
        }
        SetGpuReg(REG_OFFSET_WIN0V, WIN_RANGE(task->data[5], task->data[6]));

        if (task->data[5] != 0x50)
            return;
        break;
    case 3:
        task->data[3] += task->data[1];
        task->data[4] -= task->data[1];

        if (task->data[3] > 0x77 || task->data[4] < 0x79)
        {
            task->data[3] = 0x78;
            task->data[4] = 0x78;
            BlendPalettes(-1, 0x10, 0);
            gPlttBufferFaded[0] = 0;
        }
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(task->data[3], task->data[4]));

        if (task->data[3] != 0x78)
            return;
        break;
    default:
        ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        SetGpuReg(REG_OFFSET_BLDY, 0);
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        DestroyTask(taskId);
        return;
    }
    task->data[0]++;
}

static void SetCurrentSecretBase(void)
{
    sub_80E9608(&gPlayerFacingPosition, gMapHeader.events);
    sub_80E8B6C();
}

static void AdjustSecretPowerSpritePixelOffsets(void)
{
    if (gPlayerAvatar.flags & 0x6)
    {
        switch (gFieldEffectArguments[1])
        {
        case DIR_SOUTH:
            gFieldEffectArguments[5] = 16;
            gFieldEffectArguments[6] = 40;
            break;
        case DIR_NORTH:
            gFieldEffectArguments[5] = 16;
            gFieldEffectArguments[6] = 8;
            break;
        case DIR_WEST:
            gFieldEffectArguments[5] = -8;
            gFieldEffectArguments[6] = 24;
            break;
        case DIR_EAST:
            gFieldEffectArguments[5] = 24;
            gFieldEffectArguments[6] = 24;
            break;
        }
    }
    else
    {
        switch (gFieldEffectArguments[1])
        {
        case DIR_SOUTH:
            gFieldEffectArguments[5] = 8;
            gFieldEffectArguments[6] = 40;
            break;
        case DIR_NORTH:
            gFieldEffectArguments[5] = 8;
            gFieldEffectArguments[6] = 8;
            break;
        case DIR_WEST:
            gFieldEffectArguments[5] = -8;
            gFieldEffectArguments[6] = 24;
            break;
        case DIR_EAST:
            gFieldEffectArguments[5] = 24;
            gFieldEffectArguments[6] = 24;
            break;
        }
    }
}

bool8 SetUpFieldMove_SecretPower(void)
{
    u8 mb;

    sub_80E8BC8();

    if (gSpecialVar_Result == 1 || GetPlayerFacingDirection() != DIR_NORTH)
        return FALSE;

    GetXYCoordsOneStepInFrontOfPlayer(&gPlayerFacingPosition.x, &gPlayerFacingPosition.y);
    mb = MapGridGetMetatileBehaviorAt(gPlayerFacingPosition.x, gPlayerFacingPosition.y);

    if (MetatileBehavior_IsSecretBaseCave(mb) == TRUE)
    {
        SetCurrentSecretBase();
        gFieldCallback2 = FieldCallback_PrepareFadeInFromMenu;
        gPostMenuFieldCallback = FieldCallback_SecretBaseCave;
        return TRUE;
    }

    if (MetatileBehavior_IsSecretBaseTree(mb) == TRUE)
    {
        SetCurrentSecretBase();
        gFieldCallback2 = FieldCallback_PrepareFadeInFromMenu;
        gPostMenuFieldCallback = FieldCallback_SecretBaseTree;
        return TRUE;
    }

    if (MetatileBehavior_IsSecretBaseShrub(mb) == TRUE)
    {
        SetCurrentSecretBase();
        gFieldCallback2 = FieldCallback_PrepareFadeInFromMenu;
        gPostMenuFieldCallback = FieldCallback_SecretBaseShrub;
        return TRUE;
    }

    return FALSE;
}

static void FieldCallback_SecretBaseCave(void)
{
    gFieldEffectArguments[0] = GetCursorSelectionMonId();
    ScriptContext1_SetupScript(EventScript_275A86);
}

bool8 FldEff_UseSecretPowerCave(void)
{
    u8 taskId = oei_task_add();

    gTasks[taskId].data[8] = (u32)StartSecretBaseCaveFieldEffect >> 16;
    gTasks[taskId].data[9] = (u32)StartSecretBaseCaveFieldEffect;

    return FALSE;
}

static void StartSecretBaseCaveFieldEffect(void)
{
    FieldEffectActiveListRemove(FLDEFF_USE_SECRET_POWER_CAVE);
    FieldEffectStart(FLDEFF_SECRET_POWER_CAVE);
}

bool8 FldEff_SecretPowerCave(void)
{
    AdjustSecretPowerSpritePixelOffsets();
    CreateSprite(&gUnknown_0858E600,
                 gSprites[gPlayerAvatar.spriteId].oam.x + gFieldEffectArguments[5],
                 gSprites[gPlayerAvatar.spriteId].oam.y + gFieldEffectArguments[6],
                 148);
    return FALSE;
}

static void CaveEntranceSpriteCallback1(struct Sprite *sprite)
{
    PlaySE(SE_W088);

    sprite->data[0] = 0;
    sprite->callback = CaveEntranceSpriteCallback2;
}

static void CaveEntranceSpriteCallback2(struct Sprite *sprite)
{
    if (sprite->data[0] < 40)
    {
        sprite->data[0]++;

        if (sprite->data[0] == 20)
            sub_80E8D4C();
    }
    else
    {
        sprite->data[0] = 0;
        sprite->callback = CaveEntranceSpriteCallbackEnd;
    }
}

static void CaveEntranceSpriteCallbackEnd(struct Sprite *sprite)
{
    FieldEffectStop(sprite, FLDEFF_SECRET_POWER_CAVE);
    EnableBothScriptContexts();
}

static void FieldCallback_SecretBaseTree(void)
{
    gFieldEffectArguments[0] = GetCursorSelectionMonId();
    ScriptContext1_SetupScript(EventScript_275ADF);
}

bool8 FldEff_UseSecretPowerTree(void)
{
    u8 taskId = oei_task_add();

    gTasks[taskId].data[8] = (u32)StartSecretBaseTreeFieldEffect >> 16;
    gTasks[taskId].data[9] = (u32)StartSecretBaseTreeFieldEffect;

    return FALSE;
}

static void StartSecretBaseTreeFieldEffect(void)
{
    FieldEffectActiveListRemove(FLDEFF_USE_SECRET_POWER_TREE);
    FieldEffectStart(FLDEFF_SECRET_POWER_TREE);
}

bool8 FldEff_SecretPowerTree(void)
{
    s16 mb = MapGridGetMetatileBehaviorAt(gPlayerFacingPosition.x, gPlayerFacingPosition.y) & 0xFFF;

    if (mb == MB_SECRET_BASE_SPOT_TREE_LEFT)
        gFieldEffectArguments[7] = 0;

    if (mb == MB_SECRET_BASE_SPOT_TREE_RIGHT)
        gFieldEffectArguments[7] = 2;

    AdjustSecretPowerSpritePixelOffsets();

    CreateSprite(&gUnknown_0858E618,
                 gSprites[gPlayerAvatar.spriteId].oam.x + gFieldEffectArguments[5],
                 gSprites[gPlayerAvatar.spriteId].oam.y + gFieldEffectArguments[6],
                 148);

    if (gFieldEffectArguments[7] == 1 || gFieldEffectArguments[7] == 3)
        sub_80E8D4C();

    return FALSE;
}

static void TreeEntranceSpriteCallback1(struct Sprite *sprite)
{
    PlaySE(SE_W010);

    sprite->animNum = gFieldEffectArguments[7];
    sprite->data[0] = 0;
    sprite->callback = TreeEntranceSpriteCallback2;
}

static void TreeEntranceSpriteCallback2(struct Sprite *sprite)
{
    sprite->data[0]++;

    if (sprite->data[0] >= 40)
    {
        if (gFieldEffectArguments[7] == 0 || gFieldEffectArguments[7] == 2)
            sub_80E8D4C();

        sprite->data[0] = 0;
        sprite->callback = TreeEntranceSpriteCallbackEnd;
    }
}

static void TreeEntranceSpriteCallbackEnd(struct Sprite *sprite)
{
    FieldEffectStop(sprite, FLDEFF_SECRET_POWER_TREE);
    EnableBothScriptContexts();
}

static void FieldCallback_SecretBaseShrub(void)
{
    gFieldEffectArguments[0] = GetCursorSelectionMonId();
    ScriptContext1_SetupScript(EventScript_275B38);
}

bool8 FldEff_UseSecretPowerShrub(void)
{
    u8 taskId = oei_task_add();

    gTasks[taskId].data[8] = (u32)StartSecretBaseShrubFieldEffect >> 16;
    gTasks[taskId].data[9] = (u32)StartSecretBaseShrubFieldEffect;

    return FALSE;
}

static void StartSecretBaseShrubFieldEffect(void)
{
    FieldEffectActiveListRemove(FLDEFF_USE_SECRET_POWER_SHRUB);
    FieldEffectStart(FLDEFF_SECRET_POWER_SHRUB);
}

bool8 FldEff_SecretPowerShrub(void)
{
    AdjustSecretPowerSpritePixelOffsets();

    CreateSprite(&gUnknown_0858E630,
                 gSprites[gPlayerAvatar.spriteId].oam.x + gFieldEffectArguments[5],
                 gSprites[gPlayerAvatar.spriteId].oam.y + gFieldEffectArguments[6],
                 148);

    return FALSE;
}

static void ShrubEntranceSpriteCallback1(struct Sprite *sprite)
{
    PlaySE(SE_W077);

    sprite->data[0] = 0;
    sprite->callback = ShrubEntranceSpriteCallback2;
}

static void ShrubEntranceSpriteCallback2(struct Sprite *sprite)
{
    if (sprite->data[0] < 40)
    {
        sprite->data[0]++;

        if (sprite->data[0] == 20)
            sub_80E8D4C();
    }
    else
    {
        sprite->data[0] = 0;
        sprite->callback = ShrubEntranceSpriteCallbackEnd;
    }
}

static void ShrubEntranceSpriteCallbackEnd(struct Sprite *sprite)
{
    FieldEffectStop(sprite, FLDEFF_SECRET_POWER_SHRUB);
    EnableBothScriptContexts();
}

bool8 FldEff_SecretBasePCTurnOn(void)
{
    s16 x, y;
    u8 taskId;

    GetXYCoordsOneStepInFrontOfPlayer(&x, &y);

    taskId = CreateTask(Task_SecretBasePCTurnOn, 0);
    gTasks[taskId].data[0] = x;
    gTasks[taskId].data[1] = y;
    gTasks[taskId].data[2] = 0;

    return FALSE;
}

static void Task_SecretBasePCTurnOn(u8 taskId)
{
    s16 *data = gTasks[taskId].data;

    switch (data[2])
    {
    case 4:
    case 12:
        MapGridSetMetatileIdAt(data[0], data[1], 548);
        CurrentMapDrawMetatileAt(data[0], data[1]);
        break;
    case 8:
    case 16:
        MapGridSetMetatileIdAt(data[0], data[1], 544);
        CurrentMapDrawMetatileAt(data[0], data[1]);
        break;
    case 20:
        MapGridSetMetatileIdAt(data[0], data[1], 548);
        CurrentMapDrawMetatileAt(data[0], data[1]);
        FieldEffectActiveListRemove(FLDEFF_PCTURN_ON);
        EnableBothScriptContexts();
        DestroyTask(taskId);
        return;
    }

    data[2]++;
}

void DoSecretBasePCTurnOffEffect(void)
{
    s16 x, y;

    GetXYCoordsOneStepInFrontOfPlayer(&x, &y);
    PlaySE(SE_PC_OFF);

    if (!VarGet(VAR_CURRENT_SECRET_BASE))
        MapGridSetMetatileIdAt(x, y, 3616);
    else
        MapGridSetMetatileIdAt(x, y, 3617);

    CurrentMapDrawMetatileAt(x, y);
}

void PopSecretBaseBalloon(s16 metatileId, s16 x, s16 y)
{
    u8 taskId = CreateTask(Task_PopSecretBaseBalloon, 0);

    gTasks[taskId].data[0] = metatileId;
    gTasks[taskId].data[1] = x;
    gTasks[taskId].data[2] = y;
    gTasks[taskId].data[3] = 0;
    gTasks[taskId].data[4] = 1;
}

static void Task_PopSecretBaseBalloon(u8 taskId)
{
    s16 *data = gTasks[taskId].data;

    if (data[3] == 6)
        data[3] = 0;
    else
        data[3]++;

    if (data[3] == 0)
    {
        if (data[4] == 2)
            DoBalloonSoundEffect(data[0]);

        MapGridSetMetatileIdAt(data[1], data[2], data[0] + data[4]);
        CurrentMapDrawMetatileAt(data[1], data[2]);

        if (data[4] == 3)
            DestroyTask(taskId);
        else
            data[4]++;
    }
}

static void DoBalloonSoundEffect(s16 metatileId)
{
    switch (metatileId)
    {
    case 824:
        PlaySE(SE_FUUSEN1);
        break;
    case 828:
        PlaySE(SE_FUUSEN2);
        break;
    case 832:
        PlaySE(SE_FUUSEN3);
        break;
    case 552:
        PlaySE(SE_TOY_DANGO);
        break;
    }
}

bool8 FldEff_NopA6FC(void)
{
    return FALSE;
}

bool8 FldEff_NopA700(void)
{
    return FALSE;
}

static void DoSecretBaseBreakableDoorEffect(s16 x, s16 y)
{
    PlaySE(SE_TOY_KABE);
    MapGridSetMetatileIdAt(x, y, 630);
    MapGridSetMetatileIdAt(x, y - 1, 622);
    CurrentMapDrawMetatileAt(x, y);
    CurrentMapDrawMetatileAt(x, y - 1);
}

static void Task_ShatterSecretBaseBreakableDoor(u8 taskId)
{
    if (gTasks[taskId].data[0] == 7)
    {
        DoSecretBaseBreakableDoorEffect(gTasks[taskId].data[1], gTasks[taskId].data[2]);
        DestroyTask(taskId);
    }
    else
    {
        gTasks[taskId].data[0]++;
    }
}

void ShatterSecretBaseBreakableDoor(s16 x, s16 y)
{
    u8 dir = GetPlayerFacingDirection();

    if (dir == DIR_SOUTH)
    {
        DoSecretBaseBreakableDoorEffect(x, y);
    }
    else if (dir == DIR_NORTH)
    {
        u8 taskId = CreateTask(Task_ShatterSecretBaseBreakableDoor, 5);
        gTasks[taskId].data[0] = 0;
        gTasks[taskId].data[1] = x;
        gTasks[taskId].data[2] = y;
    }
}

#define tMetatileID data[0]
static void Task_SecretBaseMusicNoteMatSound(u8 taskId)
{
    if (gTasks[taskId].data[1] == 7)
    {
        switch (gTasks[taskId].tMetatileID)
        {
        case 632:
            PlaySE(SE_TOY_C);
            break;
        case 633:
            PlaySE(SE_TOY_D);
            break;
        case 634:
            PlaySE(SE_TOY_E);
            break;
        case 635:
            PlaySE(SE_TOY_F);
            break;
        case 636:
            PlaySE(SE_TOY_G);
            break;
        case 637:
            PlaySE(SE_TOY_A);
            break;
        case 638:
            PlaySE(SE_TOY_B);
            break;
        case 691:
            PlaySE(SE_TOY_C1);
            break;
        }

        DestroyTask(taskId);
    }
    else
    {
        gTasks[taskId].data[1]++;
    }
}

void PlaySecretBaseMusicNoteMatSound(s16 metatileId)
{
    u8 taskId = CreateTask(Task_SecretBaseMusicNoteMatSound, 5);

    gTasks[taskId].tMetatileID = metatileId;
    gTasks[taskId].data[1] = 0;
}
#undef tMetatileID

static void SpriteCB_GlitterMatSparkle(struct Sprite *sprite)
{
    sprite->data[0]++;

    if (sprite->data[0] == 8)
        PlaySE(SE_W215);

    if (sprite->data[0] >= 32)
        DestroySprite(sprite);
}

void DoSecretBaseGlitterMatSparkle(void)
{
    s16 x = gEventObjects[gPlayerAvatar.eventObjectId].currentCoords.x;
    s16 y = gEventObjects[gPlayerAvatar.eventObjectId].currentCoords.y;
    u8 spriteId;

    sub_80930E0(&x, &y, 8, 4);

    spriteId = CreateSpriteAtEnd(gFieldEffectObjectTemplatePointers[22], x, y, 0);
    if (spriteId != MAX_SPRITES)
    {
        gSprites[spriteId].coordOffsetEnabled = TRUE;
        gSprites[spriteId].oam.priority = 1;
        gSprites[spriteId].oam.paletteNum = 5;
        gSprites[spriteId].callback = SpriteCB_GlitterMatSparkle;
        gSprites[spriteId].data[0] = 0;
    }
}

bool8 FldEff_SandPillar(void)
{
    s16 x, y;

    ScriptContext2_Enable();
    GetXYCoordsOneStepInFrontOfPlayer(&x, &y);

    gFieldEffectArguments[5] = x;
    gFieldEffectArguments[6] = y;

    switch (GetPlayerFacingDirection())
    {
    case DIR_SOUTH:
        CreateSprite(&gUnknown_0858E68C,
                     gSprites[gPlayerAvatar.spriteId].oam.x + 8,
                     gSprites[gPlayerAvatar.spriteId].oam.y + 32,
                     0);

        break;

    case DIR_NORTH:
        CreateSprite(&gUnknown_0858E68C,
                     gSprites[gPlayerAvatar.spriteId].oam.x + 8,
                     gSprites[gPlayerAvatar.spriteId].oam.y,
                     148);

        break;

    case DIR_WEST:
        CreateSprite(&gUnknown_0858E68C,
                     gSprites[gPlayerAvatar.spriteId].oam.x - 8,
                     gSprites[gPlayerAvatar.spriteId].oam.y + 16,
                     148);

        break;

    case DIR_EAST:
        CreateSprite(&gUnknown_0858E68C,
                     gSprites[gPlayerAvatar.spriteId].oam.x + 24,
                     gSprites[gPlayerAvatar.spriteId].oam.y + 16,
                     148);

        break;
    }

    return FALSE;
}

static void SpriteCB_SandPillar_0(struct Sprite *sprite)
{
    PlaySE(SE_W088);

    if (MapGridGetMetatileIdAt(gFieldEffectArguments[5], gFieldEffectArguments[6] - 1) == 646)
        MapGridSetMetatileIdAt(gFieldEffectArguments[5], gFieldEffectArguments[6] - 1, 3586);
    else
        MapGridSetMetatileIdAt(gFieldEffectArguments[5], gFieldEffectArguments[6] - 1, 644);

    MapGridSetMetatileIdAt(gFieldEffectArguments[5], gFieldEffectArguments[6], 522);
    CurrentMapDrawMetatileAt(gFieldEffectArguments[5], gFieldEffectArguments[6] - 1);
    CurrentMapDrawMetatileAt(gFieldEffectArguments[5], gFieldEffectArguments[6]);

    sprite->data[0] = 0;
    sprite->callback = SpriteCB_SandPillar_1;
}

static void SpriteCB_SandPillar_1(struct Sprite *sprite)
{
    if (sprite->data[0] < 18)
    {
        sprite->data[0]++;
    }
    else
    {
        MapGridSetMetatileIdAt(gFieldEffectArguments[5], gFieldEffectArguments[6], 3724);
        CurrentMapDrawMetatileAt(gFieldEffectArguments[5], gFieldEffectArguments[6]);
        sprite->data[0] = 0;
        sprite->callback = SpriteCB_SandPillar_2;
    }
}

static void SpriteCB_SandPillar_2(struct Sprite *sprite)
{
    FieldEffectStop(sprite, FLDEFF_SAND_PILLAR);
    EnableBothScriptContexts();
}

void GetShieldToyTVDecorationInfo(void)
{
    s16 x, y;
    s32 metatileId;

    GetXYCoordsOneStepInFrontOfPlayer(&x, &y);

    metatileId = MapGridGetMetatileIdAt(x, y);

    switch (metatileId)
    {
    case 822:
        ConvertIntToDecimalStringN(gStringVar1, 100, STR_CONV_MODE_LEFT_ALIGN, 3);
        StringCopy(gStringVar2, gText_Gold);

        gSpecialVar_Result = 0;

        if (!VarGet(VAR_CURRENT_SECRET_BASE))
            return;

        VarSet(VAR_0x40EE, VarGet(VAR_0x40EE) | 0x10);
        break;
    case 734:
        ConvertIntToDecimalStringN(gStringVar1, 50, STR_CONV_MODE_LEFT_ALIGN, 2);
        StringCopy(gStringVar2, gText_Silver);

        gSpecialVar_Result = 0;

        if (!VarGet(VAR_CURRENT_SECRET_BASE))
            return;

        VarSet(VAR_0x40EE, VarGet(VAR_0x40EE) | 0x20);
        break;
    case 756:
        gSpecialVar_Result = 1;

        if (!VarGet(VAR_CURRENT_SECRET_BASE))
            return;

        VarSet(VAR_0x40EE, VarGet(VAR_0x40EE) | 0x80);
        break;
    case 757:
        gSpecialVar_Result = 2;

        if (!VarGet(VAR_CURRENT_SECRET_BASE))
            return;

        VarSet(VAR_0x40EE, VarGet(VAR_0x40EE) | 0x80);
        break;
    case 758:
        gSpecialVar_Result = 3;

        if (!VarGet(VAR_CURRENT_SECRET_BASE))
            return;

        VarSet(VAR_0x40EE, VarGet(VAR_0x40EE) | 0x80);
        break;
    }
}

bool8 sub_80FADE4(u16 metatileId, u8 arg1)
{
    if (!CurrentMapIsSecretBase())
        return FALSE;

    if (!arg1)
    {
        if (metatileId == 0x285 || metatileId == 0x286)
            return TRUE;
        if (metatileId == 0x237)
            return TRUE;
    }
    else
    {
        if (metatileId == 0x28d)
            return TRUE;
        if (metatileId == 0x23F)
            return TRUE;
    }

    return FALSE;
}

static void Task_FieldPoisonEffect(u8 taskId)
{
    s16 *data = gTasks[taskId].data;

    switch (data[0])
    {
    case 0:
        data[1] += 2;
        if (data[1] > 8)
            data[0]++;
        break;
    case 1:
        data[1] -= 2;
        if (data[1] == 0)
            data[0]++;
        break;
    case 2:
        DestroyTask(taskId);
        return;
    }
    SetGpuReg(REG_OFFSET_MOSAIC, (data[1] << 4) | data[1]);
}

void FldEffPoison_Start(void)
{
    PlaySE(SE_DOKU);
    CreateTask(Task_FieldPoisonEffect, 80);
}

bool32 FldEffPoison_IsActive(void)
{
    return FuncIsActiveTask(Task_FieldPoisonEffect);
}

static void Task_WateringBerryTreeAnim_0(u8 taskId)
{
    gTasks[taskId].func = Task_WateringBerryTreeAnim_1;
}

static void Task_WateringBerryTreeAnim_1(u8 taskId)
{
    struct EventObject *playerEventObj = &gEventObjects[gPlayerAvatar.eventObjectId];

    if (!EventObjectIsMovementOverridden(playerEventObj)
        || EventObjectClearHeldMovementIfFinished(playerEventObj))
    {
        sub_808C228(GetPlayerFacingDirection());
        EventObjectSetHeldMovement(playerEventObj, GetWalkInPlaceNormalMovementAction(GetPlayerFacingDirection()));
        gTasks[taskId].func = Task_WateringBerryTreeAnim_2;
    }
}

static void Task_WateringBerryTreeAnim_2(u8 taskId)
{
    struct EventObject *playerEventObj = &gEventObjects[gPlayerAvatar.eventObjectId];

    if (EventObjectClearHeldMovementIfFinished(playerEventObj))
    {
        s16 value = gTasks[taskId].data[1]++;

        if (value < 10)
            EventObjectSetHeldMovement(playerEventObj, GetWalkInPlaceNormalMovementAction(GetPlayerFacingDirection()));

        else
            gTasks[taskId].func = Task_WateringBerryTreeAnim_3;
    }
}

static void Task_WateringBerryTreeAnim_3(u8 taskId)
{
    SetPlayerAvatarTransitionFlags(sub_808BCD0());
    DestroyTask(taskId);
    EnableBothScriptContexts();
}

void DoWateringBerryTreeAnim(void)
{
    CreateTask(Task_WateringBerryTreeAnim_0, 80);
}

u8 CreateRecordMixingSprite(void)
{
    u8 spriteId;

    LoadSpritePalette(&gUnknown_0858E864);

    spriteId = CreateSprite(&gUnknown_0858E880, 0, 0, 82);

    if (spriteId == MAX_SPRITES)
    {
        return MAX_SPRITES;
    }
    else
    {
        struct Sprite *sprite = &gSprites[spriteId];
        sub_8092FF0(16, 13, &sprite->pos1.x, &sprite->pos1.y);
        sprite->coordOffsetEnabled = TRUE;
        sprite->pos1.x += 16;
        sprite->pos1.y += 2;
    }
    return spriteId;
}

void DestroyRecordMixingSprite(void)
{
    int i;

    for (i = 0; i < MAX_SPRITES; i++)
    {
        if (gSprites[i].template == &gUnknown_0858E880)
        {
            FreeSpritePalette(&gSprites[i]);
            DestroySprite(&gSprites[i]);
        }
    }
}
