//
// Copyright(C) 2014-2015 Samuel Villarreal
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// DESCRIPTION:
//      Mantis Boss Logic
//

#include "scripts/common.txt"

//-----------------------------------------------------------------------------
//
// Stage Info
//
//-----------------------------------------------------------------------------

enum mantisStageInfo
{
    MSI_HEALTHPERCNT   = 0,
    MSI_SPEED,
    MSI_FLYSPEED,
    MSI_FLYSPITSKIPS
}

const array<float> g_mantisStage_1 = { 75.0f, 1.0f,  7.5f, 196 };
const array<float> g_mantisStage_2 = { 50.0f, 0.8f,  7.6f, 48  };
const array<float> g_mantisStage_3 = { 20.0f, 0.7f,  7.7f, 24  };
const array<float> g_mantisStage_4 = { 0.0f,  0.5f,  7.8f, 24  };

const array<const array<float>@> g_mantisStages =
{
    @g_mantisStage_1,
    @g_mantisStage_2,
    @g_mantisStage_3,
    @g_mantisStage_4
};

//-----------------------------------------------------------------------------
//
// Stage 1 Attack Anims
//
//-----------------------------------------------------------------------------

const array<int> g_mantisFloorAttack_Stage1 =
{
    anim_mantisAttackGround2,
    anim_mantisAttackGround1,
    anim_mantisSlowAttack,
    anim_mantisAttackGround2
};

const array<int> g_mantisFloorCloseAttack_Stage1 =
{
    anim_mantisAttackGroundMelee,
    anim_mantisAttackGround1,
    anim_aiMelee1,
    anim_mantisAttackGround1
};

//-----------------------------------------------------------------------------
//
// Stage 2 Attack Anims
//
//-----------------------------------------------------------------------------

const array<int> g_mantisFloorAttack_Stage2 =
{
    anim_mantisGroundToRightWall,
    anim_mantisAttackGround2,
    anim_mantisAttackGround1,
    anim_mantisSlowAttack,
    
    anim_mantisGroundToLeftWall,
    anim_mantisAttackGround1,
    anim_mantisAttackGround2
};

const array<int> g_mantisFloorCloseAttack_Stage2 =
{
    anim_mantisAttackGroundMelee,
    anim_mantisGroundToRightWall,
    anim_aiMelee1,
    anim_mantisGroundToLeftWall
};

const array<int> g_mantisRightAttack_Stage2 =
{
    anim_mantisAttackRightWall1,
    anim_mantisAttackRightWall2,
    anim_mantisRightWallToGround,
    
    anim_mantisAttackRightWall1,
    anim_mantisAttackRightWall2,
    anim_mantisRightWallToAir
};

const array<int> g_mantisLeftAttack_Stage2 =
{
    anim_mantisAttackLeftWall1,
    anim_mantisAttackLeftWall2,
    anim_mantisLeftWallToGround,
    
    anim_mantisAttackLeftWall1,
    anim_mantisAttackLeftWall2,
    anim_mantisLeftWallToAir
};

//-----------------------------------------------------------------------------
//
// Stage 3 Attack Anims
//
//-----------------------------------------------------------------------------

const array<int> g_mantisFloorAttack_Stage3 =
{
    anim_mantisGroundToRightWall,
    anim_mantisSlowAttack,
    anim_mantisAttackGround2,
    
    anim_mantisGroundToLeftWall,
    anim_mantisAttackGround1,
    anim_mantisAttackGround2,
    
    anim_mantisGroundToCeiling,
    anim_mantisAttackGround1
};

const array<int> g_mantisFloorCloseAttack_Stage3 =
{
    anim_mantisGroundToRightWall,
    anim_mantisAttackGroundMelee,
    anim_aiMelee1,
    
    anim_mantisGroundToLeftWall,
    anim_mantisAttackGroundMelee,
    anim_aiMelee1,
    
    anim_mantisGroundToCeiling,
    anim_aiMelee1
};

const array<int> g_mantisRightAttack_Stage3 =
{
    anim_mantisAttackRightWall1,
    anim_mantisRightWallToGround,
    
    anim_mantisAttackRightWall2,
    anim_mantisRightWallToAir,
    
    anim_mantisAttackRightWall1,
    anim_mantisRightWallToCeiling,
    
    anim_mantisAttackRightWall2,
    anim_mantisRightWallToGround,
    
    anim_mantisAttackRightWall1,
    anim_mantisRightWallToAir,
    
    anim_mantisAttackRightWall2,
    anim_mantisRightWallToCeiling
};

const array<int> g_mantisLeftAttack_Stage3 =
{
    anim_mantisAttackLeftWall1,
    anim_mantisLeftWallToGround,
    
    anim_mantisAttackLeftWall2,
    anim_mantisLeftWallToAir,
    
    anim_mantisAttackLeftWall1,
    anim_mantisLeftWallToCeiling,
    
    anim_mantisAttackLeftWall2,
    anim_mantisLeftWallToGround,
    
    anim_mantisAttackLeftWall1,
    anim_mantisLeftWallToAir,
    
    anim_mantisAttackLeftWall2,
    anim_mantisLeftWallToCeiling
};

const array<int> g_mantisCeilingAttack_Stage3 =
{
    anim_mantisAttackCeiling1,
    anim_mantisCeilingToGround,
    anim_mantisAttackCeiling2,
    anim_mantisCeilingToGround
};

//-----------------------------------------------------------------------------
//
// Stage 4 Attack Anims
//
//-----------------------------------------------------------------------------

const array<int> g_mantisCeilingAttack_Stage4 =
{
    anim_mantisAttackCeiling1,
    anim_mantisAttackCeiling1,
    anim_mantisCeilingToGround,
    anim_mantisAttackCeiling2,
    anim_mantisAttackCeiling2,
    anim_mantisCeilingToGround
};

//-----------------------------------------------------------------------------
//
// Enumerators
//
//-----------------------------------------------------------------------------

enum mantisArenaLocation
{
    MAL_SOUTH   = 0,
    MAL_WEST,
    MAL_NORTH,
    MAL_EAST
}

//-----------------------------------------------------------------------------
//
// Bashable walls
//
//-----------------------------------------------------------------------------

array<bool> g_wallsDestroyed =
{
    false, false, false, false,
    false, false, false, false
};

const array<kVec3> g_wallOrigins =
{
    kVec3(-(90*GAME_SCALE),  (90*GAME_SCALE), 0.0f),
    kVec3( (90*GAME_SCALE),  (90*GAME_SCALE), 0.0f),
    kVec3( (90*GAME_SCALE), -(90*GAME_SCALE), 0.0f),
    kVec3(-(90*GAME_SCALE), -(90*GAME_SCALE), 0.0f)
};

