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

#include "scripts/common.txt"

// const array<int> g_TRexAttackAnims1 = { anim_trexRoar, anim_aiMelee9, anim_aiMelee13 };
// const array<int> g_TRexAttackAnims2 = { anim_aiMelee13, anim_aiMelee9, anim_aiMelee13 };
// const array<int> g_TRexAttackAnims3 = { anim_aiMelee8, anim_aiAltMelee4, anim_aiMelee8 };
// const array<int> g_TRexAttackAnims4 = { anim_aiMelee8, anim_aiMelee8, anim_aiAltMelee4 };
// const array<int> g_TRexAttackAnims5 = { anim_aiMelee8, anim_aiMelee8, anim_aiMelee8 };

// const array<int> g_TRexAttackAnimCount1 = { 0, 1, 2 };
// const array<int> g_TRexAttackAnimCount2 = { 0, 2, 2 };
// const array<int> g_TRexAttackAnimCount3 = { 0, 3, 3 };
// const array<int> g_TRexAttackAnimCount4 = { 1, 2, 2 };
// const array<int> g_TRexAttackAnimCount5 = { 0, 0, 0 };

// const array<array<int>> g_TRexAttackAnims =
// {
    // g_TRexAttackAnims1,
    // g_TRexAttackAnims1,
    // g_TRexAttackAnims1,
    // g_TRexAttackAnims2
// };

// const array<array<int>> g_TRexAttackAnimsCount =
// {
    // g_TRexAttackAnimCount1,
    // g_TRexAttackAnimCount2,
    // g_TRexAttackAnimCount3,
    // g_TRexAttackAnimCount4
// };

// const array<array<int>> g_TRexReachAttackAnims =
// {
    // g_TRexAttackAnims3,
    // g_TRexAttackAnims4,
    // g_TRexAttackAnims5
// };

// const array<array<int>> g_TRexReachAttackAnimsCount =
// {
    // g_TRexAttackAnimCount5,
    // g_TRexAttackAnimCount5,
    // g_TRexAttackAnimCount5
// };

// enum trexState
// {
    // TRS_IDLE        = 0,
    // TRS_CHASE,
    // TRS_REACH,
    // TRS_STUNNED,
    // TRS_DEAD,
    
    // NUM_TREX_STATES
// }

// enum trexGlobalState
// {
   // TGS_IDLE = 0, // has not been initialized before
   // TGS_ACTIVE,   // fighting the player
   // TGS_DEAD      // dead
// }

//-----------------------------------------------------------------------------
//
// T-Rex
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokTRex
==============================================================
*/

final class BP_TurokTRex : TurokEnemy
{
    float goalDistSq;               // distance to goal origin squared
    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?
    float targetRange;
    float lostSightTime;
    int attackCount;
    int curAttack;
    int selectedAttack;
    bool bRepeatAttack;
    bool bTook25PercentDamage;
    bool bTook50PercentDamage;
    bool bTook75PercentDamage;
    int initialHealth;
    int stunTime;
    bool bFiredBreathAttack;
    float stunAnimSpeed;
    bool m_bLaserTrack;
    int globalState;
	bool m_bTargetDead;
	int m_bossID;
    
    BP_TurokTRex(kActor @actor)
    {
        goalDistSq = 0;
        goalDist = 0;
        desiredYaw = 0;
        lookAtYaw = 0;
        bTurning = false;
        bRepeatAttack = false;
        targetRange = 0;
        attackCount = 0;
        selectedAttack = 0;
        curAttack = 0;
        lostSightTime = 0;
        bTook25PercentDamage = false;
        bTook50PercentDamage = false;
        bTook75PercentDamage = false;
        initialHealth = 0;
        stunTime = 0;
        stunAnimSpeed = 2.0f;
        bFiredBreathAttack = false;
        state = TRS_IDLE;
        globalState = TGS_IDLE;
        m_bLaserTrack = false;
		m_bTargetDead = false;
        
        super(actor);
    }
    
    /*
    ==============================================================
    Shockwave
    ==============================================================
    */
    
    void Shockwave(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if(self.GetTarget() is null)
        {
            return;
        }
        
        self.SpawnProjectile("fx/shockwave.kfx", kVec3(x, y, z),
            self.GetTarget().Origin(), Math::Deg2Rad(45.0f));
    }
    
    /*
    ==============================================================
    FireLaserBeam
    ==============================================================
    */
    
