#include "global.h"
#include "battle.h"
#include "battle_controllers.h"
#include "battle_ai_script_commands.h"
#include "battle_anim.h"
#include "battle_interface.h"
#include "main.h"
#include "malloc.h"
#include "rng.h"
#include "util.h"
#include "pokemon.h"
#include "moves.h"
#include "task.h"
#include "sprite.h"
#include "sound.h"
#include "m4a.h"
#include "species.h"
#include "decompress.h"
#include "data2.h"
#include "palette.h"
#include "blend_palette.h"
#include "contest.h"
#include "songs.h"

extern u8 gBattleBufferA[BATTLE_BANKS_COUNT][0x200];
extern u8 gActiveBank;
extern u8 gNoOfAllBanks;
extern u16 gUnknown_020243FC;
extern u16 gBattlePartyID[BATTLE_BANKS_COUNT];
extern struct BattlePokemon gBattleMons[BATTLE_BANKS_COUNT];
extern u8 gBanksByIdentity[BATTLE_BANKS_COUNT];
extern u8 gBankSpriteIds[BATTLE_BANKS_COUNT];
extern u8 gHealthBoxesIds[BATTLE_BANKS_COUNT];
extern u8 gBattleMonForms[BATTLE_BANKS_COUNT];
extern u32 gTransformedPersonalities[BATTLE_BANKS_COUNT];
extern struct MusicPlayerInfo gMPlay_SE1;
extern struct MusicPlayerInfo gMPlay_SE2;
extern struct MusicPlayerInfo gMPlay_BGM;

extern const struct BattleMove gBattleMoves[];
extern const u8 gUnknown_0831C604[];
extern const u8 * const gUnknown_082C9320[];
extern const u8 * const gUnknown_082C937C[];
extern const struct CompressedSpriteSheet gMonFrontPicTable[];
extern const struct CompressedSpriteSheet gMonBackPicTable[];
extern const struct CompressedSpriteSheet gTrainerFrontPicTable[];
extern const struct CompressedSpriteSheet gTrainerBackPicTable[];
extern const struct CompressedSpritePalette gTrainerFrontPicPaletteTable[];
extern const struct CompressedSpritePalette gTrainerBackPicPaletteTable[];
extern const union AnimCmd* const * const gMonAnimationsSpriteAnimsPtrTable[];
extern const struct SpriteTemplate gUnknown_08329D98[4];
extern const struct CompressedSpriteSheet gSpriteSheet_EnemyShadow;
extern const struct SpriteTemplate gSpriteTemplate_EnemyShadow;
extern const u8 gEnemyMonElevation[];

// graphics
extern const u32 gUnknown_08C093F0[];
extern const u32 gSubstituteDollTilemap[];
extern const u32 gSubstituteDollGfx[];
extern const u16 gSubstituteDollPal[];
extern const u8 gUnknown_08C1F1C8[];
extern const u8 gUnknown_08C1F46C[];
extern const u8 gUnknown_08C1F5E8[];
extern const u8 gUnknown_08C1F76C[];
extern const u8 gUnknown_08C1F8E8[];
extern const u8 gUnknown_08C0237C[];
extern const u16 gBattleInterface_BallStatusBarPal[];
extern const u16 gBattleInterface_BallDisplayPal[];

extern u8 sub_80688F8(u8, u8 bank);
extern u8 pokemon_order_func(u8); // party menu
extern void sub_81B8C68(void);

// this file's functions
static u8 sub_805D4A8(u16 move);
static u16 BattlePalaceGetTargetRetValue(void);
static void sub_805D7EC(struct Sprite *sprite);
static bool8 ShouldAnimBeDoneRegardlessOfSubsitute(u8 animId);
static void Task_ClearBitWhenBattleTableAnimDone(u8 taskId);
static void Task_ClearBitWhenSpecialAnimDone(u8 taskId);
static void ClearSpritesBankHealthboxAnimData(void);

// const rom data
static const struct CompressedSpriteSheet gUnknown_0832C0D0 =
{
    gUnknown_08C1F1C8, 0x1000, TAG_HEALTHBOX_PLAYER1_TILE
};

static const struct CompressedSpriteSheet gUnknown_0832C0D8 =
{
    gUnknown_08C1F46C, 0x1000, TAG_HEALTHBOX_OPPONENT1_TILE
};

static const struct CompressedSpriteSheet gUnknown_0832C0E0[2] =
{
    {gUnknown_08C1F5E8, 0x800, TAG_HEALTHBOX_PLAYER1_TILE},
    {gUnknown_08C1F5E8, 0x800, TAG_HEALTHBOX_PLAYER2_TILE}
};

static const struct CompressedSpriteSheet gUnknown_0832C0F0[2] =
{
    {gUnknown_08C1F76C, 0x800, TAG_HEALTHBOX_OPPONENT1_TILE},
    {gUnknown_08C1F76C, 0x800, TAG_HEALTHBOX_OPPONENT2_TILE}
};

static const struct CompressedSpriteSheet gUnknown_0832C100 =
{
    gUnknown_08C1F8E8, 0x1000, TAG_HEALTHBOX_SAFARI_TILE
};

static const struct CompressedSpriteSheet gUnknown_0832C108[BATTLE_BANKS_COUNT] =
{
    {gUnknown_08C0237C, 0x0100, 0xd704},
    {gUnknown_08C0237C, 0x0120, 0xd705},
    {gUnknown_08C0237C, 0x0100, 0xd706},
    {gUnknown_08C0237C, 0x0120, 0xd707}
};

static const struct SpritePalette gUnknown_0832C128[2] =
{
    {gBattleInterface_BallStatusBarPal, TAG_HEALTHBOX_PAL},
    {gBattleInterface_BallDisplayPal, 0xd704}
};