const array<kVec3> g_wallCoordinates =
{
    kVec3(-(100*GAME_SCALE),  (60*GAME_SCALE),  0.0f),    // top-left wall (left side)
    kVec3(-(60*GAME_SCALE),   (100*GAME_SCALE), 0.0f),    // top-left wall (right side)
    kVec3( (60*GAME_SCALE),   (100*GAME_SCALE), 0.0f),    // top-right wall (left side)
    kVec3( (100*GAME_SCALE),  (60*GAME_SCALE),  0.0f),    // top-right wall (right side)
    kVec3( (100*GAME_SCALE), -(60*GAME_SCALE),  0.0f),    // bottom-right wall (right side)
    kVec3( (60*GAME_SCALE),  -(100*GAME_SCALE), 0.0f),    // bottom-right wall (left side)
    kVec3(-(60*GAME_SCALE),  -(100*GAME_SCALE), 0.0f),    // bottom-left wall (right side)
    kVec3(-(100*GAME_SCALE), -(60*GAME_SCALE),  0.0f)     // bottom-let wall (left side)
};

//-----------------------------------------------------------------------------
//
// Mantis
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokMantis
==============================================================
*/

final class TurokMantis : TurokEnemy
{
    float m_goalDistSq;             // distance to goal origin squared
    float m_goalDist;               // distance to goal origin
    kVec3 m_goalOrigin;             // goal origin to chase at
    kAngle m_desiredYaw;            // direction yaw that it wants to turn to
    kAngle m_lookAtYaw;             // direction yaw that it wants to target at
    int m_state;
    bool m_bTurning;                // playing a turning animation?
    int m_attackLoop;               // cycles through different attack animations
    float m_floorHeight;
    float m_bobTime;
    float m_acceleration;           // for flying; accelerates speed as it begins to move
    float m_pitchTilt;              // for flying; tilts pitch when ramping up speed
    float m_rollTilt;               // for flying; tilts roll when making sharp turns
    float m_targetRange;
    int m_wallSide;                 // which side of the wall to bash?
    int m_wallTID;                  // TID value of the wall actor
    bool m_bBashAllWalls;           // will bash all walls in the arena, ignoring the target
    bool m_bBashOnly;               // we're not in attack mode while playing leaping animations
    bool m_bAllowBobbing;
    int m_initialHealth;
    int m_stage;
    float m_animSpeed;
    const array<float> @m_pStageMode;
    const array<int> @m_pFloorAttack;
    const array<int> @m_pCloseFloorAttack;
    const array<int> @m_pLeftWallAttack;
    const array<int> @m_pRightWallAttack;
    const array<int> @m_pCeilingAttack;
    int m_floorAttackCounter;
    int m_CloseFloorAttackCounter;
    int m_leftWallAttackCounter;
    int m_rightWallAttackCounter;
    int m_ceilingAttackCounter;
    bool m_bTargetDead;
    bool m_bDamageStunned;
    
    TurokMantis(kActor @actor)
    {
        m_goalDistSq = 0;
        m_goalDist = 0;
        m_desiredYaw = 0;
        m_lookAtYaw = 0;
        m_attackLoop = 0;
        m_bTurning = false;
        m_floorHeight = 0;
        m_bobTime = 0;
        m_acceleration = 0;
        m_pitchTilt = 0;
        m_rollTilt = 0;
        m_targetRange = 0;
        m_wallSide = -1;
        m_wallTID = -1;
        m_bBashAllWalls = false;
        m_bBashOnly = false;
        m_bAllowBobbing = false;
        m_initialHealth = 0;
        m_state = MS_GROUNDCHASE;
        m_stage = 0;
        m_animSpeed = 1;
        m_floorAttackCounter = 0;
        m_CloseFloorAttackCounter = 0;
        m_leftWallAttackCounter = 0;
        m_rightWallAttackCounter = 0;
        m_ceilingAttackCounter = 0;
        @m_pStageMode = g_mantisStages[0];
        @m_pFloorAttack = @g_mantisFloorAttack_Stage1;
        @m_pCloseFloorAttack = @g_mantisFloorCloseAttack_Stage1;
        @m_pLeftWallAttack = null;
        @m_pRightWallAttack = null;
        @m_pCeilingAttack = null;
        m_bTargetDead = false;
        m_bDamageStunned = false;
        
        super(actor);
    }
    
    /*
    ==============================================================
    RunAttackStage
    ==============================================================
    */
    
    int RunAttackStage(const array<int>@ &in pRefAttack, int &in inCounter, int &out outCounter)
    {
        int anim = pRefAttack[inCounter];
        
        outCounter = inCounter + 1;
        
        if(outCounter >= int(pRefAttack.length()))
        {
            outCounter = 0;
        }
        
        return anim;
    }
    
    /*
    ==============================================================
    SpitMantisAcid
    ==============================================================
    */
    
    void SpitMantisAcid(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if(self.AnimState().PlayingID() == anim_mantisAirLoop)
        {
            if(m_state == MS_BASHWALL || !self.RandomDecision(6) ||
                Math::Fabs(m_desiredYaw) > Math::Deg2Rad(10.0f))
            {
                return;
            }
        }
        
        self.SpawnProjectile("fx/fireball.kfx", kVec3(x, y, z),
            m_goalOrigin, Math::Deg2Rad(45.0f));
    }
    
    /*
    ==============================================================
    RedBombAttack
    ==============================================================
    */
    
    void RedBombAttack(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.SpawnProjectile("fx/deathrain.kfx", kVec3(x, y, z),
            m_goalOrigin, Math::Deg2Rad(45.0f));
    }
    
    /*
    ==============================================================
    SwooshTrail2
    ==============================================================
    */
    
    void SwooshTrail2(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_MantisFoot", 5);   // left front foot
        self.RenderModel().AddTrailEffect("Trail_MantisFoot", 9);   // left back foot
        self.RenderModel().AddTrailEffect("Trail_MantisFoot", 13);  // right front foot
        self.RenderModel().AddTrailEffect("Trail_MantisFoot", 17);  // right back foot
    }
    
    /*
    ==============================================================
    SwooshTrail3
    ==============================================================
    */
    
    void SwooshTrail3(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_MantisHand", 24);
        self.RenderModel().AddTrailEffect("Trail_MantisHand", 27);
    }
    
    /*
    ==============================================================
    ScreenShake
    ==============================================================
    */
    
    void ScreenShake(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        Player.Actor().RecoilPitch() = -Math::Deg2Rad(w);
        Player.Actor().Velocity().z = ((w * 0.512f) * 15.0f) * (4 * GAME_DELTA_TIME);
    }
    
    /*
    ==============================================================
    BlendAnimation
    ==============================================================
    */
    