    void FireLaserBeam(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if(self.GetTarget() is null)
        {
            return;
        }
        
        self.SpawnProjectile("fx/eyefire.kfx", kVec3(x, y, z),
            self.GetTarget().Origin(), Math::Deg2Rad(45.0f));
    }
    
    /*
    ==============================================================
    TRexFireBreath
    ==============================================================
    */
    
    void TRexFireBreath(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if(self.GetTarget() is null)
        {
            return;
        }
        
        if(!bFiredBreathAttack)
        {
            self.PlaySound("sounds/shaders/trex_fire_breathe.ksnd");
            bFiredBreathAttack = true;
        }
        
        self.SpawnProjectile("fx/breath.kfx", kVec3(x, y, z),
            self.GetTarget().Origin(), Math::Deg2Rad(45.0f));
    }
    
    /*
    ==============================================================
    ScreenShake
    ==============================================================
    */
    
    void ScreenShake(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        Player.Actor().RecoilPitch() = -Math::Deg2Rad(w);
    }
    
    /*
    ==============================================================
    BlendAnimation
    ==============================================================
    */
    
    void BlendAnimation(const int anim, const float speed, const float blend, const bool bStoppedOnly = true)
    {
        if(bStoppedOnly && self.AnimState().Blending())
        {
            // don't interrupt while blending
            return;
        }
        
        if(bStoppedOnly && (!self.AnimState().Looping() && !self.AnimState().Stopped()))
        {
            return;
        }
        else if(!bStoppedOnly && self.AnimState().PlayingID() == anim)
        {
            return;
        }
        
        self.AnimState().Blend(anim, speed, blend, ANF_ROOTMOTION);
    }
    
    /*
    ==============================================================
    BlendLoopingAnimation
    ==============================================================
    */
    
    void BlendLoopingAnimation(const int anim, const float speed, const float blend, const bool bStoppedOnly = true)
    {
        if(bStoppedOnly && !self.AnimState().Stopped())
        {
            return;
        }
        
        if(self.AnimState().PlayingID() != anim)
        {
            self.AnimState().Blend(anim, speed, blend, ANF_ROOTMOTION|ANF_LOOP);
        }
    }
    
    /*
    ==============================================================
    DistanceTo2DVector
    ==============================================================
    */
    
    float DistanceTo2DVector(const float x, const float y)
    {
        float dx = self.Origin().x - x;
        float dy = self.Origin().y - y;
        
        return dx * dx + dy * dy;
    }
    
    /*
    ==============================================================
    DistanceTo2DVector
    ==============================================================
    */
    
    float DistanceTo2DVector(const kVec3 &in vector)
    {
        return DistanceTo2DVector(vector.x, vector.y);
    }
    
    /*
    ==============================================================
    TargetOnLeftside
    ==============================================================
    */
    
    bool TargetOnLeftside(void)
    {
        return (Player.Actor().Origin().x <= -266.24f &&
                Player.Actor().Origin().y > 1187.84f);
    }
    
    /*
    ==============================================================
    TargetOnRightside
    ==============================================================
    */
    
    bool TargetOnRightside(void)
    {
        return (Player.Actor().Origin().x >= 266.24f &&
                Player.Actor().Origin().y > 1187.84f);
    }
    
    /*
    ==============================================================
    TargetAtCenter
    ==============================================================
    */
    
    bool TargetAtCenter(void)
    {
        return (Player.Actor().Origin().x > 266.24f &&
                Player.Actor().Origin().x < -266.24f &&
                Player.Actor().Origin().y > 1187.84f);
    }
    
    /*
    ==============================================================
    CheckTurning
    ==============================================================
    */
    
