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

#include "scripts/animations.txt"

//-----------------------------------------------------------------------------
//
// Hummer
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokHummer
==============================================================
*/

final class TurokHummer : TurokEnemy
{
    float m_distance;
    kAngle m_anDesiredYaw;
    kAngle m_anWheelRot1;
    kAngle m_anWheelRot2;
    kAngle m_anWheelRot3;
    kAngle m_anWheelRot4;
    kAngle m_anWheelAngle;
    kAngle m_anSteerAngle;
    float m_wheelSpeed1;
    float m_wheelSpeed2;
    float m_wheelSpeed3;
    float m_wheelSpeed4;
    float m_knockCoolDown;
    float m_turnSpeed;
    float m_wheelVel;
    kVec3 m_vGoalOrigin;
    bool m_bSpunOut;
    int m_lastSound;
    int m_targetNumber;
    float m_distFromCenter;
    int m_whichHummer;
    kActor @m_pDummyActor;
    kActor @m_pDummyActor2;
	int lastHealth = 0;
    
    TurokHummer(kActor @actor)
    {
        m_distance = 0;
        m_anDesiredYaw = 0;
        m_anWheelRot1 = 0;
        m_anWheelRot2 = 0;
        m_anWheelRot3 = 0;
        m_anWheelRot4 = 0;
        m_anWheelAngle = 0;
        m_anSteerAngle = 0;
        m_knockCoolDown = 0;
        m_wheelSpeed1 = 0;
        m_wheelSpeed2 = 0;
        m_wheelSpeed3 = 0;
        m_wheelSpeed4 = 0;
        m_wheelVel = 0;
        m_targetNumber = 0;
        m_distFromCenter = 0;
        m_lastSound = -1;
        m_whichHummer = 0;
        @m_pDummyActor = null;
        @m_pDummyActor2 = null;
        m_bSpunOut = false;
        m_turnSpeed = 0.033f;
		lastHealth = 0;
        super(actor);
    }
   
    /*
    ==============================================================
    HummerSounds
    ==============================================================
    */
    
    void HummerSounds(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        int soundID = int(w);
        
        if(soundID == m_lastSound)
        {
            return;
        }
        
        self.StopLoopingSounds();
        m_lastSound = soundID;
        
        switch(soundID)
        {
        case 66:
            self.PlaySound("sounds/shaders/explosion_2.ksnd");
            break;
            
        case 470:
            self.PlaySound("sounds/shaders/generic_85_humvee_180_spinout.ksnd");
            break;
            
        case 471:
            self.PlaySound("sounds/shaders/generic_86_humvee_180_turn.ksnd");
            break;
            
        case 472:
            self.PlaySound("sounds/shaders/generic_87_humvee_jump.ksnd");
            break;
            
        case 468:
            self.PlaySound("sounds/shaders/generic_83_humvee_accelerate_'looped'.ksnd");
            break;
            
        case 469:
            self.PlaySound("sounds/shaders/generic_84_humvee_stop_accelerate.ksnd");
            break;
        }
    }
    
    /*
    ==============================================================
    OnCollide
    ==============================================================
    */
    
    void OnCollide(kCModel @pCModel)
    {
        kActor @pTarget;
        
        if(self.Health() <= 0)
        {
            return;
        }
        
        if(m_knockCoolDown > 0.0f)
        {
            return;
        }
        
        if(pCModel.ContactActor() is null)
        {
            return;
        }
        
        @pTarget = Player.Actor().CastToActor();
        
        if(!(pCModel.ContactActor() is pTarget))
        {
            return;
        }
        
        KnockbackTarget(pTarget);
    }
    
    /*
    ==============================================================
    KnockbackTarget
    ==============================================================
    */
    