// code
void AllocateBattleSpritesData(void)
{
    gBattleSpritesDataPtr = AllocZeroed(sizeof(struct BattleSpriteData));
    gBattleSpritesDataPtr->bankData = AllocZeroed(sizeof(struct BattleSpriteInfo) * BATTLE_BANKS_COUNT);
    gBattleSpritesDataPtr->healthBoxesData = AllocZeroed(sizeof(struct BattleHealthboxInfo) * BATTLE_BANKS_COUNT);
    gBattleSpritesDataPtr->animationData = AllocZeroed(sizeof(struct BattleAnimationInfo));
    gBattleSpritesDataPtr->battleBars = AllocZeroed(sizeof(struct BattleBarInfo) * BATTLE_BANKS_COUNT);
}

void FreeBattleSpritesData(void)
{
    if (gBattleSpritesDataPtr == NULL)
        return;

    FREE_AND_SET_NULL(gBattleSpritesDataPtr->battleBars);
    FREE_AND_SET_NULL(gBattleSpritesDataPtr->animationData);
    FREE_AND_SET_NULL(gBattleSpritesDataPtr->healthBoxesData);
    FREE_AND_SET_NULL(gBattleSpritesDataPtr->bankData);
    FREE_AND_SET_NULL(gBattleSpritesDataPtr);
}

u16 ChooseMoveAndTargetInBattlePalace(void)
{
    s32 i, var1, var2;
    s32 chosenMoveId = -1;
    struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleBufferA[gActiveBank][4]);
    u8 unusableMovesBits = CheckMoveLimitations(gActiveBank, 0, 0xFF);
    s32 percent = Random() % 100;

    i = (gBattleStruct->field_92 & gBitTable[gActiveBank]) ? 2 : 0;
    var2 = i;
    var1 = i + 2;

    for (; i < var1; i++)
    {
        if (gUnknown_0831C494[GetNatureFromPersonality(gBattleMons[gActiveBank].personality)][i] > percent)
            break;
    }

    percent = i - var2;
    if (i == var1)
        percent = 2;

    for (var2 = 0, i = 0; i < 4; i++)
    {
        if (moveInfo->moves[i] == MOVE_NONE)
            break;
        if (percent == sub_805D4A8(moveInfo->moves[i]) && moveInfo->currentPp[i] != 0)
            var2 |= gBitTable[i];
    }

    if (var2 != 0)
    {
        gBattleStruct->field_92 &= 0xF;
        gBattleStruct->field_92 |= (var2 << 4);
        BattleAI_SetupAIData(var2);
        chosenMoveId = BattleAI_ChooseMoveOrAction();
    }

    if (chosenMoveId == -1)
    {
        if (unusableMovesBits != 0xF)
        {
            var1 = 0, var2 = 0;

            for (i = 0; i < 4; i++)
            {
                if (sub_805D4A8(moveInfo->moves[i]) == 0 && !(gBitTable[i] & unusableMovesBits))
                    var1 += 0x1;
                if (sub_805D4A8(moveInfo->moves[i]) == 1 && !(gBitTable[i] & unusableMovesBits))
                    var1 += 0x10;
                if (sub_805D4A8(moveInfo->moves[i]) == 2 && !(gBitTable[i] & unusableMovesBits))
                    var1 += 0x100;
            }

            if ((var1 & 0xF) > 1)
                var2++;
            if ((var1 & 0xF0) > 0x1F)
                var2++;
            if ((var1 & 0xF0) > 0x1FF)
                var2++;

            if (var2 > 1 || var2 == 0)
            {
                do
                {
                    i = Random() % 4;
                    if (!(gBitTable[i] & unusableMovesBits))
                        chosenMoveId = i;
                } while (chosenMoveId == -1);
            }
            else
            {
                if ((var1 & 0xF) > 1)
                    var2 = 0;
                if ((var1 & 0xF0) > 0x1F)
                    var2 = 1;
                if ((var1 & 0xF0) > 0x1FF)
                    var2 = 2;

                do
                {
                    i = Random() % 4;
                    if (!(gBitTable[i] & unusableMovesBits) && var2 == sub_805D4A8(moveInfo->moves[i]))
                        chosenMoveId = i;
                } while (chosenMoveId == -1);
            }

            if (Random() % 100 > 49)
            {
                gProtectStructs[gActiveBank].flag_x10 = 1;
                return 0;
            }
        }
        else
        {
            gProtectStructs[gActiveBank].flag_x10 = 1;
            return 0;
        }
    }

    if (moveInfo->moves[chosenMoveId] == MOVE_CURSE)
    {
        if (moveInfo->monType1 != TYPE_GHOST && moveInfo->monType2 != TYPE_GHOST)
            var1 = MOVE_TARGET_x10;
        else
            var1 = MOVE_TARGET_SELECTED;
    }
    else
    {
        var1 = gBattleMoves[moveInfo->moves[chosenMoveId]].target;
    }

    if (var1 & MOVE_TARGET_x10)
        chosenMoveId |= (gActiveBank << 8);
    else if (var1 == MOVE_TARGET_SELECTED)
        chosenMoveId |= (BattlePalaceGetTargetRetValue());
    else
        chosenMoveId |= (GetBankByIdentity((GetBankIdentity(gActiveBank) & BIT_SIDE) ^ BIT_SIDE) << 8);

    return chosenMoveId;
}

static u8 sub_805D4A8(u16 move)
{
    switch (gBattleMoves[move].target)
    {
    case MOVE_TARGET_SELECTED:
    case MOVE_TARGET_USER:
    case MOVE_TARGET_RANDOM:
    case MOVE_TARGET_BOTH:
    case MOVE_TARGET_FOES_AND_ALLY:
        if (gBattleMoves[move].power == 0)
            return 2;
        else
            return 0;
        break;
    case MOVE_TARGET_DEPENDS:
    case MOVE_TARGET_OPPONENTS_FIELD:
        return 2;
    case MOVE_TARGET_x10:
        return 1;
    default:
        return 0;
    }
}

