#include "scripts/common.txt"
//(7) Params 1 -128..127 (If set to 1 then will not do teleport)
//Flags: 1 = No Teleport

final class BP_SpiritCampainger : 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;
    bool bRepeatAttack;
    bool bTook25PercentDamage;
    bool bTook50PercentDamage;
    bool bTook75PercentDamage;
    int initialHealth;
    int stunTime;
    int jumpState;
    float spreadAngle;
    float spreadTime;
    int boltTime;
    int soundTime;
    int playerWeapon;
    int tauntTimer;                 // period between verbal taunts
    int lastTaunt;                  // last taunt sound played
	bool isTargetingPlayer;
	float loseTargetTime;
	kVec3 moveTarget;
	array<BP_WayPoint@> path;
	array<kVec3> pathVec;
	int pathIndex;
	float pathStuckTime;
	kVec3 lastOrigin;
	bool movingToHealth;
	bool ignorePlayer;
	float speedScale;
	bool bTeleportingSound;
	bool bTeleported;
	int modelType;
	BP_WayPoint@ healthWaypoint;
	
	//------------------------------------------------------------------------------------------------------------------------
    BP_SpiritCampainger(kActor @actor)
    {
        super(actor);
        goalDist = 0;
        desiredYaw = 0;
        lookAtYaw = 0;
        bTurning = false;
        bRepeatAttack = false;
        attackCount = 0;
        bTook25PercentDamage = false;
        bTook50PercentDamage = false;
        bTook75PercentDamage = false;
        initialHealth = 0;
        stunTime = 0;
        jumpState = 0;
        spreadAngle = 0;
        spreadTime = 0;
        boltTime = 0;
        soundTime = 240;
        playerWeapon = -1;
        state = CMS_IDLE;
        tauntTimer = 240;
        lastTaunt = CTS_NONE;
        isTargetingPlayer = false;
		loseTargetTime = 0.0f;
		pathIndex = 0;
		lastOrigin = self.Origin();
		movingToHealth = false;
		ignorePlayer = false;
		speedScale = 1.0f;
		bTeleportingSound = false;
		bTeleported = false;
		modelType = 0;
		pickupActors.insertLast(actor);
    }
	//------------------------------------------------------------------------------------------------------------------------
    void GetTargetPoint(void)
    {
		kActor@ pActor = Player.Actor().CastToActor();
		
		bool canSeeTarget = self.CanSee(pActor);
		if (!ignorePlayer && canSeeTarget)
		{
			loseTargetTime = 0.0f;
			if (Math::Fabs(self.GetTurnYaw(pActor.Origin())) < Math::Deg2Rad(35.0f))
			{
				SetAttackTarget();
			}
		}
		
		//if is moving to path and is stuck then rethink
		if (!isTargetingPlayer)
		{
			if (self.Origin().Distance(lastOrigin) < 300.0f)
			{
				pathStuckTime += GAME_DELTA_TIME;
				if (pathStuckTime >= 4.0f)
				{
					lastOrigin = self.Origin();
					pathStuckTime = 0.0f;
					Think();
				}
			}
			else
			{
				lastOrigin = self.Origin();
				pathStuckTime = 0.0f;
			}
		}
		
		if (isTargetingPlayer)
		{
			goalOrigin = pActor.Origin();
		}
		else
		{
			if (path.length() == 0)
			{
				goalOrigin = self.Origin();
				Think();
			}
			else
			{
				//if reached point (within certain distance) then increase pathIndex
				if (self.Origin().Distance(pathVec[pathIndex]) < 96.0f)
				{
					OnReachedWayPoint();
				}
				
				//Set Target Point to waypoint position
				if (path.length() != 0)
				{				
					if (path[pathIndex] !is null)
					{
						//Sys.Print("Path Length: " + int(path.length()) + " PathVec Length: " + int(pathVec.length()) + " pathIndex: " + pathIndex);
						goalOrigin = path[pathIndex].self.Origin();
						//Sys.Print("Done");
					}
					else
					{
						goalOrigin = pathVec[pathIndex];
					}
				}
			}
		}
		
        goalDist = Math::Sqrt(self.DistanceToPoint(goalOrigin));
        desiredYaw = self.GetAvoidanceAngle(goalOrigin, 1.5f) - self.Yaw();
        lookAtYaw = self.GetTurnYaw(goalOrigin);
    }
	//------------------------------------------------------------------------------------------------------------------------
	void OnReachedWayPoint()
	{
		lastOrigin = self.Origin();
		pathStuckTime = 0.0f;
				
		BP_WayPoint@ waypoint = path[pathIndex];

		//check if can see spawner that spawns health nearby and decide if needs that health type
		if (@waypoint != null && !movingToHealth)
		{
			array<BP_SpawnerMP@> targetSpawners;
			int targetItemType = -1;
			for (uint i = 0; i < waypoint.spawners.length(); i++)
			{
				int itemType = waypoint.spawners[i].GetItemType();
				//waypoint has health and has less than 100% health or less than 200% health and pickup is small or ultra
				if (waypoint.spawners[i].HasHealth() &&
					(self.Health() < initialHealth || ((itemType == 400 || itemType == 404) && self.Health() < initialHealth * 2.0f)))
				{
					if (targetItemType == -1)
					{
						targetItemType = itemType;
						targetSpawners.insertLast(waypoint.spawners[i]);
					}
					else if (itemType > targetItemType)
					{
						targetItemType = itemType;
						targetSpawners.resize(0);
						targetSpawners.insertLast(waypoint.spawners[i]);
					}
				}
			}
			
			if (targetItemType != -1)
			{
				BP_SpawnerMP@ targetSpawner = targetSpawners[Math::RandMax(targetSpawners.length())];
				NavigateTo(self.Origin(), targetSpawner.self.Origin(), path, pathVec);
				pathIndex = 0;
				movingToHealth = true;
				return;
			}
		}

		pathIndex++;
		if (pathIndex >= int(path.length())) //On reached end of path
		{
			ClearPath();
			SetIgnorePlayer(false);
			Think();
		}
	}
	//------------------------------------------------------------------------------------------------------------------------
	void ClearPath()
	{
		path.resize(0);
		pathVec.resize(0);
	}
	//------------------------------------------------------------------------------------------------------------------------
	void ThinkHealth()
	{
		//find a waypoint that has health
		@healthWaypoint = null;
		movingToHealth = false;
		for (uint i = 0; i < waypoints.length(); i++)
		{
			BP_WayPoint@ waypoint = waypoints[i];
			for (uint j = 0; j < waypoint.spawners.length(); j++)
			{
				if (waypoint.spawners[j].HasHealth())
				{
					NavigateTo(self.Origin(), waypoint.spawners[j].self.Origin(), path, pathVec);
					pathIndex = 0;
					@healthWaypoint = @waypoint;
					movingToHealth = true;
					return;
				}
			}
		}
		
		//if theres no health then wander
		if (!movingToHealth)
		{
			ThinkWander();
		}
	}
	//------------------------------------------------------------------------------------------------------------------------
	//Goes to random waypoint
	//------------------------------------------------------------------------------------------------------------------------
	void ThinkWander()
	{
		kVec3 destination = waypoints[Math::RandMax(waypoints.length())].self.Origin();
		NavigateTo(self.Origin(), destination, path, pathVec);
		pathIndex = 0;
		movingToHealth = false;
	}
	//------------------------------------------------------------------------------------------------------------------------
	// Called when: lose attack target, stuck, no path and no target, reached end of path
	//------------------------------------------------------------------------------------------------------------------------
	void Think()
	{
		// //if needs health
		// if (self.Health() < initialHealth * 0.5f && Math::RandMax(2) == 0)
		// {
			// ThinkHealth();
			// return;
		// }
		
		ThinkWander();
	}
	//------------------------------------------------------------------------------------------------------------------------
	void SetIgnorePlayer(const bool value)
	{
		ignorePlayer = value;
		if (value)
		{
			isTargetingPlayer = false;
		}
	}
	//------------------------------------------------------------------------------------------------------------------------
	void DoTeleport()
	{
		bTeleportingSound = false;
		bTeleported = false;
		
		BlendAnimation(anim_aiTeleportOut, 2.6f, 8.0f, false);
	}
	//------------------------------------------------------------------------------------------------------------------------
	void UpdateTeleporting()
    {
        if (!bTeleportingSound && self.AnimState().TrackTime() >= 0.3f)
        {
            self.PlaySound("sounds/shaders/campainger_spell.ksnd");
            //self.RunFxEvent("Campaigner_TeleportOut");
            bTeleportingSound = true;
        }
        
        if (!bTeleported && self.AnimState().TrackTime() >= 0.625f)
        {
            float x;
            float y;
            float z;
            
			kVec3 destination = waypoints[Math::RandMax(waypoints.length())].self.Origin();
			
			x = destination.x;
			y = destination.y;
			z = destination.z;
			//x = goalOrigin.x + Math::Sin(Player.Actor().Yaw()) * 153.6f;
			//y = goalOrigin.y + Math::Cos(Player.Actor().Yaw()) * 153.6f;
			//z = self.FloorHeight();
			
            self.PlaySound("sounds/shaders/campainger_spell.ksnd");
            //self.RunFxEvent("Campaigner_TeleportIn");
            bTeleported = true;
            
			self.SetPosition(destination);
            //self.MoveToPosition(x, y);
            //self.Origin().z = z;
			ClearPath();
			ThinkHealth();
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
	void SetAttackTarget()
	{
		isTargetingPlayer = true;
		loseTargetTime = 0.0f;
		self.SetHeadTrackTarget(Player.Actor().CastToActor());
		self.SetTarget(Player.Actor().CastToActor());
	}
	//------------------------------------------------------------------------------------------------------------------------
	void CheckLoseAttackTarget()
	{
		if (ignorePlayer)
		{
			loseTargetTime = 0.0f;
			return;
		}
		loseTargetTime += GAME_DELTA_TIME;
		if (loseTargetTime > 3.0f)
		{
			loseTargetTime = 0.0f;
			isTargetingPlayer = false;
			self.SetTarget(null);
			self.SetHeadTrackTarget(null);
			Think();
		}
	}
	//------------------------------------------------------------------------------------------------------------------------
    bool IsPlayerActive(void)
    {
        if (state == CMS_IDLE) // do not interrupt startup
        {
            return false;
        }
            
        return (Player.Actor().Health() > 0 && !Player.Locked());
    }
	//------------------------------------------------------------------------------------------------------------------------
    void ScreenShake(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        Player.Actor().RecoilPitch() = -Math::Deg2Rad(w);
    }
	//------------------------------------------------------------------------------------------------------------------------
    void SwooshTrail1(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        self.RenderModel().AddTrailEffect("Trail_CampWeap", 19);
    }
	//------------------------------------------------------------------------------------------------------------------------
    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);
    }
	//------------------------------------------------------------------------------------------------------------------------
    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);
    }
	//------------------------------------------------------------------------------------------------------------------------
    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");
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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");
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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");
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void SetBitMask1(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        jumpState |= 1;
    }
	//------------------------------------------------------------------------------------------------------------------------
    void SetBitMask2(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        jumpState |= 2;
    }
	//------------------------------------------------------------------------------------------------------------------------
    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);
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
        }
        
		float speedBonus = 0.0f;
		if (ignorePlayer)
			speedBonus = 0.15f;
        self.AnimState().Blend(anim, speed * (speedScale - speedBonus), blend, ANF_ROOTMOTION);
        
        // check if this animation has an fx event
        CheckBlendFx(anim);
    }
	//------------------------------------------------------------------------------------------------------------------------
    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)
        {
			float speedBonus = 0.0f;
			if (ignorePlayer)
				speedBonus = 0.15f;
            self.AnimState().Blend(anim, speed * (speedScale - speedBonus), blend, ANF_ROOTMOTION|ANF_LOOP);
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void PlayEventSound(const kStr &in sndFile, const int time)
    {
        if(soundTime <= 0)
        {
            soundTime = time;
            self.PlaySound(sndFile);
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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);
                }
            }
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void PlayLaughSound(void)
    {
        if(lastTaunt == CTS_LAUGH)
        {
            PlayTauntSound();
        }
        else
        {
            lastTaunt = CTS_LAUGH;
            PlayEventSound("sounds/shaders/campainger_shorting_out.ksnd", 240);
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
    }
	//------------------------------------------------------------------------------------------------------------------------
    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)
        {
            UpdateModel();
        }
        
        if(stunTime >= 150)
        {
            state = CMS_CHASE;
			ClearPath();
			SetIgnorePlayer(true);
			speedScale -= 0.05f;
			ThinkHealth();
			DoTeleport();
            //self.RunFxEvent("Campaigner_Normal");
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
	void UpdateModel()
	{
        kRenderModel @model = self.RenderModel();
		if(bTook75PercentDamage)
		{
			if (modelType == 3)
				return;
			modelType = 3;
			self.ModelVariation() = 1;
		}
		else if(bTook50PercentDamage)
		{
			if (modelType == 2)
				return;
			modelType = 2;
			model.SetTexture(5, 1);
			model.SetTexture(11, 1);
			model.SetTexture(12, 1);
			model.SetTexture(16, 1);
		}
		else if(bTook25PercentDamage)
		{
			if (modelType == 1)
				return;
			modelType = 1;
			model.SetTexture(13, 1);
			model.SetTexture(17, 1);
			model.SetTexture(1, 1);
		}
	}
	//------------------------------------------------------------------------------------------------------------------------
    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");
    }
	//------------------------------------------------------------------------------------------------------------------------
    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);
    }
	//------------------------------------------------------------------------------------------------------------------------
    void FaceTarget(const float angle)
    {
        TurnAngles(Math::Deg2Rad(angle), lookAtYaw);
    }
	//------------------------------------------------------------------------------------------------------------------------
    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();
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void BoltStrike(void)
    {
		if (!isTargetingPlayer)
		{
			return;
		}

        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");
    }
	//------------------------------------------------------------------------------------------------------------------------
    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();
    }
	//------------------------------------------------------------------------------------------------------------------------
    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;
        }        
    }
	//------------------------------------------------------------------------------------------------------------------------
    void MeleeAttack(void)
    {
		if (!isTargetingPlayer)
		{
			return;
		}
		
        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;
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void CheckRangeAttack(void)
    {
		if (!isTargetingPlayer)
		{
			return;
		}
		
        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();
			//melee10 - shoots bunch of blue plasma at you (keep)
			//melee11 - explosions around that take no damage (remove)
			//melee12 - throws cross attack (keep)
			//melee13 - 6 blue things in air at you (remove keeps hitting walls)
			anim = anim_aiMelee10;
			switch (Math::RandMax(2))
			{
				case 0:
					anim = anim_aiMelee10;
					break;
				case 1:
					anim = anim_aiMelee12;
					break;
			}
            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;
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void CheckBackFlip(void)
    {
        if(!IsPlayerActive())
        {
            return;
        }
        
        if(!IsChasing(false))
        {
            return;
        }
        
        if (Math::Fabs(lookAtYaw) <= Math::Deg2Rad(10.0f))
        {
            jumpState = 0;
        
            if((Math::Rand() % -2) != 0)
            {
                BlendAnimation(anim_aiDodgeCrouch, 2.6f, 8.0f, false);
            }
            else
            {
                BlendAnimation(anim_longHunterTaunt, 2.6f, 8.0f, false);
            }
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void CheckSpecialAttack(void)
    {
		if (!isTargetingPlayer)
		{
			return;
		}
		
        if (goalDist < 614.4f && Math::RandMax(1000) >= 995)
        {
            CheckBackFlip();
            return;
        }

        // if(!IsPlayerActive())
        // {
            // TauntAttack();
            // return;
        // }
        
        // if(!self.RandomDecision(3))
        // {
            // return;
        // }
        
        // if(!IsChasing(true))
        // {
            // return;
        // }
        
        // if(!self.CheckPosition(goalOrigin))
        // {
            // return;
        // }
        
        // jumpState = 0;
		// Sys.Print("anim_longHunterTaunt 2");
        // BlendAnimation(anim_longHunterTaunt, 2.6f, 8.0f, false);
    }
	//------------------------------------------------------------------------------------------------------------------------
    void CheckStun(void)
    {
		UpdateModel();

        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();
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    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);
                        BlendAnimation(anim_aiTurn_R_Run, 2.6f, 6.0f);
                    }
                }
                else
                {
                    // BlendAnimation(
                        // bTook75PercentDamage ? anim_aiTurn_L_Wound : anim_aiTurn_L_Run,
                        // 2.6f, 6.0f);
                    BlendAnimation(anim_aiTurn_L_Run, 2.6f, 6.0f);
                }
            }
            else
            {
                // BlendAnimation(
                    // bTook75PercentDamage ? anim_aiTurn_B_Wound : anim_aiTurn_B_Run,
                    // 2.6f, 6.0f);
                BlendAnimation(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);
                    BlendLoopingAnimation(anim_aiRunning, 2.6f, 6.0f, false);
                }
            }
            else if(Math::Fabs(desiredYaw) <= Math::Deg2Rad(5.0f) && isTargetingPlayer)
            {
                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);
                        BlendAnimation(anim_aiTurn_R_Walk, 4.0f, 10.0f, false);
                    }
                }
                else
                {
                    // BlendAnimation(
                        // bTook75PercentDamage ? anim_aiTurn_L_Wound : anim_aiTurn_L_Walk,
                        // 4.0f, 10.0f);
                    BlendAnimation(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);
                BlendLoopingAnimation(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);
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void OnTick(void)
    {
        switch(state)
        {
			case CMS_IDLE:
				GetTargetPoint();
				break;
				
			case CMS_STUNNED:
				Stunned();
				return;
				
			case CMS_CHASE:
				GetTargetPoint();
				CheckSpecialAttack();
				CheckRangeAttack();
				break;
				
			case CMS_DEATH:
				return;
        }
        
		CheckLoseAttackTarget();
        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
			{
				UpdateTeleporting();
			}
            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--;
    }
	//------------------------------------------------------------------------------------------------------------------------
    void OnPostBeginLevel()
    {
		initialHealth = self.Health();
        self.SetHeadTrackTarget(null);
		//self.SetTarget(Player.Actor().CastToActor());
		state = CMS_CHASE;
		self.AnimState().Set(anim_aiStanding, 4.0f, ANF_ROOTMOTION);
		// self.AnimState().Set(anim_activate, 4.0f, ANF_ROOTMOTION);
    }
	//------------------------------------------------------------------------------------------------------------------------
    void OnDamage(kActor @instigator, kDictMem @damageDef, const int damage)
    {
        bool bValue = false;
        float diff;
        
        if(self.Health() <= 0)
        {
            return;
        }
		
		if (state == CMS_DEATH)
		{
			return;
		}
        
		if (!isTargetingPlayer && !ignorePlayer)
		{
			SetAttackTarget();
		}
		
        if(state == CMS_STUNNED)
        {
            self.Health() += damage;
            return;
        }
            
        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;
        }
    }
	//------------------------------------------------------------------------------------------------------------------------
    void OnDeath(kActor @killer, kDictMem @damageDef)
    {
		TurokEnemy::OnDeath(killer, damageDef);
		
        self.SetTarget(null);
        self.SetHeadTrackTarget(null);
        self.AnimState().Set(anim_aiDeathStand, 4.0f, ANF_ROOTMOTION);
		//self.RunFxEvent("Campaigner_Death");
		
		self.Flags() &= ~AF_SOLID;
		self.Flags() |= AF_DEAD;
        state = CMS_DEATH;
        self.MarkPersistentBit(false);
    }
	//------------------------------------------------------------------------------------------------------------------------
	void OnRestore()
	{
		self.Remove();
	}
	//------------------------------------------------------------------------------------------------------------------------
}
