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

#include "scripts/common.txt"

enum campaingerState
{
    CMS_IDLE        = 0,
    CMS_CHASE,
    CMS_STUNNED,
    CMS_SNIPING,
    CMS_DEATH,
    CMS_KILLING,
    
    NUM_CAMPAINGER_STATES
}

enum campaignerTauntSounds
{
    CTS_NONE = 0, // NB: was "pathetic" in original code, but is dummied out
    CTS_DIE,
    CTS_RAGE,
    CTS_LAUGH,
    
    NUM_CAMPAIGNER_TAUNTS
}

enum campaignerTauntAttacks
{
   CTA_TALK = 200,
   CTA_RAGE,
   CTA_VICTORY
}

enum campaignerGlobalState
{
   CGS_IDLE = 0, // has not been initialized before
   CGS_ACTIVE,   // fighting the player
   CGS_DEAD      // dead
}

const kVec3 campaingerTeleSpot1(768.0f, 890.88f, 619.52f);
const kVec3 campaingerTeleSpot2(778.24f, -675.84f, 619.52f);
const kVec3 campaingerTeleSpot3(975.0f, 100.0f, 300.0f);
const kVec3 campaingerTeleSpot4(-1245.0f, 131.0f, 589.0f);

//-----------------------------------------------------------------------------
//
// Campaigner
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokCampainger
==============================================================
*/

final class TurokCampainger : TurokEnemy
{
    float goalDist;                 // distance to goal origin
    kVec3 goalOrigin;               // goal origin to chase at
    kAngle desiredYaw;              // direction yaw that it wants to turn to
    kAngle lookAtYaw;               // direction yaw that it wants to target at
    int state;
    bool bTurning;                  // playing a turning animation?
    int attackCount;
    int curAttack;
    int selectedAttack;
    bool bRepeatAttack;
    bool bTook25PercentDamage;
    bool bTook50PercentDamage;
    bool bTook75PercentDamage;
    int initialHealth;
    int stunTime;
    int jumpState;
    bool bTeleportingSound;
    bool bTeleported;
    float spreadAngle;
    float spreadTime;
    int boltTime;
    int soundTime;
    int playerWeapon;
    int tauntTimer;                 // period between verbal taunts
    int lastTaunt;                  // last taunt sound played
    int globalState;                // global state of Campaigner
    int globalHealth;               // global health value
    
    TurokCampainger(kActor @actor)
    {
        goalDist = 0;
        desiredYaw = 0;
        lookAtYaw = 0;
        bTurning = false;
        bRepeatAttack = false;
        attackCount = 0;
        selectedAttack = 0;
        curAttack = 0;
        bTook25PercentDamage = false;
        bTook50PercentDamage = false;
        bTook75PercentDamage = false;
        initialHealth = 0;
        stunTime = 0;
        jumpState = 0;
        bTeleportingSound = false;
        bTeleported = false;
        spreadAngle = 0;
        spreadTime = 0;
        boltTime = 0;
        soundTime = 240;
        playerWeapon = -1;
        state = CMS_IDLE;
        tauntTimer = 240;
        lastTaunt = CTS_NONE;
        globalState = CGS_IDLE;
        globalHealth = 0;
        super(actor);
        //self.Scale() = kVec3(0.75f, 0.75f, 0.75f);
        //self.Scale() = kVec3(1.0f, 1.0f, 1.0f);
    }
    
    /*
    ==============================================================
    GetGlobalState
    ==============================================================
    */
    
    void GetGlobalState(void)
    {
        int resetboss = 0;
        GameVariables.GetInt("g_resetboss", resetboss);
        
        if(resetboss != 0)
        {
            SetGlobalState();
        }

        GameVariables.GetInt("campaignerState",  globalState);
        GameVariables.GetInt("campaignerHealth", globalHealth);
        
        if(globalState != CGS_IDLE)
        {
            // reset state tracking vars so they don't fire CheckStunned again
            kRenderModel @model = self.RenderModel();

            if(globalHealth < int(float(initialHealth) * 0.75f))
            {
                bTook25PercentDamage = true;
                model.SetTexture(13, 1);
                model.SetTexture(17, 1);
                model.SetTexture(1, 1);
            }
            if(globalHealth < int(float(initialHealth) * 0.5f))
            {
                bTook50PercentDamage = true;
                model.SetTexture(5, 1);
                model.SetTexture(11, 1);
                model.SetTexture(12, 1);
                model.SetTexture(16, 1);
            }
            if(globalHealth < int(float(initialHealth) * 0.25f))
            {
                bTook75PercentDamage = true;
                self.ModelVariation() = 1;
            }
            
            // if dead, die instantly; otherwise, set health
            if(globalHealth <= 0)
            {
                globalState = CGS_DEAD;
                self.InflictGenericDamage(self, self.Health());
            }
            else
            {
                self.Health() = globalHealth;
            }
        }
    }
    
    /*
    ==============================================================
    SetGlobalState
    ==============================================================
    */
    
    void SetGlobalState(void)
    {
        GameVariables.SetValue("campaignerState",  "" + globalState);
        GameVariables.SetValue("campaignerHealth", "" + self.Health());
    }
    