static u16 BattlePalaceGetTargetRetValue(void)
{
    if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
    {
        u8 opposing1, opposing2;

        if (GetBankSide(gActiveBank) == SIDE_PLAYER)
        {
            opposing1 = GetBankByIdentity(IDENTITY_OPPONENT_MON1);
            opposing2 = GetBankByIdentity(IDENTITY_OPPONENT_MON2);
        }
        else
        {
            opposing1 = GetBankByIdentity(IDENTITY_PLAYER_MON1);
            opposing2 = GetBankByIdentity(IDENTITY_PLAYER_MON2);
        }

        if (gBattleMons[opposing1].hp == gBattleMons[opposing2].hp)
            return (((gActiveBank & BIT_SIDE) ^ BIT_SIDE) + (Random() & 2)) << 8;

        switch (gUnknown_0831C604[GetNatureFromPersonality(gBattleMons[gActiveBank].personality)])
        {
        case 0:
            if (gBattleMons[opposing1].hp > gBattleMons[opposing2].hp)
                return opposing1 << 8;
            else
                return opposing2 << 8;
        case 1:
            if (gBattleMons[opposing1].hp < gBattleMons[opposing2].hp)
                return opposing1 << 8;
            else
                return opposing2 << 8;
        case 2:
            return (((gActiveBank & BIT_SIDE) ^ BIT_SIDE) + (Random() & 2)) << 8;
        }
    }

    return (gActiveBank ^ BIT_SIDE) << 8;
}

void sub_805D714(struct Sprite *sprite)
{
    u8 spriteId = sprite->data1;

    if (!gSprites[spriteId].affineAnimEnded)
        return;
    if (gSprites[spriteId].invisible)
        return;

    if (gSprites[spriteId].animPaused)
    {
        gSprites[spriteId].animPaused = 0;
    }
    else
    {
        if (gSprites[spriteId].animEnded)
            sprite->callback = SpriteCallbackDummy;
    }
}

void sub_805D770(struct Sprite *sprite, bool8 arg1)
{
    sprite->animPaused = 1;
    sprite->callback = SpriteCallbackDummy;

    if (!arg1)
        StartSpriteAffineAnim(sprite, 1);
    else
        StartSpriteAffineAnim(sprite, 1);

    AnimateSprite(sprite);
}

void sub_805D7AC(struct Sprite *sprite)
{
    if (!(gUnknown_020243FC & 1))
    {
        sprite->pos2.x += sprite->data0;
        if (sprite->pos2.x == 0)
        {
            if (sprite->pos2.y != 0)
                sprite->callback = sub_805D7EC;
            else
                sprite->callback = SpriteCallbackDummy;
        }
    }
}

static void sub_805D7EC(struct Sprite *sprite)
{
    sprite->pos2.y -= 2;
    if (sprite->pos2.y == 0)
        sprite->callback = SpriteCallbackDummy;
}

void InitAndLaunchChosenStatusAnimation(bool8 isStatus2, u32 status)
{
    gBattleSpritesDataPtr->healthBoxesData[gActiveBank].statusAnimActive = 1;
    if (!isStatus2)
    {
        if (status == STATUS_FREEZE)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_FRZ);
        else if (status == STATUS_POISON || status & STATUS_TOXIC_POISON)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_PSN);
        else if (status == STATUS_BURN)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_BRN);
        else if (status & STATUS_SLEEP)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_SLP);
        else if (status == STATUS_PARALYSIS)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_PRZ);
        else // no animation
            gBattleSpritesDataPtr->healthBoxesData[gActiveBank].statusAnimActive = 0;
    }
    else
    {
        if (status & STATUS2_INFATUATION)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_INFATUATION);
        else if (status & STATUS2_CONFUSION)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_CONFUSION);
        else if (status & STATUS2_CURSED)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_CURSED);
        else if (status & STATUS2_NIGHTMARE)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_NIGHTMARE);
        else if (status & STATUS2_WRAPPED)
            LaunchStatusAnimation(gActiveBank, B_ANIM_STATUS_WRAPPED);
        else // no animation
            gBattleSpritesDataPtr->healthBoxesData[gActiveBank].statusAnimActive = 0;
    }
}

#define tBank data[0]

bool8 TryHandleLaunchBattleTableAnimation(u8 activeBank, u8 atkBank, u8 defBank, u8 tableId, u16 argument)
{
    u8 taskId;

    if (tableId == B_ANIM_CASTFORM_CHANGE && (argument & 0x80))
    {
        gBattleMonForms[activeBank] = (argument & ~(0x80));
        return TRUE;
    }
    if (gBattleSpritesDataPtr->bankData[activeBank].behindSubstitute
        && !ShouldAnimBeDoneRegardlessOfSubsitute(tableId))
    {
        return TRUE;
    }
    if (gBattleSpritesDataPtr->bankData[activeBank].behindSubstitute
        && tableId == B_ANIM_SUBSTITUTE_FADE
        && gSprites[gBankSpriteIds[activeBank]].invisible)
    {
        LoadBattleMonGfxAndAnimate(activeBank, TRUE, gBankSpriteIds[activeBank]);
        ClearBehindSubstituteBit(activeBank);
        return TRUE;
    }

    gAnimBankAttacker = atkBank;
    gAnimBankTarget = defBank;
    gBattleSpritesDataPtr->animationData->animArg = argument;
    LaunchBattleAnimation(gUnknown_082C9320, tableId, FALSE);
    taskId = CreateTask(Task_ClearBitWhenBattleTableAnimDone, 10);
    gTasks[taskId].tBank = activeBank;
    gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBank].animFromTableActive = 1;

    return FALSE;
}

static void Task_ClearBitWhenBattleTableAnimDone(u8 taskId)
{
    gAnimScriptCallback();
    if (!gAnimScriptActive)
    {
        gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBank].animFromTableActive = 0;
        DestroyTask(taskId);
    }
}

#undef tBank

static bool8 ShouldAnimBeDoneRegardlessOfSubsitute(u8 animId)
{
    switch (animId)
    {
    case B_ANIM_SUBSTITUTE_FADE:
    case B_ANIM_RAIN_CONTINUES:
    case B_ANIM_SUN_CONTINUES:
    case B_ANIM_SANDSTORM_CONTINUES:
    case B_ANIM_HAIL_CONTINUES:
    case B_ANIM_SNATCH_MOVE:
        return TRUE;
    default:
        return FALSE;
    }
}

#define tBank data[0]