    void BlendAnimation(const int anim, const float speed, const float blend, const bool bStoppedOnly = true)
    {
        if(bStoppedOnly && self.AnimState().Blending())
        {
            // don't interrupt while blending
            return;
        }
        
        if(bStoppedOnly && (!self.AnimState().Looping() && !self.AnimState().Stopped()))
        {
            return;
        }
        else if(!bStoppedOnly && self.AnimState().PlayingID() == anim)
        {
            return;
        }
        
        if(anim != anim_mantisPain)
        {
            self.Flags() &= ~AF_INVINCIBLE;
        }
        
        self.AnimState().Blend(anim, speed*m_animSpeed, blend*m_animSpeed, ANF_ROOTMOTION);
    }
    
    /*
    ==============================================================
    BlendLoopingAnimation
    ==============================================================
    */
    
    void BlendLoopingAnimation(const int anim, const float speed, const float blend)
    {
        if(!self.AnimState().Stopped())
        {
            return;
        }
        
        if(self.AnimState().PlayingID() != anim)
        {
            if(anim != anim_mantisPain)
            {
                self.Flags() &= ~AF_INVINCIBLE;
            }
        
            self.AnimState().Blend(anim, speed*m_animSpeed, blend*m_animSpeed, ANF_ROOTMOTION|ANF_LOOP);
        }
    }
    
    /*
    ==============================================================
    GetTargetPoint
    ==============================================================
    */
    
    void GetTargetPoint(void)
    {
        m_goalOrigin.x = Player.Actor().Origin().x;
        m_goalOrigin.y = Player.Actor().Origin().y;
        m_goalOrigin.z = self.Origin().z;
        
        m_targetRange = 512.0f;
        
        m_goalDistSq = self.DistanceToPoint(m_goalOrigin);
        m_goalDist = Math::Sqrt(m_goalDistSq);
        
        m_desiredYaw = self.GetAvoidanceAngle(m_goalOrigin, 1.5f) - self.Yaw();
        m_lookAtYaw = m_desiredYaw;
    }
    
    /*
    ==============================================================
    GetWallBashPoint
    ==============================================================
    */
    
    void GetWallBashPoint(void)
    {
        kVec3 org;
        
        if(m_wallSide <= -1)
        {
            m_state = MS_GROUNDCHASE;
            return;
        }
        
        // figure out which actual actor that m_wallSide belongs to
        switch(m_wallSide)
        {
        case 0:
        case 1:
            m_wallTID = 2701;
            org = g_wallOrigins[0];
            break;
            
        case 2:
        case 3:
            m_wallTID = 2702;
            org = g_wallOrigins[1];
            break;
            
        case 4:
        case 5:
            m_wallTID = 2703;
            org = g_wallOrigins[2];
            break;
            
        case 6:
        case 7:
            m_wallTID = 2704;
            org = g_wallOrigins[3];
            break;
        }
        
        if(m_wallTID <= 0)
        {
            m_state = MS_GROUNDCHASE;
            return;
        }
        
        // to make sure that we're not targeting the other side of the wall, check
        // which location of the arena we're at and change m_wallSide accordingly
        switch(GetWhichArenaLocation(self.Origin()))
        {
        case MAL_NORTH:
            if(m_wallSide == 0) { m_wallSide = 1; }
            if(m_wallSide == 3) { m_wallSide = 2; }
            break;
            
        case MAL_EAST:
            if(m_wallSide == 2) { m_wallSide = 3; }
            if(m_wallSide == 5) { m_wallSide = 4; }
            break;
            
        case MAL_SOUTH:
            if(m_wallSide == 4) { m_wallSide = 5; }
            if(m_wallSide == 7) { m_wallSide = 6; }
            break;
            
        case MAL_WEST:
            if(m_wallSide == 6) { m_wallSide = 7; }
            if(m_wallSide == 1) { m_wallSide = 0; }
            break;
        }
        
        m_goalOrigin = g_wallCoordinates[m_wallSide];
        
        m_goalOrigin.z = self.Origin().z;
        m_targetRange = 235.52f;
        
        m_goalDistSq = self.DistanceToPoint(m_goalOrigin);
        m_goalDist = Math::Sqrt(m_goalDistSq);
        
        m_desiredYaw = self.GetAvoidanceAngle(m_goalOrigin, 1.5f) - self.Yaw();
        m_lookAtYaw = self.GetTurnYaw(org);
        
        // add extra slop to line up the animation correctly
        if((m_wallSide & 1) == 0)
        {
            m_lookAtYaw -= Math::Deg2Rad(90.0f);
        }
        else
        {
            m_lookAtYaw += Math::Deg2Rad(90.0f);
        }
    }
    
    /*
    ==============================================================
    GetClosestWallPoint
    ==============================================================
    */
    
    int GetClosestWallPoint(void)
    {
        int best = -1;
        uint idx = 0;
        float dist = 0;
        float x, y, d;
        
        do
        {
            x = g_wallCoordinates[idx].x - self.Origin().x;
            y = g_wallCoordinates[idx].y - self.Origin().y;
            
            d = x * x + y * y;
            
            if(d >= 377487.36f || g_wallsDestroyed[idx])
            {
                continue;
            }
            
            if(best == -1 || d < dist)
            {
                dist = d;
                best = idx;
            }
            
        } while(++idx < g_wallCoordinates.length());
        
        return best;
    }
    
    /*
    ==============================================================
    GetWhichArenaLocation
    ==============================================================
    */
    
    int GetWhichArenaLocation(kVec3 &in origin)
    {
        float an = origin.ToYaw();
        const float an45 = Math::Deg2Rad(45.0f);
        const float an135 = Math::Deg2Rad(135.0f);
        
        if(an < -an135 || an >= -an45)
        {
            if(an < -an45 || an >= an45)
            {
                if(an < an45 || an >= an135)
                {
                    return MAL_SOUTH;
                }
                else
                {
                    return MAL_EAST;
                }
            }
            else
            {
                return MAL_NORTH;
            }
        }
        
        return MAL_WEST;
    }
    
    /*
    ==============================================================
    GetNextAvailableWall
    ==============================================================
    */
    
    void GetNextAvailableWall(void)
    {
        int idx = -1;
        m_wallSide = -1;
        
        switch(GetWhichArenaLocation(self.Origin()))
        {
        case MAL_NORTH:
            idx = 1;
            break;
            
        case MAL_EAST:
            idx = 3;
            break;
            
        case MAL_SOUTH:
            idx = 5;
            break;
            
        case MAL_WEST:
            idx = 7;
            break;
            
        default:
            return;
        }
        
        for(uint i = 0; i < g_wallsDestroyed.length(); ++i)
        {
            if(!g_wallsDestroyed[idx])
            {
                m_wallSide = idx;
                return;
            }
            
            if(++idx >= int(g_wallsDestroyed.length()))
            {
                idx = 0;
            }
        }
    }
    
    /*
    ==============================================================
    CheckWallsToBash
    ==============================================================
    */
    
    bool AllWallsDestroyed(void)
    {
        for(uint i = 0; i < g_wallsDestroyed.length(); ++i)
        {
            if(!g_wallsDestroyed[i])
            {
                return false;
            }
        }
        
        return true;
    }
    