    /*
    ==============================================================
    IsPlayerActive
    ==============================================================
    */
    bool IsPlayerActive(void)
    {
        if(state == CMS_IDLE) // do not interrupt startup
        {
            return false;
        }
            
        return (Player.Actor().Health() > 0 && !Player.Locked());
    }
    
    /*
    ==============================================================
    ScreenShake
    ==============================================================
    */
    
    void ScreenShake(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        Player.Actor().RecoilPitch() = -Math::Deg2Rad(w);
    }
    
    /*
    ==============================================================
    SwooshTrail1
    ==============================================================
    */
    
    void SwooshTrail1(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_CampWeap", 19);
    }
    
    /*
    ==============================================================
    SwooshTrail2
    ==============================================================
    */
    
    void SwooshTrail2(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_CampFoot", 4);
        self.RenderModel().AddTrailEffect("Trail_CampFoot", 8);
    }
    
    /*
    ==============================================================
    SwooshTrail3
    ==============================================================
    */
    
    void SwooshTrail3(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_CampLeftHand", 12);
        self.RenderModel().AddTrailEffect("Trail_CampRightHand", 16);
    }
    
    /*
    ==============================================================
    CampaingerSounds1
    ==============================================================
    */
    
    void CampaingerSounds1(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if((Math::Rand() % -2) != 0)
        {
            self.PlaySound("sounds/shaders/generic_180.ksnd");
        }
        else
        {
            self.PlaySound("sounds/shaders/generic_181.ksnd");
        }
    }
    
    /*
    ==============================================================
    CampaingerSounds2
    ==============================================================
    */
    
    void CampaingerSounds2(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if((Math::Rand() % -2) != 0)
        {
            self.PlaySound("sounds/shaders/generic_171.ksnd");
        }
        else
        {
            self.PlaySound("sounds/shaders/generic_172.ksnd");
        }
    }
    
    /*
    ==============================================================
    CampaingerSounds3
    ==============================================================
    */
    
    void CampaingerSounds3(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if((Math::Rand() % -2) != 0)
        {
            self.PlaySound("sounds/shaders/generic_178.ksnd");
        }
        else
        {
            self.PlaySound("sounds/shaders/generic_179.ksnd");
        }
    }
    
    /*
    ==============================================================
    SetBitMask1
    ==============================================================
    */
    
    void SetBitMask1(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        jumpState |= 1;
    }
    
    /*
    ==============================================================
    SetBitMask2
    ==============================================================
    */
    
    void SetBitMask2(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        jumpState |= 2;
    }
    
    /*
    ==============================================================
    CampaingerDeath
    ==============================================================
    */
    
    void CampaingerDeath(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.SpawnFx("fx/generic_224.kfx", kVec3(x, y, z));
        self.PlaySound("sounds/shaders/explosion_2.ksnd");
        
        PlayEventSound("sounds/shaders/campainger_death.ksnd", 195);
    }
    
    /*
    ==============================================================
    CheckBlendFx
    ==============================================================
    */
    
    void CheckBlendFx(const int anim)
    {
        switch(anim)
        {
        case anim_aiMelee10:
            // energy blast attack
            self.RunFxEvent("Campaigner_EnergyBlast");
            break;
        case anim_aiMelee12:
            // super blast attack
            self.RunFxEvent("Campaigner_SuperBlast");
            break;
        case anim_aiMelee13:
            // scatter shot attack
            self.RunFxEvent("Campaigner_ScatterBlast");
            break;
        default:
            break;
        }
    }
    
    /*
    ==============================================================
    BlendAnimation
    ==============================================================
    */
    
    void BlendAnimation(const int anim, const float speed, const float blend, const bool bStoppedOnly = true)
    {
        if(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;
        }
        
        self.AnimState().Blend(anim, speed, blend, ANF_ROOTMOTION);
        
        // check if this animation has an fx event
        CheckBlendFx(anim);
    }
    
    /*
    ==============================================================
    BlendLoopingAnimation
    ==============================================================
    */
    
    void BlendLoopingAnimation(const int anim, const float speed, const float blend, const bool bStoppedOnly = true)
    {
        if(self.AnimState().Blending())
        {
            // don't interrupt while blending
            return;
        }
        
        if(bStoppedOnly && !self.AnimState().Stopped())
        {
            return;
        }
        
        if(self.AnimState().PlayingID() != anim)
        {
            self.AnimState().Blend(anim, speed, blend, ANF_ROOTMOTION|ANF_LOOP);
        }
    }
    
    /*
    ==============================================================
    PlayEventSound
    ==============================================================
    */
    
    void PlayEventSound(const kStr &in sndFile, const int time)
    {
        if(soundTime <= 0)
        {
            soundTime = time;
            self.PlaySound(sndFile);
        }
    }
    
    /*
    ==============================================================
    PlayTauntSound
    ==============================================================
    */
    
    void PlayTauntSound(void)
    {
        if(tauntTimer <= 0 && soundTime <= 0)
        {
            int chance = Math::RandMax(100);
            int sound;
            kStr file;
            
            if(chance > 53)
            {
                sound = CTS_NONE;
            }
            else if(chance > 6)
            {
                sound = CTS_DIE;
                file  = "sounds/shaders/campainger_taunt_2.ksnd";
            }
            else
            {
                sound = CTS_RAGE;
                file  = "sounds/shaders/campainger_rage.ksnd";
            }
          
            if(sound != lastTaunt)
            {
                lastTaunt  = sound;
                tauntTimer = 240 + Math::RandMax(121);
                if(sound != CTS_NONE)
                {
                    PlayEventSound(file, 240);
                }
            }
        }
    }
    