void InitAndLaunchSpecialAnimation(u8 activeBank, u8 atkBank, u8 defBank, u8 tableId)
{
    u8 taskId;

    gAnimBankAttacker = atkBank;
    gAnimBankTarget = defBank;
    LaunchBattleAnimation(gUnknown_082C937C, tableId, FALSE);
    taskId = CreateTask(Task_ClearBitWhenSpecialAnimDone, 10);
    gTasks[taskId].tBank = activeBank;
    gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBank].specialAnimActive = 1;
}

static void Task_ClearBitWhenSpecialAnimDone(u8 taskId)
{
    gAnimScriptCallback();
    if (!gAnimScriptActive)
    {
        gBattleSpritesDataPtr->healthBoxesData[gTasks[taskId].tBank].specialAnimActive = 0;
        DestroyTask(taskId);
    }
}

#undef tBank

// great function to include newly added moves that don't have animation yet
bool8 IsMoveWithoutAnimation(u16 moveId, u8 animationTurn)
{
    return FALSE;
}

bool8 mplay_80342A4(u8 bank)
{
    u8 zero = 0;

    if (IsSEPlaying())
    {
        gBattleSpritesDataPtr->healthBoxesData[bank].field_8++;
        if (gBattleSpritesDataPtr->healthBoxesData[gActiveBank].field_8 < 30)
            return TRUE;

        m4aMPlayStop(&gMPlay_SE1);
        m4aMPlayStop(&gMPlay_SE2);
    }
    if (zero == 0)
    {
        gBattleSpritesDataPtr->healthBoxesData[bank].field_8 = 0;
        return FALSE;
    }

    return TRUE;
}

void BattleLoadOpponentMonSpriteGfx(struct Pokemon *mon, u8 bank)
{
    u32 monsPersonality, currentPersonality, otId;
    u16 species;
    u8 identity;
    u16 paletteOffset;
    const void *lzPaletteData;

    monsPersonality = GetMonData(mon, MON_DATA_PERSONALITY);

    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies == SPECIES_NONE)
    {
        species = GetMonData(mon, MON_DATA_SPECIES);
        currentPersonality = monsPersonality;
    }
    else
    {
        species = gBattleSpritesDataPtr->bankData[bank].transformSpecies;
        currentPersonality = gTransformedPersonalities[bank];
    }

    otId = GetMonData(mon, MON_DATA_OT_ID);
    identity = GetBankIdentity(bank);
    HandleLoadSpecialPokePic_DontHandleDeoxys(&gMonFrontPicTable[species],
                                              gMonSpritesGfxPtr->sprites[identity],
                                              species, currentPersonality);

    paletteOffset = 0x100 + bank * 16;

    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies == SPECIES_NONE)
        lzPaletteData = GetMonFrontSpritePal(mon);
    else
        lzPaletteData = GetFrontSpritePalFromSpeciesAndPersonality(species, otId, monsPersonality);

    LZDecompressWram(lzPaletteData, gDecompressionBuffer);
    LoadPalette(gDecompressionBuffer, paletteOffset, 0x20);
    LoadPalette(gDecompressionBuffer, 0x80 + bank * 16, 0x20);

    if (species == SPECIES_CASTFORM)
    {
        paletteOffset = 0x100 + bank * 16;
        LZDecompressWram(lzPaletteData, gBattleStruct->castformPalette[0]);
        LoadPalette(gBattleStruct->castformPalette[gBattleMonForms[bank]], paletteOffset, 0x20);
    }

    // transform's pink color
    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies != SPECIES_NONE)
    {
        BlendPalette(paletteOffset, 16, 6, 0x7FFF);
        CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, 32);
    }
}

void BattleLoadPlayerMonSpriteGfx(struct Pokemon *mon, u8 bank)
{
    u32 monsPersonality, currentPersonality, otId;
    u16 species;
    u8 identity;
    u16 paletteOffset;
    const void *lzPaletteData;

    monsPersonality = GetMonData(mon, MON_DATA_PERSONALITY);

    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies == SPECIES_NONE)
    {
        species = GetMonData(mon, MON_DATA_SPECIES);
        currentPersonality = monsPersonality;
    }
    else
    {
        species = gBattleSpritesDataPtr->bankData[bank].transformSpecies;
        currentPersonality = gTransformedPersonalities[bank];
    }

    otId = GetMonData(mon, MON_DATA_OT_ID);
    identity = GetBankIdentity(bank);

    if (sub_80688F8(1, bank) == 1 || gBattleSpritesDataPtr->bankData[bank].transformSpecies != SPECIES_NONE)
    {
        HandleLoadSpecialPokePic_DontHandleDeoxys(&gMonBackPicTable[species],
                                                  gMonSpritesGfxPtr->sprites[identity],
                                                  species, currentPersonality);
    }
    else
    {
        HandleLoadSpecialPokePic(&gMonBackPicTable[species],
                                gMonSpritesGfxPtr->sprites[identity],
                                species, currentPersonality);
    }

    paletteOffset = 0x100 + bank * 16;

    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies == SPECIES_NONE)
        lzPaletteData = GetMonFrontSpritePal(mon);
    else
        lzPaletteData = GetFrontSpritePalFromSpeciesAndPersonality(species, otId, monsPersonality);

    LZDecompressWram(lzPaletteData, gDecompressionBuffer);
    LoadPalette(gDecompressionBuffer, paletteOffset, 0x20);
    LoadPalette(gDecompressionBuffer, 0x80 + bank * 16, 0x20);

    if (species == SPECIES_CASTFORM)
    {
        paletteOffset = 0x100 + bank * 16;
        LZDecompressWram(lzPaletteData, gBattleStruct->castformPalette[0]);
        LoadPalette(gBattleStruct->castformPalette[gBattleMonForms[bank]], paletteOffset, 0x20);
    }

    // transform's pink color
    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies != SPECIES_NONE)
    {
        BlendPalette(paletteOffset, 16, 6, 0x7FFF);
        CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, 32);
    }
}

void nullsub_23(void)
{
}

void nullsub_24(u16 species)
{
}