    void KnockbackTarget(kActor @pActor)
    {
        kVec3 org;
        kVec3 pos;
        
        pActor.PlaySound("sounds/shaders/generic_81_kick_impact.ksnd");
        pActor.InflictGenericDamage(self, 10);
        
        Player.Actor().RecoilPitch() = -Math::Deg2Rad(45.0f);
        
        m_knockCoolDown = 1.0f;
        
        org.x = self.Origin().x;
        org.y = self.Origin().y;
        org.z = self.Origin().z + self.Height() * 0.5f;
        
        pos = pActor.Origin();
        
        kVec3 dir = pos - org;
        float dist = dir.Unit();
        
        if(dist > (1024 + self.Radius()))
        {
            return;
        }
        
        dir.Normalize();
        dir *= (1.75f * GAME_SCALE);
        dir.z = (0.875 * GAME_SCALE);
        
        pActor.Velocity() += dir;
        Player.Actor().PlayerFlags() |= PF_NOAIRFRICTION;
    }
    
    /*
    ==============================================================
    UpdateGoalOrigin
    ==============================================================
    */
    
    void UpdateGoalOrigin(void)
    {
        kVec3 vPos = Player.Actor().Origin();
        bool bSetNewTarget = false;
        
        m_distance = Math::Sqrt(self.DistanceToPoint(vPos));
        m_distFromCenter = self.Origin().Unit();
        
        if(m_distance < Math::RandRange(20.0f*GAME_SCALE, 40.0f*GAME_SCALE))
        {
            m_targetNumber++;
            if(m_targetNumber > 1)
            {
                m_targetNumber = 0;
            }
            
            bSetNewTarget = true;
        }
        else
        {
            bSetNewTarget = false;
        }
        
        switch(m_targetNumber)
        {
        case 0:
            if(bSetNewTarget)
            {
                float unit = vPos.Unit();
                
                if(unit > (68*GAME_SCALE))
                {
                    unit = (48*GAME_SCALE);
                }
                else
                {
                    unit = (88*GAME_SCALE);
                }
                
                m_vGoalOrigin = vPos.Normalize() * unit;
            }
            else
            {
                m_vGoalOrigin = vPos;
            }
            break;
            
        case 1:
            m_vGoalOrigin = vPos;
            break;
        }
        
        m_anDesiredYaw = self.GetAvoidanceAngle(m_vGoalOrigin, 1.5f) - self.Yaw();
    }
    
    /*
    ==============================================================
    UpdateWheels
    ==============================================================
    */
    
    void UpdateWheels(void)
    {
        float moveAngle = self.Movement().ToYaw();
        float diff = Math::Fabs(self.Yaw().Diff(moveAngle));
        float speed;
        int playingAnim;
        kQuat qTurnRotation;
        
        playingAnim = self.AnimState().PlayingID();
        
        if(self.Health() > 0)
        {
            float unit = self.Movement().Unit() * 0.035f;
            
            if(diff < Math::Deg2Rad(90))
            {
                speed = -unit;
            }
            else
            {
                speed = unit;
            }
            
            m_wheelVel += (speed - m_wheelVel) / 2.0f;
        }
        else
        {
            // dampen
            m_wheelVel -= (m_anWheelRot1 / 8192.0f);
            m_anWheelRot1 -= (m_anWheelRot1 / 512.0f);
        }
        
        // increase rotation of wheels
        m_anWheelRot1 += m_wheelVel;
        m_anWheelRot2 += m_wheelVel;
        m_anWheelRot3 += m_wheelVel;
        m_anWheelRot4 += m_wheelVel;
        
        if(self.AnimState().PlayingID() == anim_aiRunning)
        {
            const float maxWheelRange = Math::Deg2Rad(45.0f);
            
            m_anWheelAngle = -m_anDesiredYaw;
            
            if(m_anWheelAngle > maxWheelRange)
            {
                m_anWheelAngle =  maxWheelRange;
            }
            else if(m_anWheelAngle < -maxWheelRange)
            {
                m_anWheelAngle = -maxWheelRange;
            }
        }
        else
        {
            m_anWheelAngle = 0;
        }
        
        m_anSteerAngle += (m_anWheelAngle - m_anSteerAngle) / 8.0f;
        
        qTurnRotation = kQuat(m_anSteerAngle, 0, 0, 1);
        
        // 12 = right back
        // 14 = right front
        // 16 = left front
        // 18 = left back
        
        self.RenderModel().SetRotationOffset(16, kQuat(m_anWheelRot1, 0, 1, 0) * qTurnRotation);
        self.RenderModel().SetRotationOffset(14, kQuat(m_anWheelRot2, 0, 1, 0) * qTurnRotation);
        self.RenderModel().SetRotationOffset(12, m_anWheelRot3, 0, 1, 0);
        self.RenderModel().SetRotationOffset(18, m_anWheelRot4, 0, 1, 0);
    }
    
