//
// 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:
//      LongHunter Boss Logic
//

#include "scripts/common.txt"

enum longHunterState
{
    LHS_IDLE        = 0,
    LHS_OBSERVING,
    LHS_ATTACK,
    LHS_DEAD,
    
    NUM_LONGHUNTER_STATES
}

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

enum longHunterStageInfo
{
    LHSI_HEALTHPERCNT   = 0,
    LHSI_SPEED,
    LHSI_EVADETIME,
    LHSI_EVADESKIPS,
    LHSI_NOEVADETIME,
    LHSI_FLINCHPERCNT,
    LHSI_RUNTIME,
    LHSI_GUNFIRECOUNT
}

// health %, speed, evade time, evade skips, no evade time, flinch %, run time before attack, gun fire total
const array<float> g_longhuntStage_1 = { 75.0f, 1.0f,  1.0f, 4.0f, 4.0f, 20.0f, 2.0f,  2.0f };
const array<float> g_longhuntStage_2 = { 50.0f, 0.95f, 2.0f, 3.0f, 5.0f, 12.0f, 1.75f, 2.0f };
const array<float> g_longhuntStage_3 = { 25.0f, 0.9f,  2.0f, 2.0f, 6.0f, 6.0f,  1.5f,  3.0f };
const array<float> g_longhuntStage_4 = { 0.0f,  0.85f, 3.0f, 1.0f, 7.0f, 4.0f,  1.0f,  3.0f };

const array<const array<float>@> g_longhuntStages =
{
    @g_longhuntStage_1,
    @g_longhuntStage_2,
    @g_longhuntStage_3,
    @g_longhuntStage_4
};

//-----------------------------------------------------------------------------
//
// Anim Weights
//
//-----------------------------------------------------------------------------

const array<int> g_longhuntMeleeAnims = { anim_aiMelee1, anim_aiMelee3, anim_longHunterTaunt };
const array<int> g_longhuntMeleeWeights = { 50, 50, 5 };
const array<float> g_longhuntMeleeSpeed = { 2.5, 2.0f, 3.0f };

const array<int> g_longhuntRangeAnims = { anim_aiMelee4, anim_aiMelee5, anim_aiMelee6 };
const array<int> g_longhuntRangeWeights = { 50, 15, 35 };
const array<float> g_longhuntRangeSpeed = { 4.0f, 1.14f, 4.0f };

const array<int> g_longhuntWalkAnims = { anim_aiMelee2, anim_aiMelee4 };
const array<int> g_longhuntWalkWeights = { 50, 25 };
const array<float> g_longhuntWalkSpeed = { 2.667f, 4.0f };

//-----------------------------------------------------------------------------
//
// Hunter
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokLongHunter
==============================================================
*/

final class TurokLongHunter : TurokEnemy
{
    int m_gotoPoint;
    int m_state;
    int m_stage;
    int m_rangeAttackLoops;
    int m_standingTauntTime;
    float m_runTime;
    float m_observeTime;
    float m_goalDistSq;
    float m_goalDist;
    float m_evadeTime;
    int m_evadeActive;
    int m_evadeCount;
    float m_startHealth;
    float m_animSpeed;
    kVec3 m_vGoalOrigin;
    kAngle m_desiredYaw;
    kAngle m_lookAtYaw;
    kAngle m_randomYaw;
    kActor @m_pObserveActor;
    const array<float> @m_pStageMode;
    
    TurokLongHunter(kActor @actor)
    {
        m_gotoPoint = 0;
        m_observeTime = 0;
        m_goalDistSq = 0;
        m_goalDist = 0;
        m_desiredYaw = 0;
        m_lookAtYaw = 0;
        m_randomYaw = 0;
        m_runTime = 0;
        m_evadeTime = 0;
        m_evadeActive = 0;
        m_animSpeed = 1;
        m_rangeAttackLoops = 0;
        m_stage = 0;
        m_startHealth = 1;
        m_evadeCount = 0;
        m_standingTauntTime = 0;
        @m_pStageMode = g_longhuntStages[0];
        @m_pObserveActor = null;
        m_state = LHS_IDLE;
        
        super(actor);
    }
    