void DecompressTrainerFrontPic(u16 frontPicId, u8 bank)
{
    u8 identity = GetBankIdentity(bank);
    DecompressPicFromTable_2(&gTrainerFrontPicTable[frontPicId],
                             gMonSpritesGfxPtr->sprites[identity],
                             SPECIES_NONE);
    LoadCompressedObjectPalette(&gTrainerFrontPicPaletteTable[frontPicId]);
}

void DecompressTrainerBackPic(u16 backPicId, u8 bank)
{
    u8 identity = GetBankIdentity(bank);
    DecompressPicFromTable_2(&gTrainerBackPicTable[backPicId],
                             gMonSpritesGfxPtr->sprites[identity],
                             SPECIES_NONE);
    LoadCompressedPalette(gTrainerBackPicPaletteTable[backPicId].data,
                          0x100 + 16 * bank, 0x20);
}

void nullsub_25(u8 arg0)
{
}

void FreeTrainerFrontPicPalette(u16 frontPicId)
{
    FreeSpritePaletteByTag(gTrainerFrontPicPaletteTable[frontPicId].tag);
}

void sub_805DFFC(void)
{
    u8 numberOfBanks = 0;
    u8 i;

    LoadSpritePalette(&gUnknown_0832C128[0]);
    LoadSpritePalette(&gUnknown_0832C128[1]);
    if (!IsDoubleBattle())
    {
        LoadCompressedObjectPic(&gUnknown_0832C0D0);
        LoadCompressedObjectPic(&gUnknown_0832C0D8);
        numberOfBanks = 2;
    }
    else
    {
        LoadCompressedObjectPic(&gUnknown_0832C0E0[0]);
        LoadCompressedObjectPic(&gUnknown_0832C0E0[1]);
        LoadCompressedObjectPic(&gUnknown_0832C0F0[0]);
        LoadCompressedObjectPic(&gUnknown_0832C0F0[1]);
        numberOfBanks = 4;
    }
    for (i = 0; i < numberOfBanks; i++)
        LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[i]]);
}

bool8 BattleLoadAllHealthBoxesGfx(u8 state)
{
    bool8 retVal = FALSE;

    if (state != 0)
    {
        if (state == 1)
        {
            LoadSpritePalette(&gUnknown_0832C128[0]);
            LoadSpritePalette(&gUnknown_0832C128[1]);
        }
        else if (!IsDoubleBattle())
        {
            if (state == 2)
            {
                if (gBattleTypeFlags & BATTLE_TYPE_SAFARI)
                    LoadCompressedObjectPic(&gUnknown_0832C100);
                else
                    LoadCompressedObjectPic(&gUnknown_0832C0D0);
            }
            else if (state == 3)
                LoadCompressedObjectPic(&gUnknown_0832C0D8);
            else if (state == 4)
                LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[0]]);
            else if (state == 5)
                LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[1]]);
            else
                retVal = TRUE;
        }
        else
        {
            if (state == 2)
                LoadCompressedObjectPic(&gUnknown_0832C0E0[0]);
            else if (state == 3)
                LoadCompressedObjectPic(&gUnknown_0832C0E0[1]);
            else if (state == 4)
                LoadCompressedObjectPic(&gUnknown_0832C0F0[0]);
            else if (state == 5)
                LoadCompressedObjectPic(&gUnknown_0832C0F0[1]);
            else if (state == 6)
                LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[0]]);
            else if (state == 7)
                LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[1]]);
            else if (state == 8)
                LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[2]]);
            else if (state == 9)
                LoadCompressedObjectPic(&gUnknown_0832C108[gBanksByIdentity[3]]);
            else
                retVal = TRUE;
        }
    }

    return retVal;
}

void LoadBattleBarGfx(u8 arg0)
{
    LZDecompressWram(gUnknown_08C093F0, gMonSpritesGfxPtr->barFontGfx);
}

bool8 BattleInitAllSprites(u8 *state1, u8 *bank)
{
    bool8 retVal = FALSE;

    switch (*state1)
    {
    case 0:
        ClearSpritesBankHealthboxAnimData();
        (*state1)++;
        break;
    case 1:
        if (!BattleLoadAllHealthBoxesGfx(*bank))
        {
            (*bank)++;
        }
        else
        {
            *bank = 0;
            (*state1)++;
        }
        break;
    case 2:
        (*state1)++;
        break;
    case 3:
        if ((gBattleTypeFlags & BATTLE_TYPE_SAFARI) && *bank == 0)
            gHealthBoxesIds[*bank] = CreateSafariPlayerHealthboxSprites();
        else
            gHealthBoxesIds[*bank] = CreateBankHealthboxSprites(*bank);

        (*bank)++;
        if (*bank == gNoOfAllBanks)
        {
            *bank = 0;
            (*state1)++;
        }
        break;
    case 4:
        SetBankHealthboxSpritePos(*bank);
        if (gBanksByIdentity[*bank] <= 1)
            DummyBattleInterfaceFunc(gHealthBoxesIds[*bank], FALSE);
        else
            DummyBattleInterfaceFunc(gHealthBoxesIds[*bank], TRUE);

        (*bank)++;
        if (*bank == gNoOfAllBanks)
        {
            *bank = 0;
            (*state1)++;
        }
        break;
    case 5:
        if (GetBankSide(*bank) == SIDE_PLAYER)
        {
            if (!(gBattleTypeFlags & BATTLE_TYPE_SAFARI))
                UpdateHealthboxAttribute(gHealthBoxesIds[*bank], &gPlayerParty[gBattlePartyID[*bank]], HEALTHBOX_ALL);
        }
        else
        {
            UpdateHealthboxAttribute(gHealthBoxesIds[*bank], &gEnemyParty[gBattlePartyID[*bank]], HEALTHBOX_ALL);
        }
        SetHealthboxSpriteInvisible(gHealthBoxesIds[*bank]);
        (*bank)++;
        if (*bank == gNoOfAllBanks)
        {
            *bank = 0;
            (*state1)++;
        }
        break;
    case 6:
        LoadAndCreateEnemyShadowSprites();
        sub_81B8C68();
        retVal = TRUE;
        break;
    }

    return retVal;
}