    /*
    ==============================================================
    Chase
    ==============================================================
    */
    
    void Chase(void)
    {
        float assist;
        
        if(Player.Locked())
        {
            return;
        }
            
        assist = Math::Deg2Rad(10.0f);
        
        if(m_anDesiredYaw < -Math::Deg2Rad(170.0f))
        {
            if(self.RandomDecision(3))
            {
                self.AnimState().Blend(anim_aiTurn_B_Stand, 4.0f, 4.0f, ANF_ROOTMOTION);
            }
            else
            {
                self.AnimState().Blend(anim_aiTurn_B_Run, 4.0f, 4.0f, ANF_ROOTMOTION);
            }
            
            self.StopLoopingSounds();
            m_pDummyActor.StopLoopingSounds();
            self.PlaySound("sounds/shaders/generic_84_humvee_stop_accelerate.ksnd");
            return;
        }
        else
        {
            if(Math::Fabs(m_anDesiredYaw) >= Math::Deg2Rad(90.0f))
            {
                assist *= 2.0f;
                m_bSpunOut = true;
                
                if(!(m_pDummyActor is null))
                {
                    m_pDummyActor.PlaySound("sounds/shaders/generic_85_humvee_180_spinout.ksnd");
                }
            }
            else if(m_bSpunOut)
            {
                m_bSpunOut = false;
                
                if(!(m_pDummyActor is null))
                {
                    m_pDummyActor.StopLoopingSounds();
                }
            }
        }
        
        TurnAngles(assist, m_anDesiredYaw);
    }
    
    /*
    ==============================================================
    SetGlobalState
    ==============================================================
    */
    
    void SetGlobalState(const int stateVal)
    {
        switch(m_whichHummer)
        {
        case 0:
            GameVariables.SetValue("hummer1State", "" + stateVal);
            break;
        case 1:
            GameVariables.SetValue("hummer2State", "" + stateVal);
            break;
        }
        
        SetGlobalProperties();
    }
    
    /*
    ==============================================================
    SetGlobalProperties
    ==============================================================
    */
    
    void SetGlobalProperties(void)
    {
        switch(m_whichHummer)
        {
        case 0:
            GameVariables.SetValue("hummer1Health", "" + self.Health());
            GameVariables.SetValue("hummer1Sector", "" + self.SectorIndex());
            GameVariables.SetValue("hummer1Origin", self.Origin().ToString());
            GameVariables.SetValue("hummer1Yaw", "" + self.Yaw());
            break;
        case 1:
            GameVariables.SetValue("hummer2Health", "" + self.Health());
            GameVariables.SetValue("hummer2Sector", "" + self.SectorIndex());
            GameVariables.SetValue("hummer2Origin", self.Origin().ToString());
            GameVariables.SetValue("hummer2Yaw", "" + self.Yaw());
            break;
        }
    }
    
    /*
    ==============================================================
    GetGlobalState
    ==============================================================
    */
    