    /*
    ==============================================================
    CheckWallsToBash
    ==============================================================
    */
    
    void CheckWallsToBash(void)
    {
        if(self.Health() <= 0 || m_state == MS_BASHWALL)
        {
            return;
        }
        
        if(!m_bBashAllWalls)
        {
            int arenaLoc = GetWhichArenaLocation(self.Origin());
            int targetArenaLoc = GetWhichArenaLocation(Player.Actor().Origin());
            
            m_wallSide = -1;
            
            if(arenaLoc == targetArenaLoc)
            {
                m_state = MS_GROUNDCHASE;
                return;
            }
            
            if( (arenaLoc == MAL_SOUTH && targetArenaLoc == MAL_NORTH) ||
                (arenaLoc == MAL_WEST  && targetArenaLoc == MAL_EAST)  ||
                (arenaLoc == MAL_NORTH && targetArenaLoc == MAL_SOUTH) ||
                (arenaLoc == MAL_EAST  && targetArenaLoc == MAL_WEST))
            {
                return;
            }
            
            m_wallSide = GetClosestWallPoint();
        }
        else
        {
            GetNextAvailableWall();
        }
        
        if(m_wallSide <= -1)
        {
            if(m_bBashAllWalls)
            {
                if(!AllWallsDestroyed())
                {
                    // we're not done yet
                    Sys.Warning("mantis tried to chase target while walls are still up!");
                    m_state = MS_BASHWALL;
                    return;
                }
            }
            
            m_state = MS_GROUNDCHASE;
            m_bBashAllWalls = false;
            return;
        }
        
        m_state = MS_BASHWALL;
    }
    
    /*
    ==============================================================
    BashWall
    ==============================================================
    */
    
    void BashWall(void)
    {
        if(m_wallTID <= 0 || m_wallSide <= -1)
        {
            return;
        }
        
        kActor @wall = World.GetActorByTID(m_wallTID);
        
        if(wall is null)
        {
            m_state = MS_GROUNDCHASE;
            return;
        }
        
        Player.Actor().RecoilPitch() = -Math::Deg2Rad(10.0f);
        
        wall.MarkPersistentBit(false);

        if((m_wallSide & 1) != 0)
        {
            wall.AnimState().Set(anim_mantisWallCollapseOutward, 4.0f, 0);
            g_wallsDestroyed[m_wallSide] = true;
            g_wallsDestroyed[m_wallSide-1] = true;
            
            BlendAnimation(anim_mantisRightWallToGround, 2.0f, 6.0f, false);
        }
        else
        {
            wall.AnimState().Set(anim_mantisWallCollapseInward, 4.0f, 0);
            g_wallsDestroyed[m_wallSide] = true;
            g_wallsDestroyed[m_wallSide+1] = true;
            
            BlendAnimation(anim_mantisLeftWallToGround, 2.0f, 6.0f, false);
        }
        
        m_wallTID = -1;
        
        if(m_bBashAllWalls)
        {
            GetNextAvailableWall();
            
            if(m_wallSide >= 0)
            {
                return;
            }
        }
        
        m_state = MS_GROUNDCHASE;
        m_wallSide = -1;
    }
    
    /*
    ==============================================================
    AngleDiffFromZero
    ==============================================================
    */
    
    float AngleDiffFromZero(float theta)
    {
        while(theta < -Math::pi) theta += 2*Math::pi;
        while(theta > Math::pi) theta -= 2*Math::pi;
        
        return -theta;
    }
    
    /*
    ==============================================================
    AngleDiff
    ==============================================================
    */
    
    float AngleDiff(float phi, float theta)
    {
        return AngleDiffFromZero(phi + AngleDiffFromZero(theta));
    }
    
    /*
    ==============================================================
    CompareAngles
    ==============================================================
    */
    
    float AngleDist(float phi, float theta)
    {
        return Math::Fabs(AngleDiff(phi, theta));
    }
    
    /*
    ==============================================================
    CheckLeapPosition
    ==============================================================
    */
    
    bool CheckLeapPosition(const float angle)
    {
        float ang = self.Yaw() + angle;
        kVec3 pos, offset;
        
        // check min distance - wall can't be too close
        pos.x = self.Origin().x - Math::Sin(ang) * (5*GAME_SCALE);
        pos.y = self.Origin().y - Math::Cos(ang) * (5*GAME_SCALE);
        pos.z = self.Origin().z + (60*GAME_SCALE);
        
        if(!self.CheckPosition(pos))
        {
            // too close to the wall
            return false;
        }
        
        // check pt1 - is there a wall inbetween max distance and the mantis?
        pos.x = self.Origin().x - Math::Sin(ang) * (150*GAME_SCALE);
        pos.y = self.Origin().y - Math::Cos(ang) * (150*GAME_SCALE);
        pos.z = self.Origin().z + (60*GAME_SCALE);
        
        if(self.CheckPosition(pos))
        {
            // too far from the wall
            return false;
        }
        
        // check pt2 - look parallel to wall to make sure wall angle next to pt1 is the same
        float wallAngle1 = CModel.ContactNormal().ToYaw();
        kVec3 vTarget2 = pos;
        
        ang += Math::Deg2Rad(90.0f);
        vTarget2.x -= (20*GAME_SCALE) * Math::Sin(ang);
        vTarget2.y -= (20*GAME_SCALE) * Math::Cos(ang);
        
        if(self.CheckPosition(vTarget2))
        {
            return false;
        }
        
        // check pt3 - look parallel to wall to make sure wall angle next to pt1 is the same
        float wallAngle2 = CModel.ContactNormal().ToYaw();
        vTarget2 = pos;
        
        ang += Math::Deg2Rad(180.0f);
        vTarget2.x -= (20*GAME_SCALE) * Math::Sin(ang);
        vTarget2.y -= (20*GAME_SCALE) * Math::Cos(ang);
        
        if(self.CheckPosition(vTarget2))
        {
            return false;
        }
        
        // make sure all angles are very close
        float wallAngle3 = CModel.ContactNormal().ToYaw();

        if(AngleDist(wallAngle1, wallAngle2) > Math::Deg2Rad(10)) return false;
        if(AngleDist(wallAngle1, wallAngle3) > Math::Deg2Rad(10)) return false;
        if(AngleDist(wallAngle2, wallAngle3) > Math::Deg2Rad(10)) return false;
        
        wallAngle1 += (Math::pi / 2);
        
        if(AngleDist(self.Yaw(), wallAngle1 + Math::pi) < AngleDist(self.Yaw(), wallAngle1))
        {
            wallAngle1 += Math::pi;
        }
        
        kAngle normalizedAngle = wallAngle1;
        
        return (AngleDist(self.Yaw(), normalizedAngle) < Math::Deg2Rad(90.0f));
    }
    
    /*
    ==============================================================
    CheckPositionInFlight
    ==============================================================
    */
    