void ClearSpritesHealthboxAnimData(void)
{
    memset(gBattleSpritesDataPtr->healthBoxesData, 0, sizeof(struct BattleHealthboxInfo) * BATTLE_BANKS_COUNT);
    memset(gBattleSpritesDataPtr->animationData, 0, sizeof(struct BattleAnimationInfo));
}

static void ClearSpritesBankHealthboxAnimData(void)
{
    ClearSpritesHealthboxAnimData();
    memset(gBattleSpritesDataPtr->bankData, 0, sizeof(struct BattleSpriteInfo) * BATTLE_BANKS_COUNT);
}

void CopyAllBattleSpritesInvisibilities(void)
{
    s32 i;

    for (i = 0; i < gNoOfAllBanks; i++)
        gBattleSpritesDataPtr->bankData[i].invisible = gSprites[gBankSpriteIds[i]].invisible;
}

void CopyBattleSpriteInvisibility(u8 bank)
{
    gBattleSpritesDataPtr->bankData[bank].invisible = gSprites[gBankSpriteIds[bank]].invisible;
}

void HandleSpeciesGfxDataChange(u8 bankAtk, u8 bankDef, bool8 notTransform)
{
    u16 paletteOffset;
    u32 personalityValue;
    u32 otId;
    u8 identity;
    const u8 *lzPaletteData;

    if (notTransform)
    {
        StartSpriteAnim(&gSprites[gBankSpriteIds[bankAtk]], gBattleSpritesDataPtr->animationData->animArg);
        paletteOffset = 0x100 + bankAtk * 16;
        LoadPalette(gBattleStruct->castformPalette[gBattleSpritesDataPtr->animationData->animArg], paletteOffset, 32);
        gBattleMonForms[bankAtk] = gBattleSpritesDataPtr->animationData->animArg;
        if (gBattleSpritesDataPtr->bankData[bankAtk].transformSpecies != SPECIES_NONE)
        {
            BlendPalette(paletteOffset, 16, 6, 0x7FFF);
            CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, 32);
        }
        gSprites[gBankSpriteIds[bankAtk]].pos1.y = GetBankSpriteDefault_Y(bankAtk);
    }
    else
    {
        const void *src;
        void *dst;
        u16 targetSpecies;

        if (IsContest())
        {
            identity = 0;
            targetSpecies = gContestResources->field_18->field_2;
            personalityValue = gContestResources->field_18->field_8;
            otId = gContestResources->field_18->field_C;

            HandleLoadSpecialPokePic_DontHandleDeoxys(&gMonBackPicTable[targetSpecies],
                                                      gMonSpritesGfxPtr->sprites[0],
                                                      targetSpecies,
                                                      gContestResources->field_18->field_10);
        }
        else
        {
            identity = GetBankIdentity(bankAtk);

            if (GetBankSide(bankDef) == SIDE_OPPONENT)
                targetSpecies = GetMonData(&gEnemyParty[gBattlePartyID[bankDef]], MON_DATA_SPECIES);
            else
                targetSpecies = GetMonData(&gPlayerParty[gBattlePartyID[bankDef]], MON_DATA_SPECIES);

            if (GetBankSide(bankAtk) == SIDE_PLAYER)
            {
                personalityValue = GetMonData(&gPlayerParty[gBattlePartyID[bankAtk]], MON_DATA_PERSONALITY);
                otId = GetMonData(&gPlayerParty[gBattlePartyID[bankAtk]], MON_DATA_OT_ID);

                HandleLoadSpecialPokePic_DontHandleDeoxys(&gMonBackPicTable[targetSpecies],
                                                          gMonSpritesGfxPtr->sprites[identity],
                                                          targetSpecies,
                                                          gTransformedPersonalities[bankAtk]);
            }
            else
            {
                personalityValue = GetMonData(&gEnemyParty[gBattlePartyID[bankAtk]], MON_DATA_PERSONALITY);
                otId = GetMonData(&gEnemyParty[gBattlePartyID[bankAtk]], MON_DATA_OT_ID);

                HandleLoadSpecialPokePic_DontHandleDeoxys(&gMonFrontPicTable[targetSpecies],
                                                          gMonSpritesGfxPtr->sprites[identity],
                                                          targetSpecies,
                                                          gTransformedPersonalities[bankAtk]);
            }
        }

        src = gMonSpritesGfxPtr->sprites[identity];
        dst = (void *)(VRAM + 0x10000 + gSprites[gBankSpriteIds[bankAtk]].oam.tileNum * 32);
        DmaCopy32(3, src, dst, 0x800);
        paletteOffset = 0x100 + bankAtk * 16;
        lzPaletteData = GetFrontSpritePalFromSpeciesAndPersonality(targetSpecies, otId, personalityValue);
        LZDecompressWram(lzPaletteData, gDecompressionBuffer);
        LoadPalette(gDecompressionBuffer, paletteOffset, 32);

        if (targetSpecies == SPECIES_CASTFORM)
        {
            gSprites[gBankSpriteIds[bankAtk]].anims = gMonAnimationsSpriteAnimsPtrTable[targetSpecies];
            LZDecompressWram(lzPaletteData, gBattleStruct->castformPalette[0]);
            LoadPalette(gBattleStruct->castformPalette[0] + gBattleMonForms[bankDef] * 16, paletteOffset, 32);
        }

        BlendPalette(paletteOffset, 16, 6, 0x7FFF);
        CpuCopy32(gPlttBufferFaded + paletteOffset, gPlttBufferUnfaded + paletteOffset, 32);

        if (!IsContest())
        {
            gBattleSpritesDataPtr->bankData[bankAtk].transformSpecies = targetSpecies;
            gBattleMonForms[bankAtk] = gBattleMonForms[bankDef];
        }

        gSprites[gBankSpriteIds[bankAtk]].pos1.y = GetBankSpriteDefault_Y(bankAtk);
        StartSpriteAnim(&gSprites[gBankSpriteIds[bankAtk]], gBattleMonForms[bankAtk]);
    }
}