    void GetGlobalState(int &out stateVal, int &out stateHealth, kVec3 &out vStateOrigin,
                        int &out stateSector, float &out stateYaw)
    {
        int resetboss = 0;
        
        GameVariables.GetInt("g_resetboss", resetboss);
        if(resetboss != 0)
        {
            SetGlobalState(HGS_NEW);
            SetGlobalProperties();
        }
        
        switch(m_whichHummer)
        {
        case 0:
            GameVariables.GetInt("hummer1State", stateVal);
            GameVariables.GetInt("hummer1Health", stateHealth);
            GameVariables.GetInt("hummer1Sector", stateSector);
            GameVariables.GetVector("hummer1Origin", vStateOrigin);
            GameVariables.GetFloat("hummer1Yaw", stateYaw);
            break;
        case 1:
            GameVariables.GetInt("hummer2State", stateVal);
            GameVariables.GetInt("hummer2Health", stateHealth);
            GameVariables.GetInt("hummer2Sector", stateSector);
            GameVariables.GetVector("hummer2Origin", vStateOrigin);
            GameVariables.GetFloat("hummer2Yaw", stateYaw);
            break;
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if((self.Flags() & AF_HIDDEN) != 0)
        {
            return;
        }
		
		//hit so change tag boss bar
		// if (lastHealth != self.Health()) {
			// lastHealth = self.Health();
			 // Game.CallDelayedMapScript(5, self, 0.0f);
			 // Sys.Print("Hummer HP Changed - " + m_whichHummer + ", lastHP = " + lastHealth);
			// // PlayLoop.TagActorForBossBar(self);
		// }
        
        UpdateGoalOrigin();
        UpdateWheels();
        
        if(!(m_pDummyActor is null))
        {
            m_pDummyActor.Origin() = self.Origin();
        }
        
        if(!(m_pDummyActor2 is null))
        {
            m_pDummyActor2.Origin() = self.Origin();
        }
        
        if(m_knockCoolDown > 0.0f)
        {
            m_knockCoolDown -= 0.033f;
        }
        
        if((PlayLoop.Ticks() & 255) == 0)
        {
            m_bSpunOut = false;
        }
        
        switch(self.AnimState().PlayingID())
        {
        case anim_aiTeleportIn:
            if(self.AnimState().Stopped())
            {
                self.ClipFlags() &= ~CF_DROPOFF;
                self.ClipFlags() |= (CF_NOENTERWATER|CF_NOEXITWATER);
                self.AnimState().Blend(anim_aiWalking, 16.0f, 16.0f, ANF_ROOTMOTION);
                
                SetGlobalState(HGS_ENTERED_ARENA);
                self.RenderModel().HideSection(0, 18, false);
                
                m_vGoalOrigin = Player.Actor().Origin();
            }
            break;
            
        case anim_aiTurn_L_Stand:
        case anim_aiTurn_R_Stand:
        case anim_aiTurn_B_Stand:
        case anim_aiTurn_B_Run:
            if(self.AnimState().Stopped())
            {
                self.AnimState().Blend(anim_aiWalking, 16.0f, 16.0f, ANF_ROOTMOTION);
                self.StopLoopingSounds();
            }
            break;
            
        case anim_aiWalking:
            self.AnimState().Blend(anim_aiRunning, 6.0f, 16.0f, ANF_ROOTMOTION|ANF_LOOP);
            break;
            
        case anim_aiRunning:
            if(!self.AnimState().Blending())
            {
                Chase();
            }
            break;
            
        case anim_aiDeathStand:
            if(self.AnimState().PlayTime() >= 4.0f)
            {
                float x = Math::RandCFloat() * (12*GAME_SCALE);
                float y = Math::RandCFloat() * (12*GAME_SCALE);
                float z = Math::RandFloat() * (7*GAME_SCALE);
                
                self.SpawnFx("fx/hummer_smoke.kfx", kVec3(x, y, z));
                
                if((self.Flags() & AF_NOMOVEMENT) == 0)
                {
                    self.Flags() |= AF_NOMOVEMENT;
                }
            }
            break;
        }
    }
    
    /*
    ==============================================================
    GunFire
    ==============================================================
    */
    
    void GunFire(kActor @instigator, const float w, const float x, const float y, const float z)
    {
        if(Math::Fabs(self.GetTurnYaw(Player.Actor().Origin())) > Math::Deg2Rad(25.0f))
        {
            return;
        }
        
        if(m_distance <= (125*GAME_SCALE) && m_distance >= (25*GAME_SCALE))
        {
            kVec3 l_GunPos(-7.519531f*GAME_SCALE, -23.144531f*GAME_SCALE, 11.621094f*GAME_SCALE);
            kVec3 r_GunPos( 7.861328f*GAME_SCALE, -23.144531f*GAME_SCALE, 11.621094f*GAME_SCALE);
            
            m_pDummyActor2.PlaySound("sounds/shaders/rifle_shot.ksnd");
            
            self.SpawnFx("fx/generic_128.kfx", l_GunPos);
            self.SpawnFx("fx/generic_128.kfx", r_GunPos);
        }
        
        if(m_distance <= (200*GAME_SCALE) && m_distance >= (50*GAME_SCALE))
        {
            if(self.RandomDecision(3))
            {
                kVec3 l_RocketPos(-6.347656f*GAME_SCALE, -1.348877f*GAME_SCALE, 16.601563f*GAME_SCALE);
                kVec3 r_RocketPos( 7.861328f*GAME_SCALE, -1.348877f*GAME_SCALE, 16.601563f*GAME_SCALE);
                
                self.SpawnFx("fx/generic_124.kfx", l_RocketPos);
                self.SpawnFx("fx/generic_124.kfx", r_RocketPos);
                
                m_pDummyActor2.PlaySound("sounds/shaders/missile_launch.ksnd");
            }
        }
    }
    
    /*
    ==============================================================
    OnBeginLevel
    ==============================================================
    */
    
    void OnBeginLevel(void)
    {
        int state, sector, health;
        float yaw;
        kVec3 vOrigin;
        
        self.Flags() |= (AF_DISABLED|AF_HIDDEN);
        self.Health() = 750;
		lastHealth = self.Health();
        self.SetTarget(Player.Actor().CastToActor());
        
        self.RenderModel().HideSection(0, 18, true);
        
        @m_pDummyActor = ActorFactory.Spawn("DummyActor", 0, 0, 0, 0, self.SectorIndex());
        @m_pDummyActor2 = ActorFactory.Spawn("DummyActor", 0, 0, 0, 0, self.SectorIndex());
        
        switch(self.TID())
        {
        case 1140:
            m_whichHummer = 0;
            break;
        case 1141:
            m_whichHummer = 1;
            break;
        }
        
        GetGlobalState(state, health, vOrigin, sector, yaw);
        
        if(state == HGS_NEW)
        {
            return;
        }
        
        if(state == HGS_ENTERED_ARENA)
        {
            self.Flags() &= ~(AF_HIDDEN|AF_DISABLED);
            self.ClipFlags() &= ~CF_DROPOFF;
            self.ClipFlags() |= (CF_NOENTERWATER|CF_NOEXITWATER);
            self.AnimState().Set(anim_aiWalking, 16.0f, ANF_ROOTMOTION);
            
            m_vGoalOrigin = Player.Actor().Origin();
            PlayLoop.TagActorForBossBar(self);
        }
        else if(state == HGS_DEAD)
        {
            self.Flags() &= ~(AF_HIDDEN|AF_DISABLED|AF_SOLID);
            self.Flags() |= AF_DEAD;
            self.ClipFlags() &= ~CF_DROPOFF;
            self.ClipFlags() |= (CF_NOENTERWATER|CF_NOEXITWATER);
            
            self.AnimState().Set(anim_aiDeathStand, 6.0f, ANF_ROOTMOTION|ANF_LOOP);
            self.AnimState().SetLastFrame();
            self.StopLoopingSounds();
            
            if(!(m_pDummyActor is null))
            {
                m_pDummyActor.StopLoopingSounds();
            }
            
            if(!(m_pDummyActor2 is null))
            {
                m_pDummyActor2.StopLoopingSounds();
            }
        }
        
        self.Health() = health;
		lastHealth = self.Health();
        self.Origin() = vOrigin;
        self.SetSector(sector);
        self.Yaw() = yaw;
    }
    
    /*
    ==============================================================
    OnEndLevel
    ==============================================================
    */
    
    void OnEndLevel(void)
    {
		TurokEnemy::OnEndLevel();
        SetGlobalProperties();
    }
    
    /*
    ==============================================================
    OnDeath
    ==============================================================
    */
    
    void OnDeath(kActor @killer, kDictMem @damageDef)
    {
        self.AnimState().Blend(anim_aiDeathStand, 6.0f, 4.0f, ANF_ROOTMOTION|ANF_LOOP);
        self.Flags() &= ~AF_SOLID;
        
        PlayLoop.RemoveBossActor();
        self.StopLoopingSounds();
        
        if(!(m_pDummyActor is null))
        {
            m_pDummyActor.StopLoopingSounds();
        }
        
        if(!(m_pDummyActor2 is null))
        {
            m_pDummyActor2.StopLoopingSounds();
        }
        
        SetGlobalState(HGS_DEAD);
    }
}