    /*
    ==============================================================
    PlayLaughSound
    ==============================================================
    */
    
    void PlayLaughSound(void)
    {
        if(lastTaunt == CTS_LAUGH)
        {
            PlayTauntSound();
        }
        else
        {
            lastTaunt = CTS_LAUGH;
            PlayEventSound("sounds/shaders/campainger_shorting_out.ksnd", 240);
        }
    }
    
    /*
    ==============================================================
    CheckTurning
    ==============================================================
    */
    
    void CheckTurning(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Walk:
        case anim_aiTurn_B_Run:
        case anim_aiTurn_L_Wound:
        case anim_aiTurn_R_Wound:
        case anim_aiTurn_B_Wound:
            bTurning = true;
            break;
        default:
            bTurning = false;
            break;
        }
    }
    
    /*
    ==============================================================
    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:
        case anim_aiTurn_L_Wound:
        case anim_aiTurn_R_Wound:
        case anim_aiTurn_B_Wound:
            return true;
        }
        
        return false;
    }
    
    /*
    ==============================================================
    IsChasing
    ==============================================================
    */
    
    bool IsChasing(const bool bCheckWounded)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_aiStanding:
        case anim_aiWalking:
        case anim_aiRunning:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Walk:
        case anim_aiTurn_B_Run:
            return true;
            
        case anim_aiWounded:
        case anim_aiTurn_L_Wound:
        case anim_aiTurn_R_Wound:
        case anim_aiTurn_B_Wound:
            return bCheckWounded;
        }
        
        return false;
    }
    
    /*
    ==============================================================
    NeedStun
    ==============================================================
    */
    
    bool NeedStun(void)
    {
        if((!bTook25PercentDamage && self.Health() < int(float(initialHealth) * 0.75f)) ||
           (!bTook50PercentDamage && self.Health() < int(float(initialHealth) * 0.5f))  ||
           (!bTook75PercentDamage && self.Health() < int(float(initialHealth) * 0.25f)))
        {
            return true;
        }
        
        return false;
    }
    
    /*
    ==============================================================
    Stunned
    ==============================================================
    */
    
    void Stunned(void)
    {
        stunTime++;
        
        if((stunTime & 7) == 0)
        {
            self.PlaySound("sounds/shaders/explosion_2.ksnd");
            
            self.SpawnFx("fx/generic_224.kfx",
                kVec3(Math::RandRange(-81.92f, 81.92f),
                      Math::RandRange(-81.92f, 81.92f),
                      Math::RandRange(30.72f, 81.92f)));
        }
        
        if(stunTime == 100)
        {
            kRenderModel @model = self.RenderModel();
            
            if(bTook75PercentDamage)
            {
                self.ModelVariation() = 1;
            }
            else if(bTook50PercentDamage)
            {
                model.SetTexture(5, 1);
                model.SetTexture(11, 1);
                model.SetTexture(12, 1);
                model.SetTexture(16, 1);
            }
            else if(bTook25PercentDamage)
            {
                model.SetTexture(13, 1);
                model.SetTexture(17, 1);
                model.SetTexture(1, 1);
            }
        }
        
        if(stunTime >= 150)
        {
            state = CMS_CHASE;
            self.RunFxEvent("Campaigner_Normal");
        }
    }
    
    /*
    ==============================================================
    BecomeStunned
    ==============================================================
    */
    
    void BecomeStunned(void)
    {
        stunTime = 0;
        state = CMS_STUNNED;
        self.PlaySound("sounds/shaders/campainger_death.ksnd");
        BlendAnimation(anim_campaingerCrumble, 2.6f, 6.0f, false);
        self.RunFxEvent("Campaigner_ShortingOut");
    }
    
    /*
    ==============================================================
    GetTargetPoint
    ==============================================================
    */
    
    void GetTargetPoint(void)
    {
        goalOrigin = Player.Actor().Origin();
        goalDist = Math::Sqrt(self.DistanceToPoint(goalOrigin));
        
        desiredYaw = self.GetAvoidanceAngle(goalOrigin, 1.5f) - self.Yaw();
        lookAtYaw = self.GetTurnYaw(goalOrigin);
    }
    
    /*
    ==============================================================
    UpdateAttackState
    ==============================================================
    */
    
    bool UpdateAttackState(void)
    {
        if(!bRepeatAttack && self.AnimState().TrackTime() >= 1.0f)
        {
            bRepeatAttack = true;
            attackCount--;
        }
        else if(self.AnimState().TrackTime() < 1.0f)
        {
            bRepeatAttack = false;
        }
        
        return (attackCount <= 0);
    }
    
    /*
    ==============================================================
    FaceTarget
    ==============================================================
    */
    
    void FaceTarget(const float angle)
    {
        TurnAngles(Math::Deg2Rad(angle), lookAtYaw);
    }
    
    /*
    ==============================================================
    JumpTowardsTarget
    ==============================================================
    */
    
    void JumpTowardsTarget(void)
    {
        if(!IsPlayerActive())
        {
            return;
        }
        
        if((jumpState & 1) != 0)
        {
            self.Velocity().x = (goalOrigin.x - self.Origin().x) * 0.1f;
            self.Velocity().y = (goalOrigin.y - self.Origin().y) * 0.1f;
        }
        
        if((jumpState & 2) != 0)
        {
            self.Velocity().Clear();
        }
    }
    
    /*
    ==============================================================
    Teleporting
    ==============================================================
    */
    
    void Teleporting(void)
    {
        if(!bTeleportingSound && self.AnimState().TrackTime() >= 0.3f)
        {
            self.PlaySound("sounds/shaders/campainger_spell.ksnd");
            self.RunFxEvent("Campaigner_TeleportOut");
            bTeleportingSound = true;
            
            if(state != CMS_SNIPING)
            {
                if((Math::Rand() % -2) != 0)
                {
                    state = CMS_SNIPING;
                }
            }
            else
            {
                state = CMS_CHASE;
            }
        }
        
        if(!bTeleported && self.AnimState().TrackTime() >= 0.625f)
        {
            float x;
            float y;
            float z;
            
            if(state == CMS_SNIPING)
            {
                self.Gravity() = 0.0f;
                self.Flags() &= ~AF_NONSHOOTABLE;
                
				int tSpot = Math::RandMax(5);
                if (tSpot == 0) {
                    x = campaingerTeleSpot1.x;
                    y = campaingerTeleSpot1.y;
                    z = campaingerTeleSpot1.z;
                }
                else if (tSpot == 1) {
                    x = campaingerTeleSpot2.x;
                    y = campaingerTeleSpot2.y;
                    z = campaingerTeleSpot2.z;
                }
                else if (tSpot == 2) {
                    x = campaingerTeleSpot3.x;
                    y = campaingerTeleSpot3.y;
                    z = campaingerTeleSpot3.z;
                }
                else {
                    x = campaingerTeleSpot4.x;
                    y = campaingerTeleSpot4.y;
                    z = campaingerTeleSpot4.z;
                }
            }
            else
            {
                x = goalOrigin.x + Math::Sin(Player.Actor().Yaw()) * 153.6f;
                y = goalOrigin.y + Math::Cos(Player.Actor().Yaw()) * 153.6f;
                z = self.FloorHeight();
                
                self.Gravity() = 0.5f;
                
                if((self.Flags() & AF_NONSHOOTABLE) == 0)
                {
                    self.Flags() |= AF_NONSHOOTABLE;
                }
            }
            
            self.PlaySound("sounds/shaders/campainger_spell.ksnd");
            self.RunFxEvent("Campaigner_TeleportIn");
            bTeleported = true;
            
            self.MoveToPosition(x, y);
            self.Origin().z = z;
        }
    }
    
    /*
    ==============================================================
    BoltStrike
    ==============================================================
    */
    
    void BoltStrike(void)
    {
        if(self.AnimState().TrackTime() < 0.35f)
        {
            return;
        }
        
        float dist = goalDist - 30.72f;
        float angle;
        float x, y, z;
        
        if(boltTime == 0)
        {
            // start glowing fiery orange
            self.RunFxEvent("Campaigner_ExplosionStart");
        }
        
        boltTime++;
        
        if((boltTime & 7) != 0)
        {
            return;
        }
        
        if(dist < 153.6f) { dist = 153.6f; }
        if(dist > 819.2f) { dist = 819.2f; }
        
        spreadTime += 3.75f;
        
        angle = spreadAngle + ((spreadTime - 6.0) * Math::Deg2Rad(12.0f));
        
        x = Math::Sin(angle) * (dist * 2.0f);
        y = Math::Cos(angle) * (dist * 2.0f);
        z = 0;
        
        kVec3 org(x, y, z);
        
        self.MeleeObject("Damage_Blunt_15", org, 2.0f);
        self.SpawnFx("fx/generic_65.kfx", org);
        
        self.PlaySound("sounds/shaders/explosion_2.ksnd");
    }
    
    /*
    ==============================================================
    CheckForcedBlend
    ==============================================================
    */
    
    bool CheckForcedBlend(void)
    {
        const int animID = self.AnimState().PlayingID();
        
        if(bTurning && self.AnimState().TrackTime() >= 0.7f)
        {
            return true;
        }
        
        if(animID >= anim_aiMelee1 && animID <= anim_aiMelee9)
        {
            return (self.AnimState().TrackTime() >= 0.8f);
        }
        
        if(animID >= anim_aiMelee10 && animID <= anim_aiMelee13)
        {
            return (self.AnimState().TrackTime() >= 0.825f);
        }
        
        switch(animID)
        {
        case anim_aiDeathKnockback1:
        case anim_aiDeathKnockback2:
        case anim_aiDeathKnockback3:
        case anim_aiDeathKnockback4:
        case anim_aiDodgeCrouch:
        case anim_campaingerCrumble:
            return (self.AnimState().TrackTime() >= 0.7f);
            
        case anim_aiAltMelee2:
        case anim_aiAltMelee3:
        case anim_aiAltMelee1:
        case anim_aiMelee14:
        case anim_longHunterTaunt:
        case anim_aiTeleportOut:
            return (self.AnimState().TrackTime() >= 0.8f);
            
        case CTA_TALK:
        case CTA_RAGE:
        case CTA_VICTORY:
            return (self.AnimState().TrackTime() >= 0.938f || IsPlayerActive());            
        }
        
        return self.AnimState().Stopped();
    }
    
    /*
    ==============================================================
    TauntAttack
    ==============================================================
    */
    
    void TauntAttack(void)
    {        
        switch(Math::RandMax(3))
        {
        case 0:
            // NB: this state plays the laugh sfx
            BlendAnimation(CTA_TALK, 2.6f, 8.0f, true);
            break;
        case 1:
            PlayTauntSound();
            BlendAnimation(CTA_RAGE, 2.6f, 8.0f, true);
            break;
        case 2:
            PlayTauntSound();
            BlendAnimation(CTA_VICTORY, 2.6f, 8.0f, true);
            break;
        }        
    }
    
    /*
    ==============================================================
    MeleeAttack
    ==============================================================
    */
    
    void MeleeAttack(void)
    {
        if(!IsPlayerActive())
        {
            TauntAttack();
            return;
        }
        
        if(NeedStun())
        {
            // don't get stuck meleeing if need to get stunned
            return;
        }
        
        PlayTauntSound();
        switch(Math::RandMax(9))
        {
        case 0:
            BlendAnimation(anim_aiMelee1, 2.6f, 8.0f, false);
            break;
        case 1:
            BlendAnimation(anim_aiMelee2, 2.6f, 8.0f, false);
            break;
        case 2:
            BlendAnimation(anim_aiMelee3, 2.6f, 8.0f, false);
            break;
        case 3:
            BlendAnimation(anim_aiMelee4, 2.6f, 8.0f, false);
            break;
        case 4:
            BlendAnimation(anim_aiMelee5, 2.6f, 8.0f, false);
            break;
        case 5:
            BlendAnimation(anim_aiMelee6, 2.6f, 8.0f, false);
            break;
        case 6:
            BlendAnimation(anim_aiMelee7, 2.6f, 8.0f, false);
            break;
        case 7:
            BlendAnimation(anim_aiMelee8, 2.6f, 8.0f, false);
            break;
        case 8:
            BlendAnimation(anim_aiMelee9, 2.6f, 8.0f, false);
            break;
        }
    }
    
    /*
    ==============================================================
    CheckRangeAttack
    ==============================================================
    */
    
    void CheckRangeAttack(void)
    {
        int anim;
              
        if(goalDist <= 143.36f || Math::RandMax(100) < 98)
        {
            return;
        }

        if(!IsPlayerActive())
        {
            TauntAttack();
            return;
        }
        
        switch(self.AnimState().PlayingID())
        {  
        case anim_aiWalking:
        case anim_aiRunning:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Walk:
        case anim_aiTurn_B_Run:
        case anim_aiWounded:
        case anim_aiTurn_L_Wound:
        case anim_aiTurn_R_Wound:
        case anim_aiTurn_B_Wound:
            PlayTauntSound();
            anim = anim_aiMelee10 + Math::RandMax(4);
            break;
            
        default:
            return;
        }
        
        BlendAnimation(anim, 2.6f, 8.0f, false);
        
        if(anim == anim_aiMelee11)
        {
            spreadAngle = (self.Yaw() + desiredYaw) + Math::pi;
            spreadTime = -6.0f;
            boltTime = 0;
        }
    }
    
    /*
    ==============================================================
    CheckBackFlip
    ==============================================================
    */
    
    void CheckBackFlip(void)
    {
        if(!IsPlayerActive())
        {
            return;
        }
        
        if(!IsChasing(false))
        {
            return;
        }
        
        if(!self.RandomDecision(2))
        {
            return;
        }
        
        if(Math::RandMax(100) >= 97 && Math::Fabs(lookAtYaw) <= Math::Deg2Rad(10.0f))
        {
            jumpState = 0;
            bTeleportingSound = false;
            bTeleported = false;
        
            if((Math::Rand() % -2) != 0)
            {
                BlendAnimation(anim_aiDodgeCrouch, 2.6f, 8.0f, false);
            }
            else
            {
                BlendAnimation(anim_longHunterTaunt, 2.6f, 8.0f, false);
            }
        }
    }
    
    /*
    ==============================================================
    CheckSpecialAttack
    ==============================================================
    */
    
    void CheckSpecialAttack(void)
    {
        if(goalDist < 614.4f)
        {
            CheckBackFlip();
            return;
        }

        if(!IsPlayerActive())
        {
            TauntAttack();
            return;
        }
        
        if(!self.RandomDecision(3))
        {
            return;
        }
        
        if(!IsChasing(true))
        {
            return;
        }
        
        if(!self.CheckPosition(goalOrigin))
        {
            return;
        }
        
        jumpState = 0;
        bTeleportingSound = false;
        bTeleported = false;
        
        if((Math::Rand() % -2) != 0)
        {
            BlendAnimation(anim_aiTeleportOut, 2.0f, 8.0f, false);
        }
        else
        {
            BlendAnimation(anim_longHunterTaunt, 2.6f, 8.0f, false);
        }
    }
    
    /*
    ==============================================================
    CheckShield
    ==============================================================
    */
    
    void CheckShield(void)
    {
        if(self.ImpactType() == IT_FORCEFIELD)
        {
            if((PlayLoop.Ticks() & 31) == 0)
            {
                self.SpawnFx("fx/campainger_shield.kfx", Math::vecZero);
                
                if((PlayLoop.Ticks() & 255) == 0)
                {
                    self.PlaySound("sounds/shaders/energy_shield_hum.ksnd");
                }
            }
        }
        
        if(self.AnimState().PlayingID() != anim_aiTeleportOut)
        {
            if(playerWeapon != Player.CurrentWeapon())
            {
                playerWeapon = Player.CurrentWeapon();
                
                switch(playerWeapon)
                {
                case 6:     // pulse rifle
                case 8:     // grenade launcher
                case 11:    // accelerator
                case 12:    // fusion cannon
				case 13:    // Chrono
				case 14:    // laser rifle
                    //PlayEventSound("sounds/shaders/campainger_shorting_out.ksnd", 360);
                    if(self.ImpactType() == IT_FORCEFIELD)
                    {
                        PlayTauntSound();
                    }
                    else
                    {
                        PlayLaughSound();
                    }
                    self.PlaySound("sounds/shaders/energy_shield_hum.ksnd");
                    
                    self.ImpactType() = IT_FORCEFIELD;
                    
                    if(IsRunning() && self.RandomDecision(1))
                    {
                        BlendAnimation(anim_activate, 2.6f, 8.0f, false);
                    }
                    
                    break;
                    
                default:
                    if(self.ImpactType() != IT_FLESH_HUMAN)
                    {
                        self.PlaySound("sounds/shaders/generic_189.ksnd");
                        self.ImpactType() = IT_FLESH_HUMAN;
                    }
                    break;
                }
            }
        }
    }
    
    /*
    ==============================================================
    CheckStun
    ==============================================================
    */
    
    void CheckStun(void)
    {
        if(!IsChasing(true) || self.AnimState().Blending())
        {
            return;
        }
        
        if(!bTook25PercentDamage && self.Health() < int(float(initialHealth) * 0.75f))
        {
            bTook25PercentDamage = true;
            BecomeStunned();
        }
        else if(!bTook50PercentDamage && self.Health() < int(float(initialHealth) * 0.5f))
        {
            bTook50PercentDamage = true;
            BecomeStunned();
        }
        else if(!bTook75PercentDamage && self.Health() < int(float(initialHealth) * 0.25f))
        {
            bTook75PercentDamage = true;
            BecomeStunned();
        }
    }
    
    /*
    ==============================================================
    ChaseTarget
    ==============================================================
    */
    
    void ChaseTarget(void)
    {
        if(state == CMS_IDLE)
        {
            return;
        }
        
        if(goalDist > 204.8f || IsRunning())
        {
            if(bTurning)
            {
                TurnAngles(Math::Deg2Rad(2.0f), desiredYaw);
            }
            else
            {
                TurnAngles(Math::Deg2Rad(10.0f), desiredYaw);
            }
            
            if(desiredYaw >= -Math::Deg2Rad(150.0f))
            {
                if(desiredYaw >= -Math::Deg2Rad(55.0f))
                {
                    if(desiredYaw > Math::Deg2Rad(55.0f))
                    {
                        BlendAnimation(
                            bTook75PercentDamage ? anim_aiTurn_R_Wound : anim_aiTurn_R_Run,
                            2.6f, 6.0f);
                    }
                }
                else
                {
                    BlendAnimation(
                        bTook75PercentDamage ? anim_aiTurn_L_Wound : anim_aiTurn_L_Run,
                        2.6f, 6.0f);
                }
            }
            else
            {
                BlendAnimation(
                    bTook75PercentDamage ? anim_aiTurn_B_Wound : anim_aiTurn_B_Run,
                    2.6f, 6.0f);
            }
            
            if(goalDist >= 245.76f || goalDist <= 225.28f)
            {
                if(Math::Fabs(desiredYaw) <= Math::Deg2Rad(35.0f) || self.AnimState().Stopped())
                {
                    BlendLoopingAnimation(
                        bTook75PercentDamage ? anim_aiWounded : anim_aiRunning,
                        2.6f, 6.0f, false);
                }
            }
            else if(Math::Fabs(desiredYaw) <= Math::Deg2Rad(5.0f))
            {
                BlendAnimation(anim_aiMelee14, 2.6f, 4.0f, false);
                return;
            }
        }
        else if(goalDist > 143.36f)
        {
            TurnAngles(Math::Deg2Rad(5.0f), desiredYaw);
            
            if(desiredYaw >= -Math::Deg2Rad(150.0f))
            {
                if(desiredYaw >= -Math::Deg2Rad(35.0f))
                {
                    if(desiredYaw > Math::Deg2Rad(35.0f))
                    {
                        BlendAnimation(
                            bTook75PercentDamage ? anim_aiTurn_R_Wound : anim_aiTurn_R_Walk,
                            4.0f, 10.0f, false);
                    }
                }
                else
                {
                    BlendAnimation(
                        bTook75PercentDamage ? anim_aiTurn_L_Wound : anim_aiTurn_L_Walk,
                        4.0f, 10.0f);
                }
            }
            else
            {
                BlendAnimation(anim_aiTurn_B_Walk, 4.0f, 10.0f);
            }
            
            if(Math::Fabs(desiredYaw) <= Math::Deg2Rad(25.0f) || self.AnimState().Stopped())
            {
                BlendLoopingAnimation(
                    bTook75PercentDamage ? anim_aiWounded : anim_aiWalking,
                    4.0f, 10.0f);
            }
        }
        
        if(goalDist <= 143.36f)
        {
            if(self.AnimState().PlayingID() != anim_aiTurn_B_Stand)
            {
                if(lookAtYaw >= -Math::Deg2Rad(150.0f) && lookAtYaw < Math::Deg2Rad(165.0f))
                {
                    if(lookAtYaw >= -Math::Deg2Rad(20.0f))
                    {
                        if(lookAtYaw > Math::Deg2Rad(20.0f))
                        {
                            BlendAnimation(anim_aiTurn_R_Stand, 2.6f, 10.0f);
                        }
                    }
                    else
                    {
                        BlendAnimation(anim_aiTurn_L_Stand, 2.6f, 10.0f);
                    }
                }
                else
                {
                    BlendAnimation(anim_aiTurn_B_Stand, 2.6f, 10.0f);
                }
            }
            
            if(Math::Fabs(lookAtYaw) <= Math::Deg2Rad(20.0f))
            {
                MeleeAttack();
            }
        }
        
        if(self.AnimState().Stopped())
        {
            BlendLoopingAnimation(anim_aiStanding, 4.0f, 10.0f, false);
        }
    }
    
    /*
    ==============================================================
    SnipeTarget
    ==============================================================
    */
    
    void SnipeTarget(void)
    {
        if(!IsPlayerActive())
        {
            return;
        }

        GetTargetPoint();
        
        switch(self.AnimState().PlayingID())
        {
        case anim_aiTeleportOut:
            if(CheckForcedBlend())
            {
                BlendAnimation(anim_aiMelee10, 2.6f, 8.0f, false);
            }
            else
            {
                Teleporting();
            }
            break;
            
        case anim_aiMelee10:
            FaceTarget(7.0f);
            if(CheckForcedBlend())
            {
                bTeleportingSound = false;
                bTeleported = false;
                
                BlendAnimation(anim_aiTeleportOut, 2.6f, 8.0f, false);
            }
            break;
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        // killing the player?
        if(Player.Actor().Health() <= 0)
        {
            state = CMS_KILLING;
            return;
        }
        
        switch(state)
        {
        case CMS_IDLE:
            GetTargetPoint();
            break;
            
        case CMS_STUNNED:
            Stunned();
            return;
            
        case CMS_SNIPING:
            SnipeTarget();
            return;
            
        case CMS_CHASE:
            GetTargetPoint();
            CheckSpecialAttack();
            CheckRangeAttack();
            break;
            
        case CMS_DEATH:
            return;
        }
        
        CheckShield();
        CheckTurning();
        CheckStun();
        
        switch(self.AnimState().PlayingID())
        {  
        case anim_aiStanding:
        case anim_aiWalking:
        case anim_aiRunning:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Walk:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Walk:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Walk:
        case anim_aiTurn_B_Run:
        case anim_aiWounded:
        case anim_aiTurn_L_Wound:
        case anim_aiTurn_R_Wound:
        case anim_aiTurn_B_Wound:
            ChaseTarget();
            break;
            
        case anim_aiTeleportOut:
            if(CheckForcedBlend())
            {
                ChaseTarget();
            }
            else
            {
                Teleporting();
            }
            break;
            
        case anim_longHunterTaunt:
            if(CheckForcedBlend())
            {
                ChaseTarget();
            }
            else
            {
                JumpTowardsTarget();
            }
            break;
            
        case anim_aiMelee11:
            if(CheckForcedBlend())
            {
                ChaseTarget();
                self.RunFxEvent("Campaigner_ExplosionEnd");
            }
            else
            {
                BoltStrike();
            }
            break;
            
        case anim_aiDodgeCrouch:
        case anim_aiMelee14:
            if(CheckForcedBlend())
            {
                ChaseTarget();
            }
            break;
            
        case anim_aiMelee1:
        case anim_aiMelee2:
        case anim_aiMelee3:
        case anim_aiMelee4:
        case anim_aiMelee5:
        case anim_aiMelee6:
        case anim_aiMelee7:
        case anim_aiMelee8:
        case anim_aiMelee9:
        case anim_aiMelee10:
        case anim_aiMelee12:
        case anim_aiMelee13:
            FaceTarget(7.0f);
            if(CheckForcedBlend())
            {
                ChaseTarget();
            }
            break;
            
        case anim_event07:
            if(self.AnimState().Stopped())
            {
                BlendLoopingAnimation(anim_campaingerRage, 4.0f, 8.0f, false);
            }
            break;
            
        case anim_aiDeathKnockback1:
        case anim_aiDeathKnockback2:
        case anim_aiDeathKnockback3:
        case anim_aiDeathKnockback4:
        case anim_special_event01:
        case anim_special_event02:
        case anim_special_event03:
        case anim_special_event04:
            if(CheckForcedBlend())
            {
                if(Math::RandMax(100) >= 90)
                {
                    jumpState = 0;
                    BlendAnimation(anim_longHunterTaunt, 2.6f, 8.0f, false);
                }
                else
                {
                    ChaseTarget();
                }
            }
            break;
            
        case CTA_TALK:
        case CTA_RAGE:
        case CTA_VICTORY:
        case anim_campaingerCrumble:
            if(CheckForcedBlend())
            {
                state = CMS_CHASE;
                ChaseTarget();
            }
            break;
        }
        
        soundTime--;
        tauntTimer--;
    }
    
    /*
    ==============================================================
    OnBeginLevel
    ==============================================================
    */
    
    void OnBeginLevel(void)
    {
        initialHealth = 5000;
        self.Health() = initialHealth;
        self.SetTarget(Player.Actor().CastToActor());
        self.SetHeadTrackTarget(Player.Actor().CastToActor());

        PlayLoop.TagActorForBossBar(self);
        
        GetGlobalState();
		//self.Scale() = kVec3(1.0f, 1.0f, 1.0f); //original scale is 0.5
		SetScaleOnHealth();
		//self.Scale() = kVec3(0.75f, 0.75f, 0.75f);
        if(globalState == CGS_IDLE)
        {
            // first time, laugh at player
            self.AnimState().Set(anim_activate, 4.0f, ANF_ROOTMOTION);
            globalState = CGS_ACTIVE;
        }
        else if(globalState == CGS_ACTIVE)
        {
            state = CMS_CHASE;
        }
    }
    
	void SetScaleOnHealth() {
		float lastScale = self.Scale().x;
		float newScale = 0.75f;
		bool bStartedIntro = false;
		GameVariables.GetBool("bStartedHubIntro", bStartedIntro);
		if (!bStartedIntro) {
			return;
		}
		if (self.Health() > 0 and globalState != CGS_DEAD and Player.Actor().Health() > 0) {
		//if (self.Health() > 0) {
			if (self.Health() <= 1000) {
				newScale = 1.75f;
			} else if (self.Health() <= 2000) {
				newScale = 1.5f;
			} else if (self.Health() <= 3000) {
				newScale = 1.25f;
			} else if (self.Health() <= 4000) {
				newScale = 1.0f;
			}
		}
		
		if (newScale != lastScale) {
			self.Scale() = kVec3(newScale, newScale, newScale);
		}
		if (newScale > lastScale) {
			self.RunFxEvent("CGrow_Scale");
		}
	}
    /*
    ==============================================================
    OnEndLevel
    ==============================================================
    */
    
    void OnEndLevel(void)
    {
		TurokEnemy::OnEndLevel();
        SetGlobalState();
    }
    
    /*
    ==============================================================
    OnDamage
    ==============================================================
    */
    
    void OnDamage(kActor @instigator, kDictMem @damageDef, const int damage)
    {
        bool bValue = false;
        float diff;
        
        if(self.Health() <= 0)
        {
            return;
        }
        
        if(self.ImpactType() == IT_FORCEFIELD)
        {
            PlayEventSound("sounds/shaders/campainger_shorting_out.ksnd", 360);
            return;
        }
        
        if(state == CMS_STUNNED)
        {
            self.Health() += damage;
            return;
        }
		
		if (damageDef !is null and damageDef.GetBool("bNoHitCamp", bValue) && bValue == true)
        {
			self.Health() += damage;
			return;
		}
		
		SetScaleOnHealth();
		
		self.RunFxEvent("C_Hit");
            
        if(IsChasing(true))
        {
            if(!self.RandomDecision(5))
            {
                if(self.RandomDecision(4))
                {
                    switch(Math::RandMax(4))
                    {
                    case 0:
                        BlendAnimation(anim_special_event01, 2.0f, 6.0f, false);
                        break;
                    case 1:
                        BlendAnimation(anim_special_event02, 2.0f, 6.0f, false);
                        break;
                    case 2:
                        BlendAnimation(anim_special_event03, 2.0f, 6.0f, false);
                        break;
                    case 3:
                        BlendAnimation(anim_special_event04, 2.0f, 6.0f, false);
                        break;
                    }
                }
                return;
            }
        }
        else
        {
            return;
        }
        
        diff = (Player.Actor().Yaw() - self.Yaw() - Math::Deg2Rad(45.0f)) * 0.6366197723675814f;
        
        switch(int(diff) & 3)
        {
        case 0:
            BlendAnimation(anim_aiDeathKnockback1, 2.6f, 8.0f, false);
            break;
        case 1:
            BlendAnimation(anim_aiDeathKnockback3, 2.6f, 8.0f, false);
            break;
        case 2:
            BlendAnimation(anim_aiDeathKnockback4, 2.6f, 8.0f, false);
            break;
        case 3:
            BlendAnimation(anim_aiDeathKnockback2, 2.6f, 8.0f, false);
            break;
        }
    }
    
    /*
    ==============================================================
    OnDeath
    ==============================================================
    */
    
    void OnDeath(kActor @killer, kDictMem @damageDef)
    {
		self.Scale() = kVec3(0.75f, 0.75f, 0.75f);
        self.SetTarget(null);
        self.SetHeadTrackTarget(null);
        
        state = CMS_DEATH;
        globalState = CGS_DEAD;
    }
}