void BattleLoadSubstituteOrMonSpriteGfx(u8 bank, bool8 loadMonSprite)
{
    u8 identity;
    s32 i;
    u32 var;
    const void *substitutePal;

    if (!loadMonSprite)
    {
        if (IsContest())
            identity = 0;
        else
            identity = GetBankIdentity(bank);

        if (IsContest())
            LZDecompressVram(gSubstituteDollTilemap, gMonSpritesGfxPtr->sprites[identity]);
        else if (GetBankSide(bank) != SIDE_PLAYER)
            LZDecompressVram(gSubstituteDollGfx, gMonSpritesGfxPtr->sprites[identity]);
        else
            LZDecompressVram(gSubstituteDollTilemap, gMonSpritesGfxPtr->sprites[identity]);

        i = 1;
        var = bank * 16;
        substitutePal = gSubstituteDollPal;
        for (; i < 4; i++)
        {
            register void *dmaSrc asm("r0") = gMonSpritesGfxPtr->sprites[identity];
            void *dmaDst = (i * 0x800) + dmaSrc;
            u32 dmaSize = 0x800;
            DmaCopy32(3, dmaSrc, dmaDst, dmaSize);
            i++;i--;
        }

        LoadCompressedPalette(substitutePal, 0x100 + var, 32);
    }
    else
    {
        if (!IsContest())
        {
            if (GetBankSide(bank) != SIDE_PLAYER)
                BattleLoadOpponentMonSpriteGfx(&gEnemyParty[gBattlePartyID[bank]], bank);
            else
                BattleLoadPlayerMonSpriteGfx(&gPlayerParty[gBattlePartyID[bank]], bank);
        }
    }
}

void LoadBattleMonGfxAndAnimate(u8 bank, bool8 loadMonSprite, u8 spriteId)
{
    BattleLoadSubstituteOrMonSpriteGfx(bank, loadMonSprite);
    StartSpriteAnim(&gSprites[spriteId], gBattleMonForms[bank]);

    if (!loadMonSprite)
        gSprites[spriteId].pos1.y = GetSubstituteSpriteDefault_Y(bank);
    else
        gSprites[spriteId].pos1.y = GetBankSpriteDefault_Y(bank);
}

void TrySetBehindSubstituteSpriteBit(u8 bank, u16 move)
{
    if (move == MOVE_SUBSTITUTE)
        gBattleSpritesDataPtr->bankData[bank].behindSubstitute = 1;
}

void ClearBehindSubstituteBit(u8 bank)
{
    gBattleSpritesDataPtr->bankData[bank].behindSubstitute = 0;
}

void HandleLowHpMusicChange(struct Pokemon *mon, u8 bank)
{
    u16 hp = GetMonData(mon, MON_DATA_HP);
    u16 maxHP = GetMonData(mon, MON_DATA_MAX_HP);

    if (GetHPBarLevel(hp, maxHP) == HP_BAR_RED)
    {
        if (!gBattleSpritesDataPtr->bankData[bank].lowHpSong)
        {
            if (!gBattleSpritesDataPtr->bankData[bank ^ BIT_MON].lowHpSong)
                PlaySE(SE_HINSI);
            gBattleSpritesDataPtr->bankData[bank].lowHpSong = 1;
        }
    }
    else
    {
        gBattleSpritesDataPtr->bankData[bank].lowHpSong = 0;
        if (!IsDoubleBattle())
        {
            m4aSongNumStop(SE_HINSI);
            return;
        }
        if (IsDoubleBattle() && !gBattleSpritesDataPtr->bankData[bank ^ BIT_MON].lowHpSong)
        {
            m4aSongNumStop(SE_HINSI);
            return;
        }
    }
}

void BattleStopLowHpSound(void)
{
    u8 playerBank = GetBankByIdentity(IDENTITY_PLAYER_MON1);

    gBattleSpritesDataPtr->bankData[playerBank].lowHpSong = 0;
    if (IsDoubleBattle())
        gBattleSpritesDataPtr->bankData[playerBank ^ BIT_MON].lowHpSong = 0;

    m4aSongNumStop(SE_HINSI);
}

u8 GetMonHPBarLevel(struct Pokemon *mon)
{
    u16 hp = GetMonData(mon, MON_DATA_HP);
    u16 maxHP = GetMonData(mon, MON_DATA_MAX_HP);

    return GetHPBarLevel(hp, maxHP);
}

void sub_805EAE8(void)
{
    if (gMain.inBattle)
    {
        u8 playerBank1 = GetBankByIdentity(IDENTITY_PLAYER_MON1);
        u8 playerBank2 = GetBankByIdentity(IDENTITY_PLAYER_MON2);
        u8 bank1PartyId = pokemon_order_func(gBattlePartyID[playerBank1]);
        u8 bank2PartyId = pokemon_order_func(gBattlePartyID[playerBank2]);

        if (GetMonData(&gPlayerParty[bank1PartyId], MON_DATA_HP) != 0)
            HandleLowHpMusicChange(&gPlayerParty[bank1PartyId], playerBank1);
        if (IsDoubleBattle() && GetMonData(&gPlayerParty[bank2PartyId], MON_DATA_HP) != 0)
            HandleLowHpMusicChange(&gPlayerParty[bank2PartyId], playerBank2);
    }
}

void sub_805EB9C(u8 affineMode)
{
    s32 i;

    for (i = 0; i < gNoOfAllBanks; i++)
    {
        if (IsBankSpritePresent(i))
        {
            gSprites[gBankSpriteIds[i]].oam.affineMode = affineMode;
            if (affineMode == 0)
            {
                gBattleSpritesDataPtr->healthBoxesData[i].field_6 = gSprites[gBankSpriteIds[i]].oam.matrixNum;
                gSprites[gBankSpriteIds[i]].oam.matrixNum = 0;
            }
            else
            {
                gSprites[gBankSpriteIds[i]].oam.matrixNum = gBattleSpritesDataPtr->healthBoxesData[i].field_6;
            }
        }
    }
}

#define tBank data0