    void CheckTurning(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Run:
            bTurning = true;
            break;
        default:
            bTurning = false;
            break;
        }
    }
    
    /*
    ==============================================================
    Stunned
    ==============================================================
    */
    
    void Stunned(void)
    {
        stunTime++;
        
        if((stunTime & 7) == 0)
        {
            stunAnimSpeed = Math::NLerp(stunAnimSpeed, 0.2f, 0.5f);
            self.AnimState().ChangeSpeed(stunAnimSpeed);
            
            self.PlaySound("sounds/shaders/explosion_2.ksnd");
            
            self.SpawnFx("fx/generic_224.kfx",
                kVec3(Math::RandRange(-307.2f, 307.2f),
                      Math::RandRange(-307.2f, 307.2f),
                      Math::RandRange(122.88f, 358.4f)));
        }
        
        if(stunTime == 100)
        {
            kRenderModel @model = self.RenderModel();
            
            if(bTook75PercentDamage)
            {
                model.SetTexture(1, 1);
                model.SetTexture(31, 1);
                model.SetTexture(34, 1);
            }
            else if(bTook50PercentDamage)
            {
                model.SetTexture(8, 1);
                model.SetTexture(18, 1);
                model.SetTexture(6, 1);
                model.SetTexture(7, 1);
                model.SetTexture(16, 1);
                model.SetTexture(17, 1);
            }
            else if(bTook25PercentDamage)
            {
                model.SetTexture(9, 1);
                model.SetTexture(10, 1);
                model.SetTexture(11, 1);
                model.SetTexture(12, 1);
                model.SetTexture(13, 1);
                model.SetTexture(19, 1);
                model.SetTexture(20, 1);
                model.SetTexture(21, 1);
                model.SetTexture(22, 1);
                model.SetTexture(23, 1);
            }
        }
        
        if(stunTime >= 150)
        {
            state = TRS_CHASE;
            BlendAnimation(anim_aiAltMelee5, 2.0f, 6.0f, false);
        }
    }
    
    /*
    ==============================================================
    BecomeStunned
    ==============================================================
    */
    
    void BecomeStunned(void)
    {
        stunTime = 0;
        stunAnimSpeed = 2.0f;
        state = TRS_STUNNED;
        BlendLoopingAnimation(anim_stun2, stunAnimSpeed, 6.0f, false);
    }
    
    /*
    ==============================================================
    GetTargetPoint
    ==============================================================
    */
    
    void GetTargetPoint(void)
    {
        goalOrigin.x = Player.Actor().Origin().x;
        goalOrigin.y = Player.Actor().Origin().y;
        goalOrigin.z = self.Origin().z;
        
        goalDistSq = DistanceTo2DVector(goalOrigin);
        goalDist = Math::Sqrt(goalDistSq);
        
        if(state == TRS_REACH)
        {
            targetRange = 286.72f;
        }
        else
        {
            targetRange = 409.6f;
        }
        
        desiredYaw = self.GetAvoidanceAngle(goalOrigin, 1.5f) - self.Yaw();
        lookAtYaw = self.GetTurnYaw(goalOrigin);
    }
    
    /*
    ==============================================================
    CloseRangeAttack
    ==============================================================
    */
    
    void CloseRangeAttack(void)
    {
        BlendAnimation(anim_aiMelee7, 2.6f, 16.0f, false);
    }
    
    /*
    ==============================================================
    RangeAttack
    ==============================================================
    */
    
    void RangeAttack(void)
    {
        if(++curAttack >= int(g_TRexAttackAnimsCount[selectedAttack].length()))
        {
            curAttack = 0;
            selectedAttack = Math::RandMax(int(g_TRexAttackAnims.length()));
        }
        
        attackCount = g_TRexAttackAnimsCount[selectedAttack][curAttack];
        
        if(attackCount > 0)
        {
            BlendLoopingAnimation(g_TRexAttackAnims[selectedAttack][curAttack], 4.0f, 16.0f, false);
        }
        else
        {
            BlendAnimation(g_TRexAttackAnims[selectedAttack][curAttack], 4.0f, 16.0f, false);
        }
    }
    
    /*
    ==============================================================
    ReachAttack
    ==============================================================
    */
    
    void ReachAttack(void)
    {
        if(++curAttack >= int(g_TRexReachAttackAnimsCount[selectedAttack].length()))
        {
            curAttack = 0;
            selectedAttack = Math::RandMax(int(g_TRexReachAttackAnims.length()));
        }
        
        attackCount = g_TRexReachAttackAnimsCount[selectedAttack][curAttack];
        
        if(attackCount > 0)
        {
            BlendLoopingAnimation(g_TRexReachAttackAnims[selectedAttack][curAttack], 4.0f, 16.0f, false);
        }
        else
        {
            BlendAnimation(g_TRexReachAttackAnims[selectedAttack][curAttack], 4.0f, 16.0f, false);
        }
    }
    
    /*
    ==============================================================
    UpdateAttackState
    ==============================================================
    */
    
    bool UpdateAttackState(void)
    {
        if(self.AnimState().PlayingID() == anim_aiMelee9)
        {
            FaceTarget(3.0f);
        }
        if(self.AnimState().PlayingID() == anim_aiMelee13)
        {
            FaceTarget(8.0f);
        }
        
        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)
    {
        lostSightTime = 0;
        TurnAngles(Math::Deg2Rad(angle), lookAtYaw);
    }
    
    /*
    ==============================================================
    CheckForcedBlend
    ==============================================================
    */
    
    bool CheckForcedBlend(void)
    {
        if(bTurning && self.AnimState().TrackTime() >= 0.7f)
        {
            return true;
        }
        
        switch(self.AnimState().PlayingID())
        {
        case anim_aiDeathKnockback3:
            return (self.AnimState().TrackTime() >= 0.7f);
            
        case anim_aiAltMelee2:
        case anim_aiAltMelee3:
        case anim_aiAltMelee1:
            return (self.AnimState().TrackTime() >= 0.8f);
        }
        
        return false;
    }
    
    /*
    ==============================================================
    UpdateLaserTracker
    ==============================================================
    */
    
    void UpdateLaserTracker(void)
    {
        if(self.AnimState().Stopped() || self.AnimState().Blending())
        {
            return;
        }
        
        switch(self.AnimState().PlayingID())
        {
        case anim_aiStanding:
        case anim_aiWalking:
        case anim_aiRunning:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Stand:
            if(!m_bLaserTrack || self.RenderModel().GetNumAttachedTrails() <= 0)
            {
                self.RenderModel().AddTrailEffect("Trail_TrexLaser", 4);
                self.SetHeadTrackTarget(Player.Actor().CastToActor());
                m_bLaserTrack = true;
            }
            break;
            
        default:
            if(m_bLaserTrack)
            {
                self.RenderModel().RemoveTrailEffect();
                self.SetHeadTrackTarget(null);
                m_bLaserTrack = false;
            }
            break;
        }
    }
    
    /*
    ==============================================================
    Stand
    ==============================================================
    */
    
    void Stand(void)
    {
        if(!self.AnimState().Blending())
        {
            if(Math::Fabs(lookAtYaw) <= Math::Deg2Rad(15.0f))
            {
                TurnAngles(Math::Deg2Rad(12.0f), desiredYaw);
            }
            else
            {
                TurnAngles(Math::Deg2Rad(6.0f), desiredYaw);
            }
        }
        
        if(Math::Fabs(lookAtYaw) <= Math::Deg2Rad(150.0f))
        {
            if(lookAtYaw >= -Math::Deg2Rad(35.0f))
            {
                if(lookAtYaw >= Math::Deg2Rad(35.0f))
                {
                    lostSightTime += 0.0125f;
                    BlendAnimation(anim_aiTurn_R_Stand, 2.6f, 16.0f);
                    return;
                }
            }
            else
            {
                lostSightTime += 0.0125f;
                BlendAnimation(anim_aiTurn_L_Stand, 2.6f, 16.0f);
                return;
            }
        }
        else
        {
            lostSightTime += 0.0125f;
            
            BlendAnimation(anim_aiTurn_B_Stand, 2.6f, 16.0f);
            return;
        }
        
        if(Math::Fabs(lookAtYaw) <= Math::Deg2Rad(15.0f))
        {
            lostSightTime -= 1.0f;
            
            switch(state)
            {
            case TRS_CHASE:
                if(goalDist > 174.08f)
                {
                    RangeAttack();
                    return;
                }
                CloseRangeAttack();
                return;
                
            case TRS_REACH:
                ReachAttack();
                return;
            }
        }
    }
    
    /*
    ==============================================================
    Run
    ==============================================================
    */
    
    void Run(void)
    {
        bool bForceRun = CheckForcedBlend();
        
        if(!self.AnimState().Blending())
        {
            TurnAngles(Math::Deg2Rad(12.0f), desiredYaw);
        }
        
        if(self.AnimState().CycleCompleted())
        {
            if(desiredYaw >= -Math::Deg2Rad(35.0f))
            {
                if(desiredYaw > Math::Deg2Rad(35.0f))
                {
                    BlendAnimation(anim_aiTurn_R_Run, 2.6f, 16.0f);
                    return;
                }
            }
            else
            {
                BlendAnimation(anim_aiTurn_L_Run, 2.6f, 16.0f);
                return;
            }
        }
        
        if(Math::Fabs(desiredYaw) <= Math::Deg2Rad(15.0f))
        {
            if(goalDist > 204.8f)
            {
                BlendLoopingAnimation(anim_aiRunning, 2.6f, 16.0f, !bForceRun);
            }
            else
            {
                if(Math::Fabs(desiredYaw) <= Math::Deg2Rad(10.0f))
                {
                    BlendAnimation(anim_aiAltMelee2, 2.6f, 16.0f, false);
                }
                else
                {
                    BlendLoopingAnimation(anim_aiRunning, 2.6f, 16.0f, !bForceRun);
                }
            }
        }
    }
    
    /*
    ==============================================================
    ChaseTarget
    ==============================================================
    */
    
    void ChaseTarget(void)
    {
        if(state == TRS_IDLE)
        {
            return;
        }
        
        if(lostSightTime < 0.0f)
        {
            lostSightTime = 0.0f;
        }
        
        if(lostSightTime >= 3.0f)
        {
            lostSightTime = 0;
            
            if(self.RandomDecision(3))
            {
                BlendAnimation(anim_aiLostSight, 4.0f, 16.0f, false);
                return;
            }
        }
        
        if(!bTurning && goalDist <= 512.0f && (Math::Rand() & 3) == 0)
        {
            int thrashChance;
            
            if(Math::Fabs(lookAtYaw) >= Math::Deg2Rad(150.0f))
            {
                thrashChance = 4;
            }
            else
            {
                thrashChance = 6;
            }
            
            if(self.RandomDecision(thrashChance))
            {
                lostSightTime = 0;
                BlendAnimation(anim_aiAltMelee3, 2.6f, 16.0f, false);
                return;
            }
        }
        
        if(goalDist <= targetRange)
        {
            Stand();
        }
        else
        {
            Run();
        }
    }
    
    /*
    ==============================================================
    CheckInvincibility
    ==============================================================
    */
    
    void CheckInvincibility(void)
    {
        switch(self.AnimState().PlayingID())
        {
        case anim_trexRoar:
            self.Flags() |= AF_INVINCIBLE;
            break;
            
        default:
            self.Flags() &= ~AF_INVINCIBLE;
            break;
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
		if (globalState == TGS_DEAD)
		{
			return;
		}
		
		if(Player.Actor().Health() <= 0 || Player.Locked())
        {
            if(!m_bTargetDead)
            {
                SetGlobalState();
                m_bTargetDead = true;
            }
        }
		
        switch(state)
        {
        case TRS_IDLE:
            GetTargetPoint();
            break;
            
        case TRS_STUNNED:
            Stunned();
            break;
            
        case TRS_CHASE:
        case TRS_REACH:
            GetTargetPoint();
           
            if(Player.Actor().Origin().z - self.Origin().z > 61.44f)
            {
                if(state != TRS_REACH)
                {
                    state = TRS_REACH;
                    selectedAttack = 0;
                    curAttack = 0;
                }
            }
            else if(state != TRS_CHASE)
            {
                state = TRS_CHASE;
                selectedAttack = 0;
                curAttack = 0;
            }
            break;            
        }
        
        CheckInvincibility();
        CheckTurning();
        UpdateLaserTracker();

        if(state != TRS_DEAD && Player.Locked() && Player.Actor().Health() > 0)
        {
            if(self.AnimState().PlayingID() == anim_trexRoar)
            {
                return;
            }
            
            if(self.AnimState().PlayingID() != anim_aiLostSight)
            {
                BlendLoopingAnimation(anim_aiLostSight, 4.0f, 6.0f);
            }
            else if(self.AnimState().CycleCompleted() == true)
            {
                self.AnimState().flags &= ~ANF_CYCLECOMPLETED;
            }
            return;
        }
        
        switch(self.AnimState().PlayingID())
        {
        case anim_aiMelee9:
            if(UpdateAttackState())
            {
                if(bFiredBreathAttack)
                {
                    bFiredBreathAttack = false;
                    self.StopLoopingSounds();
                }
                
                BlendAnimation(anim_aiMelee11, 4.0f, 6.0f);
            }
            break;
            
        case anim_aiMelee13:
            if(UpdateAttackState())
            {
                BlendAnimation(anim_aiAltMelee1, 4.0f, 6.0f);
            }
            break;
            
        case anim_aiAltMelee4:
        case anim_aiMelee8:
            FaceTarget(8.0f);
            if(self.AnimState().Stopped())
            {
                if(bFiredBreathAttack)
                {
                    bFiredBreathAttack = false;
                    self.StopLoopingSounds();
                }
                ChaseTarget();
            }
            break;
            
        case anim_trexRoar:
            if(self.AnimState().Stopped())
            {
                // just in case
                state = TRS_CHASE;
                bFiredBreathAttack = false;
                self.StopLoopingSounds();
                
                BlendAnimation(anim_aiAltMelee3, 2.6f, 16.0f, false);
            }
            break;
            
        case anim_aiDeathKnockback1:
        case anim_aiDeathKnockback2:
        case anim_aiDeathKnockback4:
        case anim_aiLostSight:
        case anim_aiAltMelee3:
        case anim_aiMelee7:
        case anim_aiAltMelee2:
        case anim_aiAltMelee5:
        case anim_stun1:
            if(self.AnimState().Stopped())
            {
                state = TRS_CHASE;
                bFiredBreathAttack = false;
                self.StopLoopingSounds();
                ChaseTarget();
            }
            break;
            
        case anim_aiDeathKnockback3:
        case anim_aiAltMelee1:
        case anim_aiMelee11:
        case anim_aiStanding:
        case anim_aiRunning:
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_L_Run:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_R_Run:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Run:
            ChaseTarget();
            break;
        }
    }
    
    /*
    ==============================================================
    SetGlobalState
    ==============================================================
    */
    
    void SetGlobalState(void)
    {
        GameVariables.SetValue("trexState" + m_bossID,  "" + globalState);
        GameVariables.SetValue("trexHealth" + m_bossID, "" + self.Health());
        GameVariables.SetValue("trexInitHealth" + m_bossID, "" + initialHealth);
        GameVariables.SetValue("trexSector" + m_bossID, "" + self.SectorIndex());
        GameVariables.SetValue("trexOrigin" + m_bossID, "" + self.Origin().ToString());
        GameVariables.SetValue("trexYaw" + m_bossID, "" + self.Yaw());		
    }
    
    /*
    ==============================================================
    GetGlobalState
    ==============================================================
    */
    
    void GetGlobalState(void)
    {
		int state, sector, health, initHealth;
        float yaw;
        kVec3 vOrigin;
		
        GameVariables.GetInt("trexState" + m_bossID,  globalState);
        GameVariables.GetInt("trexHealth" + m_bossID, health);
        GameVariables.GetInt("trexInitHealth" + m_bossID, initHealth);
        GameVariables.GetInt("trexSector" + m_bossID, sector);
        GameVariables.GetVector("trexOrigin" + m_bossID, vOrigin);
        GameVariables.GetFloat("trexYaw" + m_bossID, yaw);

		bool isNew = (globalState == TGS_IDLE);
		if (isNew)
        {
			initialHealth = self.Health();
        }
		else
		{
			initialHealth = initHealth;
		}
		
		if (globalState != TGS_DEAD)
		{
			self.AnimState().Set(anim_trexRoar, 6.0f, ANF_ROOTMOTION);
			self.SetHeadTrackTarget(Player.Actor().CastToActor());
			self.SetTarget(Player.Actor().CastToActor());
		}
		else
		{
			self.AnimState().Set(anim_aiDeathStand, 8.0f, ANF_ROOTMOTION);
			self.AnimState().SetLastFrame();
			self.Flags() &= ~AF_SOLID;
			self.Flags() |= AF_DEAD;
			self.SetTarget(null);
		}
				
        if(globalState != TGS_IDLE)
        {

            kRenderModel @model = self.RenderModel();

            // reset state tracking vars so they don't fire CheckStunned again
            if(health < int(float(initialHealth) * 0.75f))
            {
                bTook25PercentDamage = true;
                model.SetTexture(9, 1);
                model.SetTexture(10, 1);
                model.SetTexture(11, 1);
                model.SetTexture(12, 1);
                model.SetTexture(13, 1);
                model.SetTexture(19, 1);
                model.SetTexture(20, 1);
                model.SetTexture(21, 1);
                model.SetTexture(22, 1);
                model.SetTexture(23, 1);
            }
            if(health < int(float(initialHealth) * 0.5f))
            {
                bTook50PercentDamage = true;
                model.SetTexture(8, 1);
                model.SetTexture(18, 1);
                model.SetTexture(6, 1);
                model.SetTexture(7, 1);
                model.SetTexture(16, 1);
                model.SetTexture(17, 1);
            }
            if(health < int(float(initialHealth) * 0.25f))
            {
                bTook75PercentDamage = true;
                model.SetTexture(1, 1);
                model.SetTexture(31, 1);
                model.SetTexture(34, 1);
            }
        }
		
		if (isNew)
		{
            globalState = TGS_ACTIVE;
			SetGlobalState();
		}
		else
		{
			self.Health() = health;
			self.Origin() = vOrigin;
			self.SetSector(sector);
			self.Yaw() = yaw;
		}

    }
    
    /*
    ==============================================================
    OnBeginLevel
    ==============================================================
    */
    
    void OnPostBeginLevel(void)
    {
		m_bossID = self.SpawnParams(7);
        // self.AnimState().Set(anim_aiStanding, 4.0f, ANF_ROOTMOTION);
        GetGlobalState();
    }
    
    /*
    ==============================================================
    OnEndLevel
    ==============================================================
    */
    
    void OnEndLevel(void)
    {
        SetGlobalState();
    }
    
    /*
    ==============================================================
    OnDamage
    ==============================================================
    */
    
    void OnDamage(kActor @instigator, kDictMem @damageDef, const int damage)
    {
        bool bValue = false;
        int health;
        
        if(self.Health() <= 0)
        {
            return;
        }
		
		if (globalState == TGS_DEAD)
		{
			return;
		}
        
        lostSightTime = 0;
        health = self.Health() - damage;
        
        if(!bTook25PercentDamage && health < int(float(initialHealth) * 0.75f))
        {
            bTook25PercentDamage = true;
            BecomeStunned();
            return;
        }
        else if(!bTook50PercentDamage && health < int(float(initialHealth) * 0.5f))
        {
            bTook50PercentDamage = true;
            BecomeStunned();
            return;
        }
        else if(!bTook75PercentDamage && health < int(float(initialHealth) * 0.25f))
        {
            bTook75PercentDamage = true;
            BecomeStunned();
            return;
        }
        
        if(state != TRS_CHASE)
        {
            return;
        }
        
        if(damageDef is null)
        {
            return;
        }
        
        if(damageDef.GetBool("bCauseSpecialAnimation", bValue) && bValue)
        {
            float diff;
            
            switch(self.AnimState().PlayingID())
            {
            case anim_aiMelee9:
                if(self.RandomDecision(4))
                {
                    BlendAnimation(anim_stun1, 4.5f, 8.0f, false);
                }
                return;
                
            case anim_aiStanding:
            case anim_aiRunning:
            case anim_aiMelee11:
            case anim_aiAltMelee1:
                if(!self.RandomDecision(4))
                {
                    return;
                }
                break;
            case anim_trexRoar:
            case anim_aiLostSight:
                if(!self.RandomDecision(2))
                {
                    return;
                }
                break;
                
            default:
                return;
            }
            
            diff = (Player.Actor().Yaw() - self.Yaw() - Math::Deg2Rad(45.0f)) * 0.6366197723675814f;
            
            switch(int(diff) & 3)
            {
            case 0:
                BlendAnimation(anim_aiDeathKnockback1, 4.5f, 8.0f, false);
                break;
            case 1:
                BlendAnimation(anim_aiDeathKnockback3, 4.5f, 8.0f, false);
                break;
            case 2:
                BlendAnimation(anim_aiDeathKnockback4, 4.5f, 8.0f, false);
                break;
            case 3:
                BlendAnimation(anim_aiDeathKnockback2, 4.5f, 8.0f, false);
                break;
            }
        }
    }
    
    /*
    ==============================================================
    OnDeath
    ==============================================================
    */
    
    void OnDeath(kActor @killer, kDictMem @damageDef)
    {
        state = TRS_DEAD;
        globalState = TGS_DEAD;
        
		self.AnimState().Set(anim_aiDeathStand, 8.0f, ANF_ROOTMOTION);
        self.SetHeadTrackTarget(null);
        self.SetTarget(null);
        self.StopLoopingSounds();
		self.Flags() &= ~AF_SOLID;
		self.Flags() |= AF_DEAD;
		SetGlobalState();
    }
}