    /*
    ==============================================================
    SwooshTrail2
    ==============================================================
    */
    
    void SwooshTrail2(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_LongFootEvade", 3);
        self.RenderModel().AddTrailEffect("Trail_LongFootEvade", 8);
    }
    
    /*
    ==============================================================
    SwooshTrail3
    ==============================================================
    */
    
    void SwooshTrail3(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_LongHand", 12);
        self.RenderModel().AddTrailEffect("Trail_LongHand", 16);
    }
    
    /*
    ==============================================================
    SwooshRightFoot
    ==============================================================
    */
    
    void SwooshRightFoot(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_LongFoot", 8);
    }
    
    /*
    ==============================================================
    SwooshRightHand
    ==============================================================
    */
    
    void SwooshRightHand(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_LongHand", 18);
    }
    
    /*
    ==============================================================
    PickAnimation
    ==============================================================
    */
    
    void PickAnimation(const array<int> &anims, const array<int> &weights, const array<float> &speeds)
    {
        if(anims.length() != weights.length())
        {
            return;
        }
        
        int randomSum = 0;
        uint i;
        int count = 0;
        array<int> rndArray;
        array<int> animArray;
        array<float> animSpeed;
        
        for(i = 0; i < weights.length(); i++)
        {
            if(self.AnimState().CheckAnimID(anims[i]))
            {
                rndArray.insertLast(randomSum);
                animArray.insertLast(anims[i]);
                animSpeed.insertLast(speeds[i]);
                randomSum += weights[i];
                count++;
            }
        }
        
        if(randomSum == 0 || count == 0)
        {
            return;
        }
        
        int r = Math::RandMax(randomSum);
        
        for(i = count-1; i >= 0; i--)
        {
            if(rndArray[i] <= r)
            {
                break;
            }
        }
        
        BlendAnimation(animArray[i], animSpeed[i], 8.0f, false);
    }
    
    /*
    ==============================================================
    BlendAnimation
    ==============================================================
    */
    
    void BlendAnimation(const int anim, const float speed, const float blend, const bool bStoppedOnly = true)
    {
        if(bStoppedOnly && !self.AnimState().Stopped())
        {
            return;
        }
        else if(!bStoppedOnly && self.AnimState().PlayingID() == anim)
        {
            return;
        }
        
        if(self.AnimState().PlayingID() == anim)
        {
            self.AnimState().Set(anim, speed, ANF_ROOTMOTION);
            return;
        }
        
        self.AnimState().Blend(anim, speed*m_animSpeed, blend*m_animSpeed, ANF_ROOTMOTION);
    }
    
    /*
    ==============================================================
    IsRunning
    ==============================================================
    */
    
    bool IsRunning(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_aiRunning:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Run:
            return true;
        }
        