    bool CheckPositionInFlight(const float angle)
    {
        float ang = self.Yaw() + angle;
        kVec3 pos;
        
        pos.x = self.Origin().x - Math::Sin(ang) * (10*GAME_SCALE);
        pos.y = self.Origin().y - Math::Cos(ang) * (10*GAME_SCALE);
        pos.z = self.Origin().z;
        
        return (self.CheckPosition(pos) == false);
    }
    
    /*
    ==============================================================
    CheckLeftWallForLeap
    ==============================================================
    */
    
    bool CheckLeftWallForLeap(void)
    {
        return CheckLeapPosition(Math::Deg2Rad(-90.0f));
    }
    
    /*
    ==============================================================
    CheckRightWallForLeap
    ==============================================================
    */
    
    bool CheckRightWallForLeap(void)
    {
        return CheckLeapPosition(Math::Deg2Rad(90.0f));
    }
    
    /*
    ==============================================================
    SetTilt
    ==============================================================
    */
    
    float SetTilt(float tilt, const float dest)
    {
        float amount;
        float d = dest;
        
        if(dest == 0.0f)
        {
            amount = Math::Deg2Rad(0.5f);
        }
        else
        {
            const float an = Math::Deg2Rad(15.0f);
            
            if(dest >= -an)
            {
                if(dest > an)
                {
                    d = an;
                }
            }
            else
            {
                d = -an;
            }
            
            amount = Math::Deg2Rad(1.0f);
        }
        
        return Math::NLerp(tilt, amount * 0.5f, d);
    }
    
    /*
    ==============================================================
    FlyBob
    ==============================================================
    */
    
    void FlyBob(void)
    {
        if(!m_bAllowBobbing && self.Origin().z < m_floorHeight - 0.1f)
        {
            self.Origin().z = (m_floorHeight - self.Origin().z) * 0.125f + self.Origin().z;
            return;
        }
        else if(!m_bAllowBobbing)
        {
            m_bAllowBobbing = true;
        }
        
        self.Origin().z = m_floorHeight + (Math::Sin(m_bobTime) * 15.0f);
        m_bobTime += Math::Deg2Rad(18.0f) * 0.5f;
    }
    
    /*
    ==============================================================
    TryGroundAttack
    ==============================================================
    */
    
    void TryGroundAttack(void)
    {
        int anim;
        
        if(m_state == MS_BASHWALL)
        {
            m_bBashOnly = true;
                
            if((m_wallSide & 1) == 1)
            {
                BlendAnimation(anim_mantisGroundToRightWall, 1.6f, 6.0f, false);
            }
            else
            {
                BlendAnimation(anim_mantisGroundToLeftWall, 1.6f, 6.0f, false);
            }
            
            return;
        }
        
        if(m_bBashAllWalls)
        {
            return;
        }
        
        if(m_goalDist < 35*GAME_SCALE)
        {
            anim = RunAttackStage(m_pCloseFloorAttack, m_CloseFloorAttackCounter, m_CloseFloorAttackCounter);
        }
        else
        {
            anim = RunAttackStage(m_pFloorAttack, m_floorAttackCounter, m_floorAttackCounter);
        }
        
        if(anim == anim_mantisGroundToLeftWall &&
            !CheckLeftWallForLeap())
        {
            return;
        }
        if(anim == anim_mantisGroundToRightWall &&
            !CheckRightWallForLeap())
        {
            return;
        }
        
        BlendAnimation(anim, 2.0f, 6.0f, !m_bTurning);
    }
    
    /*
    ==============================================================
    TryCeilingAttack
    ==============================================================
    */
    
    void TryCeilingAttack(void)
    {
        int anim;
        
        if(m_pCeilingAttack is null)
        {
            return;
        }
        
        anim = RunAttackStage(m_pCeilingAttack,
            m_ceilingAttackCounter, m_ceilingAttackCounter);
            
        BlendAnimation(anim, 2.0f, 3.0f);
    }
    
    /*
    ==============================================================
    TakeOffFromGround
    ==============================================================
    */
    
    void TakeOffFromGround(void)
    {
        BlendAnimation(anim_mantisGroundToAir, 4.0f, 6.0f);
        
        self.Gravity() = 0;
        self.ClipFlags() |= CF_NOSTEPDOWN;
        
        m_floorHeight = self.FloorHeight() + 50.0f;
        m_bAllowBobbing = false;
        m_bobTime = 0;
        m_acceleration = 0;
        m_pitchTilt = 0;
        m_rollTilt = 0;
    }
    
    /*
    ==============================================================
    FlightToGround
    ==============================================================
    */
    
    void FlightToGround(void)
    {
        BlendAnimation(anim_mantisAirToGround, 4.0f, 8.0f);
        self.Gravity() = 0.5f;
        self.ClipFlags() &= ~CF_NOSTEPDOWN;
    }
    
    /*
    ==============================================================
    GroundChase
    ==============================================================
    */
    
    void GroundChase(void)
    {
        UpdateStage();
        
        if(m_goalDist <= 1433.6f)
        {
            if(m_goalDist <= m_targetRange)
            {
                if(m_lookAtYaw >= -Math::Deg2Rad(35.0f))
                {
                    if(m_lookAtYaw <= Math::Deg2Rad(35.0f))
                    {
                        TryGroundAttack();
                    }
                    else
                    {
                        BlendAnimation(anim_mantisGroundStandTurnLeft, 2.5f, 6.0f);
                        m_attackLoop = 0;
                    }
                }
                else
                {
                    BlendAnimation(anim_mantisGroundStandTurnRight, 2.5f, 6.0f);
                    m_attackLoop = 0;
                }
            }
            else
            {
                TurnAngles(Math::Deg2Rad(4.0f), m_desiredYaw);
                
                if(m_desiredYaw >= -Math::Deg2Rad(45.0f))
                {
                    if(m_desiredYaw <= Math::Deg2Rad(45.0f))
                    {
                        BlendAnimation(anim_mantisGroundForward, 1.6f, 6.0f);
                        m_attackLoop = 0;
                    }
                    else
                    {
                        BlendAnimation(anim_mantisGroundStandTurnLeft, 2.5f, 6.0f);
                        m_attackLoop = 0;
                    }
                }
                else
                {
                    BlendAnimation(anim_mantisGroundStandTurnRight, 2.5f, 6.0f);
                    m_attackLoop = 0;
                }
            }
        }
        else
        {
            TakeOffFromGround();
        }
    }
    
    /*
    ==============================================================
    CeilingChase
    ==============================================================
    */
    