void LoadAndCreateEnemyShadowSprites(void)
{
    u8 bank;

    LoadCompressedObjectPic(&gSpriteSheet_EnemyShadow);

    bank = GetBankByIdentity(IDENTITY_OPPONENT_MON1);
    gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId = CreateSprite(&gSpriteTemplate_EnemyShadow, sub_80A5C6C(bank, 0), sub_80A5C6C(bank, 1) + 29, 0xC8);
    gSprites[gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId].data0 = bank;

    if (IsDoubleBattle())
    {
        bank = GetBankByIdentity(IDENTITY_OPPONENT_MON2);
        gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId = CreateSprite(&gSpriteTemplate_EnemyShadow, sub_80A5C6C(bank, 0), sub_80A5C6C(bank, 1) + 29, 0xC8);
        gSprites[gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId].data0 = bank;
    }
}

void SpriteCB_EnemyShadow(struct Sprite *shadowSprite)
{
    bool8 invisible = FALSE;
    u8 bank = shadowSprite->tBank;
    struct Sprite *bankSprite = &gSprites[gBankSpriteIds[bank]];

    if (!bankSprite->inUse || !IsBankSpritePresent(bank))
    {
        shadowSprite->callback = SpriteCB_SetInvisible;
        return;
    }
    if (gAnimScriptActive || bankSprite->invisible)
        invisible = TRUE;
    else if (gBattleSpritesDataPtr->bankData[bank].transformSpecies != SPECIES_NONE
             && gEnemyMonElevation[gBattleSpritesDataPtr->bankData[bank].transformSpecies] == 0)
        invisible = TRUE;

    if (gBattleSpritesDataPtr->bankData[bank].behindSubstitute)
        invisible = TRUE;

    shadowSprite->pos1.x = bankSprite->pos1.x;
    shadowSprite->pos2.x = bankSprite->pos2.x;
    shadowSprite->invisible = invisible;
}

#undef tBank

void SpriteCB_SetInvisible(struct Sprite *sprite)
{
    sprite->invisible = 1;
}

void SetBankEnemyShadowSpriteCallback(u8 bank, u16 species)
{
    if (GetBankSide(bank) == SIDE_PLAYER)
        return;

    if (gBattleSpritesDataPtr->bankData[bank].transformSpecies != SPECIES_NONE)
        species = gBattleSpritesDataPtr->bankData[bank].transformSpecies;

    if (gEnemyMonElevation[species] != 0)
        gSprites[gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId].callback = SpriteCB_EnemyShadow;
    else
        gSprites[gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId].callback = SpriteCB_SetInvisible;
}

void EnemyShadowCallbackToSetInvisible(u8 bank)
{
    gSprites[gBattleSpritesDataPtr->healthBoxesData[bank].shadowSpriteId].callback = SpriteCB_SetInvisible;
}

void sub_805EF14(void)
{
    u16 *vramPtr = (u16*)(VRAM + 0x240);
    s32 i;
    s32 j;

    for (i = 0; i < 9; i++)
    {
        for (j = 0; j < 16; j++)
        {
            if (!(*vramPtr & 0xF000))
                *vramPtr |= 0xF000;
            if (!(*vramPtr & 0x0F00))
                *vramPtr |= 0x0F00;
            if (!(*vramPtr & 0x00F0))
                *vramPtr |= 0x00F0;
            if (!(*vramPtr & 0x000F))
                *vramPtr |= 0x000F;
            vramPtr++;
        }
    }
}

void ClearTemporarySpeciesSpriteData(u8 bank, bool8 dontClearSubstitute)
{
    gBattleSpritesDataPtr->bankData[bank].transformSpecies = SPECIES_NONE;
    gBattleMonForms[bank] = 0;
    if (!dontClearSubstitute)
        ClearBehindSubstituteBit(bank);
}

void AllocateMonSpritesGfx(void)
{
    u8 i = 0, j;

    gMonSpritesGfxPtr = NULL;
    gMonSpritesGfxPtr = AllocZeroed(sizeof(*gMonSpritesGfxPtr));
    gMonSpritesGfxPtr->firstDecompressed = AllocZeroed(0x8000);

    for (i = 0; i < BATTLE_BANKS_COUNT; i++)
    {
        gMonSpritesGfxPtr->sprites[i] = gMonSpritesGfxPtr->firstDecompressed + (i * 0x2000);
        *(gMonSpritesGfxPtr->templates + i) = gUnknown_08329D98[i];

        for (j = 0; j < 4; j++)
        {
            gMonSpritesGfxPtr->field_74[i][j].data = gMonSpritesGfxPtr->sprites[i] + (j * 0x800);
            gMonSpritesGfxPtr->field_74[i][j].size = 0x800;
        }

        gMonSpritesGfxPtr->templates[i].images = gMonSpritesGfxPtr->field_74[i];
    }

    gMonSpritesGfxPtr->barFontGfx = AllocZeroed(0x1000);
}

void FreeMonSpritesGfx(void)
{
    if (gMonSpritesGfxPtr == NULL)
        return;

    if (gMonSpritesGfxPtr->field_17C != NULL)
        FREE_AND_SET_NULL(gMonSpritesGfxPtr->field_17C);
    if (gMonSpritesGfxPtr->field_178 != NULL)
        FREE_AND_SET_NULL(gMonSpritesGfxPtr->field_178);

    FREE_AND_SET_NULL(gMonSpritesGfxPtr->barFontGfx);
    FREE_AND_SET_NULL(gMonSpritesGfxPtr->firstDecompressed);
    gMonSpritesGfxPtr->sprites[0] = NULL;
    gMonSpritesGfxPtr->sprites[1] = NULL;
    gMonSpritesGfxPtr->sprites[2] = NULL;
    gMonSpritesGfxPtr->sprites[3] = NULL;
    FREE_AND_SET_NULL(gMonSpritesGfxPtr);
}

bool32 ShouldPlayNormalPokeCry(struct Pokemon *mon)
{
    s16 hp, maxHP;
    s32 barLevel;

    if (GetMonData(mon, MON_DATA_STATUS) & (STATUS_ANY | STATUS_TOXIC_COUNTER))
        return FALSE;

    hp = GetMonData(mon, MON_DATA_HP);
    maxHP = GetMonData(mon, MON_DATA_MAX_HP);

    barLevel = GetHPBarLevel(hp, maxHP);
    if (barLevel <= HP_BAR_YELLOW)
        return FALSE;

    return TRUE;
}