        return false;
    }
    
    /*
    ==============================================================
    IsWalking
    ==============================================================
    */
    
    bool IsWalking(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_aiWalking:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_B_Walk:
            return true;
        }
        
        return false;
    }
    
    /*
    ==============================================================
    CheckForEvade
    ==============================================================
    */
    
    void CheckForEvade(void)
    {
        bool bCanEvade = false;
        
        m_evadeTime -= GAME_DELTA_TIME;
        
        if(m_evadeTime < 0)
        {
            // swap evade states
            m_evadeActive ^= 1;
            
            // reset evade/non-evade time
            if(m_evadeActive == 1)
            {
                m_evadeTime = m_pStageMode[LHSI_EVADETIME];
            }
            else
            {
                m_evadeTime = m_pStageMode[LHSI_NOEVADETIME];
            }
        }
        
        // can we evade in this mode?
        switch(self.AnimState().PlayingID())
        {
        case anim_longHunterTaunt:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Walk:
        case anim_aiTurn_B_Run:
        case anim_aiStanding:
        case anim_aiWalking:
        case anim_aiRunning:
            bCanEvade = true;
            break;
        }
        
        if(!bCanEvade)
        {
            return;
        }
        
        if(m_evadeActive == 1 && ++m_evadeCount >= int(m_pStageMode[LHSI_EVADESKIPS]) &&
            InPlayerProjectilePath(Math::Deg2Rad(10.0f)))
        {
            m_evadeCount = 0;
            
            if(m_lookAtYaw >= 0.0f)
            {
                BlendAnimation(anim_aiDodgeRight, 4.0f, 8.0f, false);
            }
            else
            {
                BlendAnimation(anim_aiDodgeLeft, 4.0f, 8.0f, false);
            }
        }
    }
    
    /*
    ==============================================================
    CheckForWalkRunAttack
    ==============================================================
    */
    
    bool CheckForWalkRunAttack(void)
    {
        if(m_state != LHS_ATTACK)
        {
            return false;
        }
        
        bool bWalking = IsWalking();
        bool bRunning = IsRunning();
        int anim = self.AnimState().PlayingID();
        
        if(bWalking)
        {
            m_runTime += (GAME_DELTA_TIME*0.7f)*2;
        }
        else if(bRunning)
        {
            m_runTime += (GAME_DELTA_TIME*0.7f);
        }
        
        if(bWalking)
        {
            kVec3 vDir = (Player.Actor().Origin() - self.Origin()).Normalize();
            kVec3 vMoveDir = Player.Actor().Movement();
            
            if(vMoveDir.Dot(vDir) >= (0.2f*GAME_SCALE))
            {
                bWalking = false;
                bRunning = true;
            }
        }
        
        // been walking/running too long?
        if(m_runTime > m_pStageMode[LHSI_RUNTIME])
        {
            m_runTime = 0;
            
            if(Math::RandMax(100) >= 80)
            {
                self.PlaySound("sounds/shaders/longhunter_taunt_1.ksnd");
            }
            
            if(bWalking)
            {
                PickAnimation(g_longhuntWalkAnims, g_longhuntWalkWeights, g_longhuntWalkSpeed);
            }
            else if(bRunning)
            {
                PickAnimation(g_longhuntRangeAnims, g_longhuntRangeWeights, g_longhuntRangeSpeed);
            }
        }
        
        return (self.AnimState().PlayingID() != anim);
    }
    
    /*
    ==============================================================
    GetObservationPoint
    ==============================================================
    */
    
    void GetObservationPoint(void)
    {
        float ang = float(m_gotoPoint) * Math::Deg2Rad(45.0f);
        
        m_vGoalOrigin.x = Math::Sin(ang) * (168*GAME_SCALE);
        m_vGoalOrigin.y = Math::Cos(ang) * (168*GAME_SCALE);
        m_vGoalOrigin.z = self.Origin().z;
        
        m_observeTime += GAME_FRAME_TIME;
        
        if(m_observeTime >= 300.0f)
        {
            m_observeTime = 0;
            m_gotoPoint = (m_gotoPoint + 1) & 7;
            SetGlobalState(LHGS_OBSERVING);
        }
        
        m_goalDistSq = self.DistanceToPoint(m_vGoalOrigin);
        m_goalDist = Math::Sqrt(m_goalDistSq);
        
        m_desiredYaw = self.GetAvoidanceAngle(m_vGoalOrigin, 1.5f) - self.Yaw();
        m_lookAtYaw = self.GetTurnYaw(Player.Actor().Origin());
    }
    
    /*
    ==============================================================
    GetTargetPoint
    ==============================================================
    */
    
    void GetTargetPoint(void)
    {
        float dist;
        float x, y;
        kVec3 dir;
        kVec3 org;
        
        if(self.AnimState().PlayingID() == anim_aiMelee6)
        {
            m_desiredYaw = 0;
            m_lookAtYaw = 0;
            return;
        }
        
        org = Player.Actor().Origin();
        
        dist = (Math::Sqrt(self.DistanceToPoint(org)) - (15*GAME_SCALE)) * 2.0f;
        if(dist > (80*GAME_SCALE))
        {
            dist = (80*GAME_SCALE);
        }
        else if(dist < 0)
        {
            dist = 0;
        }
        
        dir = (org - self.Origin()).Normalize();
        
        x = dir.y;
        y = dir.x;
        
        m_vGoalOrigin.x = Math::Sin(m_randomYaw) * x * dist + org.x;
        m_vGoalOrigin.y = Math::Cos(m_randomYaw) * y * dist + org.y;
        m_vGoalOrigin.z = self.Origin().z;
        
        m_randomYaw += Math::Deg2Rad(5.0f) * 0.5f;
        
        m_goalDistSq = self.DistanceToPoint(org);
        m_goalDist = Math::Sqrt(m_goalDistSq);
        
        m_desiredYaw = self.GetAvoidanceAngle(m_vGoalOrigin, 1.5f) - self.Yaw();
        m_lookAtYaw = self.GetTurnYaw(Player.Actor().Origin());
    }
    
    /*
    ==============================================================
    StandTurn
    ==============================================================
    */
    
    void StandTurn(void)
    {
        bool bInterrupt = !(self.AnimState().PlayingID() == anim_aiStanding);
        
        if(self.AnimState().PlayingID() != anim_aiTurn_B_Stand)
        {
            if(m_lookAtYaw >= Math::Deg2Rad(-100.0f))
            {
                if(m_lookAtYaw > Math::Deg2Rad(-15.0f))
                {
                    if(m_lookAtYaw > Math::Deg2Rad(15.0f))
                    {
                        BlendAnimation(anim_aiTurn_R_Stand, 4.0f, 8.0f, bInterrupt);
                    }
                }
                else
                {
                    BlendAnimation(anim_aiTurn_L_Stand, 4.0f, 8.0f, bInterrupt);
                }
            }
            else
            {
                BlendAnimation(anim_aiTurn_B_Stand, 4.0f, 8.0f, bInterrupt);
            }
        }
        
        if(Math::Fabs(m_lookAtYaw) <= Math::Deg2Rad(8.0f))
        {
            BlendAnimation(anim_aiStanding, 4.0f, 8.0f, false);
        }
    }
    
    /*
    ==============================================================
    WalkTurn
    ==============================================================
    */
    
    void WalkTurn(void)
    {
        bool bInterrupt = !(self.AnimState().PlayingID() == anim_aiStanding);
        
        if(self.AnimState().PlayingID() != anim_aiTurn_B_Walk)
        {
            TurnAngles(Math::Deg2Rad(6.0f), m_desiredYaw);
            
            if(m_desiredYaw >= Math::Deg2Rad(-150.0f))
            {
                if(m_desiredYaw > Math::Deg2Rad(-20.0f))
                {
                    if(m_desiredYaw > Math::Deg2Rad(20.0f))
                    {
                        BlendAnimation(anim_aiTurn_R_Walk, 4.0f, 8.0f, bInterrupt);
                    }
                }
                else
                {
                    BlendAnimation(anim_aiTurn_L_Walk, 4.0f, 8.0f, bInterrupt);
                }
            }
            else
            {
                BlendAnimation(anim_aiTurn_B_Walk, 4.0f, 8.0f, bInterrupt);
            }
        }
        
        if(Math::Fabs(m_desiredYaw) <= Math::Deg2Rad(30.0f))
        {
            BlendAnimation(anim_aiWalking, 4.0f, 8.0f);
        }
    }
    
    /*
    ==============================================================
    RunTurn
    ==============================================================
    */
    
    void RunTurn(void)
    {
        bool bInterrupt = !(self.AnimState().PlayingID() == anim_aiStanding);
        
        if(self.AnimState().PlayingID() != anim_aiTurn_B_Run)
        {
            TurnAngles(Math::Deg2Rad(12.0f), m_desiredYaw);
            
            if(m_desiredYaw >= Math::Deg2Rad(-150.0f))
            {
                if(m_desiredYaw > Math::Deg2Rad(-25.0f))
                {
                    if(m_desiredYaw > Math::Deg2Rad(25.0f))
                    {
                        BlendAnimation(anim_aiTurn_R_Run, 5.0f, 8.0f, bInterrupt);
                    }
                }
                else
                {
                    BlendAnimation(anim_aiTurn_L_Run, 5.0f, 8.0f, bInterrupt);
                }
            }
            else
            {
                BlendAnimation(anim_aiTurn_B_Run, 5.0f, 8.0f, bInterrupt);
            }
        }
        
        if(Math::Fabs(m_desiredYaw) <= Math::Deg2Rad(35.0f))
        {
            BlendAnimation(anim_aiRunning, 5.0f, 8.0f);
        }
    }
    
    /*
    ==============================================================
    IsPlayerLocked
    ==============================================================
    */
    
    bool IsPlayerLocked(void)
    {
        return (Player.Actor().Health() > 0 && Player.Locked());
    }
    
    /*
    ==============================================================
    DoLockedTaunt
    ==============================================================
    */
    
    void DoLockedTaunt(void)
    {
        if(!Player.Locked() || m_state == LHS_OBSERVING)
        {
            return;
        }
        --m_standingTauntTime;
        if(m_standingTauntTime <= 0)
        {
            BlendAnimation(anim_longHunterTaunt, 4.0f, 8.0f, false);
            self.PlaySound("sounds/shaders/longhunter_taunt_1.ksnd");
            m_standingTauntTime = 250 + Math::RandMax(111);
        }
        else
        {
            BlendAnimation(anim_aiStanding, 4.0f, 8.0f, false);
        }
    }
    
    /*
    ==============================================================
    DoAttacks
    ==============================================================
    */
    
    void DoAttacks(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Run:
            return;
        }
        
        // don't attack during cinematics unless the player is dead
        if(IsPlayerLocked())
        {
            DoLockedTaunt();
            return;
        }
        
        if(m_goalDist <= (12*GAME_SCALE) && Math::Fabs(m_desiredYaw) <= Math::Deg2Rad(15.0f))
        {
            PickAnimation(g_longhuntMeleeAnims, g_longhuntMeleeWeights, g_longhuntMeleeSpeed);
        }
        else if(CheckForWalkRunAttack())
        {
            m_rangeAttackLoops = int(m_pStageMode[LHSI_GUNFIRECOUNT]);
        }
    }
    
    /*
    ==============================================================
    Chase
    ==============================================================
    */
    
    void Chase(void)
    {
        float dist = (6*GAME_SCALE);
        
        // don't chase toward player during cinematics unless he is dead
        if(IsPlayerLocked() && m_state != LHS_OBSERVING)
        {
            DoLockedTaunt();
            return;
        }
        
        if(m_goalDist > (20*GAME_SCALE))
        {
            RunTurn();
        }
        else if(m_goalDist > dist)
        {
            WalkTurn();
        }
        
        if(m_goalDist <= dist)
        {
            StandTurn();
        }
        
        if(m_state == LHS_ATTACK)
        {
            DoAttacks();
        }
        
        if(self.AnimState().Stopped())
        {
            self.AnimState().Blend(anim_aiStanding, 4.0f, 8.0f, ANF_ROOTMOTION);
        }
    }
    
    /*
    ==============================================================
    FindClosestPoint
    ==============================================================
    */
    
    void FindClosestPoint(void)
    {
        const float an  = self.Yaw() + Math::Deg2Rad(90.0f);
        const float x   = self.Origin().x - Math::Sin(an) * (150*GAME_SCALE);
        const float y   = self.Origin().y - Math::Cos(an) * (150*GAME_SCALE);
        
        const float maxDist = 500*GAME_SCALE;
        float maxDistSq = maxDist*maxDist;
        
        for(int i = 0; i < 8; ++i)
        {
            float ang   = float(i) * Math::Deg2Rad(45.0f);
            float dx    = x - Math::Sin(ang) * (168*GAME_SCALE);
            float dy    = y - Math::Cos(ang) * (168*GAME_SCALE);
            
            float dist = self.DistanceToPoint(dx, dy, self.Origin().z);
            
            if(dist < maxDistSq)
            {
                maxDistSq = dist;
                m_gotoPoint = i;
            }
        }
        
        m_state = LHS_OBSERVING;
        @m_pObserveActor = World.GetActorByTID(1141);
        
        self.SetHeadTrackTarget(Player.Actor().CastToActor());
        self.AnimState().Blend(anim_aiTurn_L_Run, 4.0f, 10.0f, ANF_ROOTMOTION);
        
        SetGlobalState(LHGS_OBSERVING);
    }
    
    /*
    ==============================================================
    LoopAttack
    ==============================================================
    */
    
    void LoopAttack(const int anim, const float speed)
    {
        if(self.AnimState().Stopped())
        {
            m_rangeAttackLoops--;
        }
        else
        {
            TurnAngles(Math::Deg2Rad(12.0f), m_lookAtYaw);
            return;
        }
        
        if(m_rangeAttackLoops > 0)
        {
            self.AnimState().Blend(anim, speed, 4.0f, ANF_ROOTMOTION);
            TurnAngles(Math::Deg2Rad(20.0f), m_lookAtYaw);
        }
        else
        {
            DoAttacks();
        }
        
        if(self.AnimState().Stopped())
        {
            Chase();
        }
    }
    
    /*
    ==============================================================
    UpdateStage
    ==============================================================
    */
    
    void UpdateStage(void)
    {
        float healthPercnt = float(self.Health()*100) / m_startHealth;
        
        if(healthPercnt < m_pStageMode[LHSI_HEALTHPERCNT])
        {
            m_stage++;
            if(m_stage >= int(g_longhuntStages.length()))
            {
                m_stage = int(g_longhuntStages.length()-1);
            }
            
            @m_pStageMode = g_longhuntStages[m_stage];
            m_animSpeed = m_pStageMode[LHSI_SPEED];
        }
    }
    
    /*
    ==============================================================
    SetGlobalState
    ==============================================================
    */
    
    void SetGlobalState(const int stateVal)
    {
        GameVariables.SetValue("longHunterState", "" + stateVal);
        SetGlobalProperties();
    }
    
    /*
    ==============================================================
    SetGlobalProperties
    ==============================================================
    */
    
    void SetGlobalProperties(void)
    {
        GameVariables.SetValue("longHunterHealth", "" + self.Health());
        GameVariables.SetValue("longHunterSector", "" + self.SectorIndex());
        GameVariables.SetValue("longHunterOrigin", self.Origin().ToString());
        GameVariables.SetValue("longHunterYaw", "" + self.Yaw());
        GameVariables.SetValue("longHunterObservationPoint", "" + m_gotoPoint);
    }
    
    /*
    ==============================================================
    GetGlobalState
    ==============================================================
    */
    
    void GetGlobalState(int &out stateVal, int &out stateHealth, kVec3 &out vStateOrigin,
                        int &out stateSector, float &out stateYaw)
    {
        int resetboss = 0;
        GameVariables.GetInt("g_resetboss", resetboss);
        
        if(resetboss != 0)
        {
            SetGlobalState(LHGS_NEW);
            SetGlobalProperties();
        }

        GameVariables.GetInt("longHunterState", stateVal);
        GameVariables.GetInt("longHunterHealth", stateHealth);
        GameVariables.GetInt("longHunterSector", stateSector);
        GameVariables.GetVector("longHunterOrigin", vStateOrigin);
        GameVariables.GetFloat("longHunterYaw", stateYaw);
        
        if(stateVal != LHGS_NEW)
        {
            GameVariables.GetInt("longHunterObservationPoint", m_gotoPoint);
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        switch(m_state)
        {
        case LHS_OBSERVING:
            GetObservationPoint();
            if(m_pObserveActor.Health() <= 0)
            {
                m_state = LHS_ATTACK;
                self.SetHeadTrackTarget(Player.Actor().CastToActor());
                SetGlobalState(LHGS_INCOMBAT);
            }
            break;
            
        case LHS_ATTACK:
            GetTargetPoint();
            CheckForEvade();
            break;
            
        case LHS_IDLE:
            if(self.AnimState().PlayingID() == anim_longHunterTaunt)
            {
                if(self.AnimState().TrackTime() >= 0.8f)
                {
                    FindClosestPoint();
                }
            }
            return;
            
        case LHS_DEAD:
            return;
        }
        
        UpdateStage();
        
        switch(self.AnimState().PlayingID())
        {
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Run:
            if(Math::Fabs(m_desiredYaw) <= Math::Deg2Rad(15.0f))
            {
                BlendAnimation(anim_aiRunning, 4.0f, 8.0f, false);
                break;
            }
            // fall through
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Walk:
        case anim_aiTurn_B_Run:
        case anim_aiStanding:
        case anim_aiWalking:
        case anim_aiRunning:
            Chase();
            break;
            
        case anim_aiDodgeRight:
        case anim_aiDodgeLeft:
        case anim_aiMelee1:
        case anim_aiMelee2:
        case anim_aiMelee3:
        case anim_longHunterTaunt:
            if(self.AnimState().Stopped())
            {
                DoAttacks();
                
                if(self.AnimState().Stopped())
                {
                    Chase();
                }
            }
            else
            {
                float max = m_goalDist / (4*GAME_SCALE);
                Math::Clamp(max, 2.0f, 50.0f);
                
                TurnAngles(Math::Deg2Rad(max), m_lookAtYaw);
            }
            break;
            
        case anim_aiMelee4:
            LoopAttack(anim_aiMelee4, 4.0f);
            break;
            
        case anim_aiMelee5:
            LoopAttack(anim_aiMelee5, 1.14f);
            break;
            
        case anim_aiMelee6:
            LoopAttack(anim_aiMelee6, 4.0f);
            break;
        }
    }
    
    /*
    ==============================================================
    OnBeginLevel
    ==============================================================
    */
    
    void OnBeginLevel(void)
    {
        int state, sector, health;
        float yaw;
        kVec3 vOrigin;
        
        self.Health() = 1000;
        m_startHealth = float(self.Health());
        self.SetTarget(Player.Actor().CastToActor());
        
        self.AnimState().Set(anim_aiStanding, 4.0f, ANF_ROOTMOTION);
        self.ImpactType() = IT_FORCEFIELD;
        
        GetGlobalState(state, health, vOrigin, sector, yaw);
        
        if(state == LHGS_NEW)
        {
            return;
        }
        
        switch(state)
        {
        case LHGS_OBSERVING:
            FindClosestPoint();
            break;
        case LHGS_INCOMBAT:
            m_state = LHS_ATTACK;
            self.ImpactType() = IT_FLESH_HUMAN;
            Game.CallDelayedMapScript(8, Player.Actor().CastToActor(), 0);
            self.SetHeadTrackTarget(Player.Actor().CastToActor());
            PlayLoop.TagActorForBossBar(self);
            break;
        case LHGS_DEAD:
            self.AnimState().Set(anim_aiDeathStand, 8.0f, ANF_ROOTMOTION);
            self.AnimState().SetLastFrame();
            self.ModelVariation() = 1;
            break;
        }
        
        self.Health() = health;
        self.Origin() = vOrigin;
        self.SetSector(sector);
        self.Yaw() = yaw;
    }
    
    /*
    ==============================================================
    OnEndLevel
    ==============================================================
    */
    
    void OnEndLevel(void)
    {
		TurokEnemy::OnEndLevel();
        SetGlobalProperties();
    }
    
    /*
    ==============================================================
    OnDamage
    ==============================================================
    */
    
    void OnDamage(kActor @instigator, kDictMem @damageDef, const int damage)
    {
        if(self.Health() <= 0)
        {
            return;
        }
        
        if(Math::RandMax(99) > 80)
        {
            self.PlaySound("sounds/shaders/longhunter_taunt_3.ksnd");
        }
    }
    
    /*
    ==============================================================
    OnDeath
    ==============================================================
    */
    
    void OnDeath(kActor @killer, kDictMem @damageDef)
    {
        self.SetTarget(null);
        self.SetHeadTrackTarget(null);
        
        m_state = LHS_DEAD;
        SetGlobalState(LHGS_DEAD);
    }
}