    void CeilingChase(void)
    {
        if(m_state != MS_BASHWALL && m_goalDist <= 1433.6f)
        {
            if((self.AnimState().PlayingID() == anim_mantisGroundToCeiling ||
                self.AnimState().PlayingID() == anim_mantisRightWallToCeiling ||
                self.AnimState().PlayingID() == anim_mantisLeftWallToCeiling) &&
                self.AnimState().Stopped())
            {
                BlendAnimation(anim_mantisAttackCeiling1, 2.0f, 4.0f, false);
                return;
            }
            
            if(m_goalDist <= (m_targetRange + 286.72f))
            {
                if(m_lookAtYaw >= -Math::Deg2Rad(35.0f))
                {
                    if(m_lookAtYaw <= Math::Deg2Rad(35.0f))
                    {
                        TryCeilingAttack();
                    }
                    else
                    {
                        BlendAnimation(anim_mantisCeilingStandTurnLeft, 1.5f, 3.0f);
                    }
                }
                else
                {
                    BlendAnimation(anim_mantisCeilingStandTurnRight, 1.5f, 3.0f);
                }
            }
            else
            {
                TurnAngles(Math::Deg2Rad(4.0f), m_desiredYaw);
                
                if(m_desiredYaw >= -Math::Deg2Rad(45.0f))
                {
                    if(m_desiredYaw <= Math::Deg2Rad(45.0f))
                    {
                        BlendAnimation(anim_mantisCeilingForward, 1.5f, 6.0f);
                    }
                    else
                    {
                        BlendAnimation(anim_mantisCeilingStandTurnLeft, 1.5f, 3.0f);
                    }
                }
                else
                {
                    BlendAnimation(anim_mantisCeilingStandTurnRight, 1.5f, 3.0f);
                }
            }
        }
        else
        {
            BlendAnimation(anim_mantisCeilingToGround, 2.0f, 3.0f);
        }
    }
    
    /*
    ==============================================================
    FlyChase
    ==============================================================
    */
    
    void FlyChase(void)
    {
        float mx = self.Velocity().x;
        float my = self.Velocity().y;
        float md = mx * mx + my * my;
        
        if(md != 0.0f)
        {
            md = Math::Sqrt(md);
        }
        
        if(self.AnimState().PlayingID() == anim_mantisAirLoop)
        {
            float speed = m_pStageMode[MSI_FLYSPEED];
            
            if(m_goalDist > m_targetRange)
            {
                m_acceleration = Math::IncMax(m_acceleration, 0.0625f, 1.0f);
            }
            else
            {
                m_acceleration = Math::NLerp(m_acceleration, 0.125f, 0.0f);
            }
            
            self.Velocity().x = (speed * m_acceleration) * Math::Sin(self.Yaw());
            self.Velocity().y = (speed * m_acceleration) * Math::Cos(self.Yaw());
        }
        
        FlyBob();
        TurnAngles(Math::Deg2Rad(6.0f), m_desiredYaw);
        
        if(m_goalDist < m_targetRange)
        {
            if(Math::Fabs(m_desiredYaw) < Math::Deg2Rad(10.0f))
            {
                FlightToGround();
                return;
            }
        }
        
        m_pitchTilt = SetTilt(m_pitchTilt, md * 0.125f);
        m_rollTilt = SetTilt(m_rollTilt, m_desiredYaw);
    }
    
    /*
    ==============================================================
    CheckTurning
    ==============================================================
    */
    
    void CheckTurning(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_mantisGroundStandTurnRight:
        case anim_mantisCeilingStandTurnRight:
        case anim_mantisGroundStandTurnLeft:
        case anim_mantisCeilingStandTurnLeft:
            m_bTurning = true;
            break;
        default:
            m_bTurning = false;
            break;
        }
    }
    
    /*
    ==============================================================
    CheckLeapFromGround
    ==============================================================
    */
    
    void CheckLeapFromGround(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_mantisGroundToRightWall:
            if(m_state == MS_BASHWALL)
            {
                BlendAnimation(anim_mantisWallRightIdle, 2.0f, 6.0f);
                return;
            }
            break;
            
        case anim_mantisGroundToLeftWall:
            if(m_state == MS_BASHWALL)
            {
                BlendAnimation(anim_mantisWallLeftIdle, 2.0f, 6.0f);
                return;
            }
            break;
        }
        
        CheckWallToLandOn();
    }
    
    /*
    ==============================================================
    CheckWallToLandOn
    ==============================================================
    */
    
    void CheckWallToLandOn(void)
    {
        if(self.AnimState().Blending() ||
            (!self.AnimState().Looping() && !self.AnimState().Stopped()))
        {
            return;
        }
        
        switch(self.AnimState().PlayingID())
        {
        case anim_mantisRightWallToAirLoop:
        case anim_mantisGroundToLeftWall:
            if(CheckPositionInFlight(Math::Deg2Rad(-90.0f)))
            {
                BlendAnimation(anim_mantisAirToLeftWall, 1.0f, 3.0f, false);
                
                if(!m_bBashOnly)
                {
                    self.Yaw() = (CModel.ContactNormal().ToYaw() + Math::pi) + Math::Deg2Rad(90.0f);
                    self.Yaw() += Math::pi;
                }
            }
            else
            {
                BlendLoopingAnimation(anim_mantisRightWallToAirLoop, 1.0f, 3.0f);
            }
            break;
            
        case anim_mantisLeftWallToAirLoop:
        case anim_mantisGroundToRightWall:
            if(CheckPositionInFlight(Math::Deg2Rad(90.0f)))
            {
                BlendAnimation(anim_mantisAirToRightWall, 1.0f, 3.0f, false);
                
                if(!m_bBashOnly)
                {
                    self.Yaw() = (CModel.ContactNormal().ToYaw() + Math::pi) + Math::Deg2Rad(90.0f);
                }
            }
            else
            {
                BlendLoopingAnimation(anim_mantisLeftWallToAirLoop, 1.0f, 3.0f);
            }
            break;
        }
    }
    
    /*
    ==============================================================
    LeapDecision
    ==============================================================
    */
    
    void LeapDecision(const bool bLeftSide)
    {
        int anim = -1;

        if(!bLeftSide)
        {
            if(m_pRightWallAttack is null)
            {
                return;
            }
            
            anim = RunAttackStage(m_pRightWallAttack,
                m_rightWallAttackCounter, m_rightWallAttackCounter);
        }
        else
        {
            if(m_pLeftWallAttack is null)
            {
                return;
            }
            
            anim = RunAttackStage(m_pLeftWallAttack,
                m_leftWallAttackCounter, m_leftWallAttackCounter);
        }
        
        if(anim == -1)
        {
            return;
        }
        
        BlendAnimation(anim, 2.0f, 6.0f);
    }
    
    /*
    ==============================================================
    CheckGroundToLeapOn
    ==============================================================
    */
    
    void CheckGroundToLeapOn(void)
    {
        int anim;
        
        if(!self.AnimState().Blending())
        {
            if(m_bBashOnly)
            {
                m_bBashOnly = false;
                BashWall();
                return;
            }
        }
        
        if(!self.AnimState().CycleCompleted())
        {
            return;
        }
        
        switch(self.AnimState().PlayingID())
        {
        case anim_mantisAttackRightWall1:
        case anim_mantisAttackRightWall2:
        case anim_mantisWallRightIdle:
            LeapDecision(false);
            break;
            
        case anim_mantisAttackLeftWall1:
        case anim_mantisAttackLeftWall2:
        case anim_mantisWallLeftIdle:
            LeapDecision(true);
            break;
        }
    }
    
    /*
    ==============================================================
    LandedOnWall
    ==============================================================
    */
    
    void LandedOnWall(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_mantisAirToRightWall:
            BlendAnimation(anim_mantisAttackRightWall2, 2.0f, 6.0f);
            break;
            
        case anim_mantisAirToLeftWall:
            BlendAnimation(anim_mantisAttackLeftWall2, 2.0f, 6.0f);
            break;
        }
    }
    
    /*
    ==============================================================
    UpdateStage
    ==============================================================
    */
    
    void UpdateStage(void)
    {
        float healthPercnt = float(self.Health()*100) / float(m_initialHealth);
        
        if(healthPercnt < m_pStageMode[MSI_HEALTHPERCNT])
        {
            m_stage++;
            if(m_stage >= int(g_mantisStages.length()))
            {
                m_stage = int(g_mantisStages.length()-1);
            }
            
            @m_pStageMode = g_mantisStages[m_stage];
            m_animSpeed = m_pStageMode[MSI_SPEED];
            
            m_floorAttackCounter = 0;
            m_CloseFloorAttackCounter = 0;
            m_leftWallAttackCounter = 0;
            m_rightWallAttackCounter = 0;
            m_ceilingAttackCounter = 0;
            
            m_bDamageStunned = true;
            
            switch(m_stage)
            {
            case 1:
                m_bBashAllWalls = true;
                self.RunFxEvent("Mantis_GlowYellow");
                @m_pFloorAttack = @g_mantisFloorAttack_Stage2;
                @m_pCloseFloorAttack = @g_mantisFloorCloseAttack_Stage2;
                @m_pLeftWallAttack = g_mantisLeftAttack_Stage2;
                @m_pRightWallAttack = g_mantisRightAttack_Stage2;
                break;
            case 2:
                self.RunFxEvent("Mantis_GlowPurple");
                @m_pFloorAttack = @g_mantisFloorAttack_Stage3;
                @m_pCloseFloorAttack = @g_mantisFloorCloseAttack_Stage3;
                @m_pLeftWallAttack = g_mantisLeftAttack_Stage3;
                @m_pRightWallAttack = g_mantisRightAttack_Stage3;
                @m_pCeilingAttack = g_mantisCeilingAttack_Stage3;
                break;
            case 3:
                self.RunFxEvent("Mantis_GlowRed");
                @m_pCeilingAttack = g_mantisCeilingAttack_Stage4;
                break;
            }
            
            BlendAnimation(anim_mantisPain, 2.0f, 6.0f, false);
            self.Flags() |= AF_INVINCIBLE;
        }
    }
    
    /*
    ==============================================================
    SetGlobalState
    ==============================================================
    */
    
    void SetGlobalState(void)
    {
        int wallBits = 0;
        
        GameVariables.SetValue("mantisState", "" + m_state);
        GameVariables.SetValue("mantisHealth", "" + self.Health());
        GameVariables.SetValue("mantisSector", "" + self.SectorIndex());
        GameVariables.SetValue("mantisOrigin", self.Origin().ToString());
        GameVariables.SetValue("mantisYaw", "" + self.Yaw());
        GameVariables.SetValue("mantisActive", ((self.Flags() & AF_DISABLED) != 0) ? "0" : "1");
        
        for(uint i = 0; i < g_wallsDestroyed.length(); ++i)
        {
            if(g_wallsDestroyed[i])
            {
                wallBits |= (1 << i);
            }
        }
        
        GameVariables.SetValue("mantisWallBits", "" + wallBits);
    }
    
    /*
    ==============================================================
    GetGlobalState
    ==============================================================
    */
    
    void GetGlobalState(void)
    {
        int wallBits = 0;
        int active = 0;
        int state, sector, health;
        float yaw;
        kVec3 vOrigin;
        int resetboss = 0;
        
        GameVariables.GetInt("g_resetboss", resetboss);
        if(resetboss != 0)
        {
            SetGlobalState();
        }
        
        GameVariables.GetInt("mantisState", state);
        GameVariables.GetInt("mantisHealth", health);
        GameVariables.GetInt("mantisSector", sector);
        GameVariables.GetVector("mantisOrigin", vOrigin);
        GameVariables.GetFloat("mantisYaw", yaw);
        GameVariables.GetInt("mantisWallBits", wallBits);
        GameVariables.GetInt("mantisActive", active);
        
        for(uint i = 0; i < g_wallsDestroyed.length(); ++i)
        {
            if((wallBits & (1 << i)) != 0)
            {
                g_wallsDestroyed[i] = true;
            }
        }
        
        if(active == 1)
        {
            self.Flags() &= ~(AF_HIDDEN|AF_DISABLED);
            
            if(m_state != MS_DEAD)
            {
                self.AnimState().Set(anim_mantisGroundIdle, 4.0f, ANF_ROOTMOTION);
                self.SetHeadTrackTarget(Player.Actor().CastToActor());
                PlayLoop.TagActorForBossBar(self);
            }
            else
            {
                self.Flags() |= AF_IGNORESOUNDEVENTS;
                self.AnimState().Set(anim_mantisDeath, 8.0f, ANF_ROOTMOTION);
                self.AnimState().SetLastFrame();
                self.SetTarget(null);
            }
            
            Game.CallDelayedMapScript(1, self, 1.0f);
            
            self.Gravity() = 0.5f;
            self.ClipFlags() &= ~CF_NOSTEPDOWN;
            
            self.Health() = health;
            self.Origin() = vOrigin;
            self.SetSector(sector);
            self.Yaw() = yaw;
            m_state = state;
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if((self.Flags() & AF_DISABLED) != 0)
        {
            return;
        }
        
        if(m_state == MS_DEAD)
        {
            return;
        }
        
        if(Player.Actor().Health() <= 0 || Player.Locked())
        {
            if(!m_bTargetDead)
            {
                SetGlobalState();
                m_bTargetDead = true;
            }
            return;
        }
        
        switch(m_state)
        {
        case MS_GROUNDCHASE:
            if(m_bBashAllWalls)
            {
                m_state = MS_BASHWALL;
                Sys.Warning("mantis tried to chase target while m_bBashAllWalls==true!");
                GetWallBashPoint();
            }
            else
            {
                GetTargetPoint();
            }
            break;
            
        case MS_BASHWALL:
            GetWallBashPoint();
            break;
        }
        
        CheckTurning();
        CheckWallsToBash();
        
        switch(self.AnimState().PlayingID())
        {
        case anim_mantisWake:
            if(self.AnimState().Stopped())
            {
                self.SetHeadTrackTarget(Player.Actor().CastToActor());
                GroundChase();
            }
            break;
        
        case anim_mantisPain:
            if(self.AnimState().Stopped())
            {
                self.Flags() &= ~AF_INVINCIBLE;
                m_bDamageStunned = false;
                GroundChase();
            }
            break;
            
        case anim_mantisChargeForward:
        case anim_mantisRightWallToGround:
        case anim_mantisLeftWallToGround:
        case anim_mantisAirToGround:
        case anim_mantisCeilingToGround:
        case anim_mantisDamageBack:
        case anim_mantisDamageRight:
        case anim_mantisDamageLeft:
            if(self.AnimState().Stopped())
            {
                GroundChase();
            }
            break;
            
        case anim_mantisGroundToRightWall:
        case anim_mantisGroundToLeftWall:
            CheckLeapFromGround();
            break;
            
        case anim_mantisRightWallToAirLoop:
        case anim_mantisLeftWallToAirLoop:
            CheckWallToLandOn();
            break;
            
        case anim_mantisAirToRightWall:
        case anim_mantisAirToLeftWall:
            LandedOnWall();
            break;
            
        case anim_mantisRightWallToAir:
            BlendLoopingAnimation(anim_mantisRightWallToAirLoop, 1.0f, 3.0f);
            break;
            
        case anim_mantisLeftWallToAir:
            BlendLoopingAnimation(anim_mantisLeftWallToAirLoop, 1.0f, 3.0f);
            break;
            
        case anim_mantisGroundToCeiling:
        case anim_mantisRightWallToCeiling:
        case anim_mantisLeftWallToCeiling:
            if(self.AnimState().Stopped())
            {
                CeilingChase();
            }
            break;
            
        case anim_mantisCeilingIdle:
        case anim_mantisCeilingForward:
        case anim_mantisCeilingStandTurnRight:
        case anim_mantisCeilingStandTurnLeft:
        case anim_mantisAttackCeiling1:
        case anim_mantisAttackCeiling2:
            CeilingChase();
            break;
            
        case anim_mantisWallRightIdle:
        case anim_mantisWallLeftIdle:
        case anim_mantisAttackRightWall1:
        case anim_mantisAttackRightWall2:
        case anim_mantisAttackLeftWall1:
        case anim_mantisAttackLeftWall2:
            CheckGroundToLeapOn();
            break;
        
        case anim_mantisAttackGround1:
        case anim_mantisAttackGround2:
        case anim_mantisSlowAttack:
        case anim_aiMelee1:
        case anim_mantisAttackGroundMelee:
            if(self.AnimState().Blending())
            {
                TurnAngles(Math::Deg2Rad(45.0f), self.GetTurnYaw(m_goalOrigin));
            }
            // fall through
        case anim_mantisGroundIdle:
        case anim_mantisGroundStandTurnRight:
        case anim_mantisGroundStandTurnLeft:
        case anim_mantisGroundForward:
            GroundChase();
            break;
            
        case anim_mantisGroundToAir:
            BlendLoopingAnimation(anim_mantisAirLoop, 1.0f, 3.0f);
            break;
            
        case anim_mantisAirLoop:
            FlyChase();
            break;
        }
        
        if(self.AnimState().PlayingID() != anim_mantisAirLoop)
        {
            m_pitchTilt = Math::NLerp(m_pitchTilt, Math::Deg2Rad(4.0f), 0.0f);
            m_rollTilt = Math::NLerp(m_rollTilt, Math::Deg2Rad(4.0f), 0.0f);
        }
        
        self.Pitch() = m_pitchTilt;
        self.Roll() = -m_rollTilt;
    }
    
    /*
    ==============================================================
    OnBeginLevel
    ==============================================================
    */
    
    void OnBeginLevel(void)
    {
        m_initialHealth = 3000;
        self.Health() = m_initialHealth;
        self.SetTarget(Player.Actor().CastToActor());
        
        self.Flags() |= (AF_DISABLED|AF_HIDDEN);
        
        for(uint i = 0; i < g_wallsDestroyed.length(); ++i)
        {
            g_wallsDestroyed[i] = false;
        }
    }
    
    /*
    ==============================================================
    OnPostBeginLevel
    ==============================================================
    */
    
    void OnPostBeginLevel(void)
    {
        GetGlobalState();
    }
    
    /*
    ==============================================================
    OnActivate
    ==============================================================
    */
    
    void OnActivate(void)
    {
        self.Flags() &= ~(AF_HIDDEN|AF_DISABLED);
        self.AnimState().Set(anim_mantisWake, 4.0f, ANF_ROOTMOTION);
        
        Game.CallDelayedMapScript(1, self, 1.0f);
        PlayLoop.TagActorForBossBar(self);
        
        self.Gravity() = 0.5f;
        self.ClipFlags() &= ~CF_NOSTEPDOWN;
    }
    
    /*
    ==============================================================
    OnDamage
    ==============================================================
    */
    
    void OnDamage(kActor @instigator, kDictMem @damageDef, const int damage)
    {
        bool bValue = false;
        
        if(self.Health() > 0 && !m_bDamageStunned)
        {
            self.RunFxEvent("Mantis_Hit");
        }
        
        if(self.Health() <= 0 || m_state == MS_BASHWALL)
        {
            return;
        }
        
        if(damageDef is null)
        {
            return;
        }
        
        if(damageDef.GetBool("bCauseSpecialAnimation", bValue) && bValue)
        {
            float diff;
            
            if(!self.RandomDecision(2))
            {
                return;
            }
            
            switch(self.AnimState().PlayingID())
            {
            case anim_mantisGroundIdle:
            case anim_mantisGroundStandTurnRight:
            case anim_mantisGroundStandTurnLeft:
            case anim_mantisGroundForward:
                break;
                
            default:
                return;
            }
            
            diff = (Player.Actor().Yaw() - self.Yaw() - Math::Deg2Rad(45.0f)) / Math::Deg2Rad(90.0f);
            
            switch(int(diff) & 3)
            {
            case 0:
                BlendAnimation(anim_mantisDamageRight, 1.5f, 4.0f, false);
                break;
            case 1:
                BlendAnimation(anim_mantisChargeForward, 1.5f, 4.0f, false);
                break;
            case 2:
                BlendAnimation(anim_mantisDamageBack, 1.5f, 4.0f, false);
                break;
            case 3:
                BlendAnimation(anim_mantisDamageLeft, 1.5f, 4.0f, false);
                break;
            }
        }
    }
    
    /*
    ==============================================================
    OnDeath
    ==============================================================
    */
    
    void OnDeath(kActor @killer, kDictMem @damageDef)
    {
        self.SetTarget(null);
        self.SetHeadTrackTarget(null);
        self.Velocity().Clear();
        self.Movement().Clear();
        
        FlightToGround();
        
        m_state = MS_DEAD;
        SetGlobalState();
    }
}
