//
// 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:
//      Weapon Actions
//

/*
==============================================================
TurokWeapon
==============================================================
*/

class TurokWeapon : ScriptObject
{
    kWeapon @self;
    
    TurokWeapon(kWeapon @actor)
    {
        @self = actor;
    }
    
    ~TurokWeapon() {}
    
    void OnTick(void) {}
    void OnSpawn(void) {}
    void OnBeginFire(void) {}
    void OnFire(void) {}
    void OnEndFire(void) {}
    void OnLower(void) {}
    void OnRaise(void) {}
    void OnHoldster(void) {}
    void OnFiredParticleFromAnim(kParticle@) {}
}

/*
==============================================================
TurokBow
==============================================================
*/

final class TurokBow : TurokWeapon
{   
    float m_fScalar;
    
    TurokBow(kWeapon @actor)
    {
        super(actor);
        m_fScalar = 1.0f;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fScalar);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fScalar);
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        m_fScalar = Math::Max(0.5f, Math::Min(1.5f, self.AnimTrackComponent().PlayTime()));
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pArrow)
    {
        pArrow.Velocity() *= m_fScalar;
        m_fScalar = 1.0f;
    }
}

/*
==============================================================
TurokTekBow
==============================================================
*/

final class TurokTekBow : TurokWeapon
{   
    float m_fScalar;
    
    TurokTekBow(kWeapon @actor)
    {
        super(actor);
        m_fScalar = 1.0f;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fScalar);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fScalar);
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        m_fScalar = Math::Max(0.5f, Math::Min(2.0f, self.AnimTrackComponent().PlayTime()));
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pArrow)
    {
        if(self.ScopeReady())
        {
            return;
        }
        
        pArrow.Velocity() *= m_fScalar;
    }
}

/*
==============================================================
TurokPistol
==============================================================
*/

final class TurokPistol : TurokWeapon
{   
    TurokPistol(kWeapon @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
    }
    
    /*
    ==============================================================
    OnFire
    ==============================================================
    */
    
    void OnFire(void)
    {
        if(self.AnimTrackComponent().TrackTime() >= 0.8f)
        {
            self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
        }
    }
}

/*
==============================================================
TurokChargeDart
==============================================================
*/

final class TurokChargeDart : TurokWeapon
{   
    TurokChargeDart(kWeapon @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        self.PlaySound("sounds/shaders/Charge Dart PowerUp.ksnd");
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
        
        self.PlaySound("sounds/shaders/Charge Dart Fire.ksnd");
        self.FireProjectile("particles/Charge_Dart.particle", kVec3(0, 0, 0));
        
        float scalar = self.AnimTrackComponent().PlayTime() * 2.75f;
        if(scalar > 6.0f)
        {
            scalar = 6.0f;
        }
        
        switch(int(scalar))
        {
        case 0:
            self.FireProjectile("particles/Charge_Dart_1.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(1);
            break;
        case 1:
            self.FireProjectile("particles/Charge_Dart_2.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(1);
            break;
        case 2:
            self.FireProjectile("particles/Charge_Dart_3.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(1);
            break;
        case 3:
            self.FireProjectile("particles/Charge_Dart_4.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(1);
            break;
        case 4:
            self.FireProjectile("particles/Charge_Dart_5.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(1);
            break;
        case 5:
            self.FireProjectile("particles/Charge_Dart_6.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(2);
            break;
        case 6:
            self.FireProjectile("particles/Charge_Dart_7.particle", kVec3(0, -10, 0));
            self.Owner().ConsumeAmmo(3);
            break;
        }
    }
}

/*
==============================================================
TurokShotgun
==============================================================
*/

final class TurokShotgun : TurokWeapon
{   
    float m_fYOffset;
    float m_fInitialYOffset;
    float m_fReturnTime;
    
    TurokShotgun(kWeapon @actor)
    {
        super(actor);
        
        m_fInitialYOffset = self.Origin().y;
        m_fYOffset = 0.0f;
        m_fReturnTime = 0.0f;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fYOffset);
        SERIALIZE(m_fInitialYOffset);
        SERIALIZE(m_fReturnTime);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fYOffset);
        DESERIALIZE_FLOAT(m_fInitialYOffset);
        DESERIALIZE_FLOAT(m_fReturnTime);
        
        self.Origin().y = m_fYOffset;
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        self.Origin().y = m_fYOffset;
                
        if(m_fReturnTime <= 0.0f)
        {
            if(self.AnimTrackComponent().PlayingID() == ANIM_WPN_ATK1)
            {
                if(self.AnimTrackComponent().TrackTime() > 0.8f)
                {
                    m_fReturnTime = 1.0f;
                } 
            }
            
            return;
        }
        
        m_fReturnTime = Math::Max(m_fReturnTime - (GAME_DELTA_TIME * 0.35f), 0.0f);
        
        float t = Math::HermiteBlend(0.0f, 0.0f, Math::SmoothStep(0.0, 0.5, 1.0f - m_fReturnTime));
        m_fYOffset = (m_fInitialYOffset - m_fYOffset) * t + m_fYOffset;
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        m_fYOffset = 5*GAME_SCALE;
        m_fReturnTime = 0.0f;
    }
    
    /*
    ==============================================================
    OnLower
    ==============================================================
    */
    
    void OnLower(void)
    {
        m_fReturnTime = 0.0f;
    }
    
    /*
    ==============================================================
    OnRaise
    ==============================================================
    */
    
    void OnRaise(void)
    {
        m_fReturnTime = 0.0f;
    }
}

/*
==============================================================
TurokPlasmaRifle
==============================================================
*/

final class TurokPlasmaRifle : TurokWeapon
{   
    float m_fSpeed;
    float m_fFireTime;
    
    TurokPlasmaRifle(kWeapon @actor)
    {
        m_fSpeed = 4.0f;
        m_fFireTime = 0.0f;
        super(actor);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        m_fSpeed = 4.0f;
        m_fFireTime = 0.0f;
    }
    
    /*
    ==============================================================
    OnFire
    ==============================================================
    */
    
    void OnFire(void)
    {
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if(self.AnimTrackComponent().PlayingID() == ANIM_WPN_ATK1)
        {
            if(self.AnimTrackComponent().TrackTime() >= 0.5f)
            {
                if(m_fSpeed <= 4.0f)
                {
                    m_fSpeed = IsMultiplayer() ? 64.0f : 32.0f;
                    m_fFireTime = 1.0f;
                }
                else
                {
                    self.AnimTrackComponent().ChangeSpeed(m_fSpeed);
                
                    m_fFireTime = Math::Max(m_fFireTime - (GAME_DELTA_TIME * 0.5f), 0.0f);
                    float t = Math::HermiteBlend(0.0f, 0.0f, Math::SmoothStep(0.125, 0.4, 1.0f - m_fFireTime));
                    m_fSpeed = (4.0f - m_fSpeed) * t + m_fSpeed;
                }
            }
        }
    }
    
    /*
    ==============================================================
    OnLower
    ==============================================================
    */
    
    void OnLower(void)
    {
        m_fSpeed = 4.0f;
        m_fFireTime = 0.0f;
    }
    
    /*
    ==============================================================
    OnRaise
    ==============================================================
    */
    
    void OnRaise(void)
    {
        m_fSpeed = 4.0f;
        m_fFireTime = 0.0f;
    }
}

/*
==============================================================
TurokFireStormCannon
==============================================================
*/

class TurokFireStormCannon : TurokWeapon
{   
    float m_fFireIntervals;
    float m_spinRotVel;
    float m_spinAngle;
    bool m_bFiring;
    
    TurokFireStormCannon(kWeapon @actor)
    {
        m_fFireIntervals = 0.0f;
        m_spinRotVel = 0.0f;
        m_spinAngle = 0.0f;
        m_bFiring = false;
        
        super(actor);
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fFireIntervals);
        SERIALIZE(m_spinRotVel);
        SERIALIZE(m_spinAngle);
        SERIALIZE(m_bFiring);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fFireIntervals);
        DESERIALIZE_FLOAT(m_spinRotVel);
        DESERIALIZE_FLOAT(m_spinAngle);
        DESERIALIZE_BOOL(m_bFiring);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        m_fFireIntervals = 0.125f;
        self.PlayLoopingSound("sounds/shaders/Firestorm Cannon Spin.ksnd");
    }
    
    /*
    ==============================================================
    OnFire
    ==============================================================
    */
    
    void OnFire(void)
    {
        m_spinRotVel = Math::IncMax(m_spinRotVel, 0.2f, 5);
        m_spinAngle += m_spinRotVel;
        
        m_bFiring = true;
        
        if(m_spinRotVel < 3.75f)
        {
            return;
        }
        
        m_fFireIntervals -= GAME_DELTA_TIME;
        if(m_fFireIntervals <= 0)
        {
            if(IsMultiplayer())
            {
                // Consume extra ammo in multiplayer
                self.Owner().ConsumeAmmo(1);
            }
            self.FireProjectile("particles/Firestorm_Cannon.particle", kVec3(0, 0, 6));
            self.PlayLoopingSound("sounds/shaders/Firestorm Cannon Fire.ksnd");
            m_fFireIntervals = 0.125f;
        }
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
        self.PlaySound("sounds/shaders/Firestorm Cannon Spin Stop.ksnd");
        self.StopLoopingSounds();
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        self.RenderMeshComponent().SetRotationOffset(1, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(7, Math::Deg2Rad(-m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(8, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(9, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(10, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(11, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(12, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        
        m_spinAngle += m_spinRotVel;
        
        if(m_bFiring)
        {
            m_bFiring = false;
            return;
        }
        
        m_spinRotVel = Math::IncMax(m_spinRotVel, Math::Deg2Rad(0.375f) * GAME_FRAME_UNIT, 0);
    }
    
    /*
    ==============================================================
    OnLower
    ==============================================================
    */
    
    void OnLower(void)
    {
        m_spinRotVel = 0.0f;
    }
    
    /*
    ==============================================================
    OnRaise
    ==============================================================
    */
    
    void OnRaise(void)
    {
        m_spinRotVel = 0.0f;
    }
}

/*
==============================================================
TurokFireStormCannonMP
==============================================================
*/

final class TurokFireStormCannonMP : TurokFireStormCannon
{
    TurokFireStormCannonMP(kWeapon @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnFire
    ==============================================================
    */
    
    void OnFire(void)
    {
        m_spinRotVel = Math::IncMax(m_spinRotVel, 0.2f, 5);
        m_spinAngle += m_spinRotVel;
        
        m_bFiring = true;
        
        if(m_spinRotVel < 3.75f)
        {
            return;
        }
        
        m_fFireIntervals -= GAME_DELTA_TIME;
        if(m_fFireIntervals <= 0)
        {
            self.FireProjectile("particles/Multiplayer5.particle", kVec3(0, 0, 6));
            self.PlayLoopingSound("sounds/shaders/Firestorm Cannon Fire.ksnd");
            m_fFireIntervals = 0.125f;
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        self.RenderMeshComponent().SetRotationOffset(2, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        m_spinAngle += m_spinRotVel;
        
        if(m_bFiring)
        {
            m_bFiring = false;
            return;
        }
        
        m_spinRotVel = Math::IncMax(m_spinRotVel, Math::Deg2Rad(0.375f) * GAME_FRAME_UNIT, 0);
    }
}

/*
==============================================================
TurokSunfirePod
==============================================================
*/

final class TurokSunfirePod : TurokWeapon
{   
    float m_fScalar;
    
    TurokSunfirePod(kWeapon @actor)
    {
        super(actor);
        m_fScalar = 1.0f;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fScalar);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fScalar);
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        m_fScalar = Math::Max(0.5f, Math::Min(1.5f, self.AnimTrackComponent().PlayTime()));
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pPod)
    {
        pPod.Velocity() *= m_fScalar;
        m_fScalar = 1.0f;
        self.Owner().ConsumeAmmo(1);
    }
}

/*
==============================================================
TurokBore
==============================================================
*/

final class TurokBore : TurokWeapon
{   
    TurokBore(kWeapon @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pMissile)
    {
        // target is set if this weapon is able to track things
        if(self.GetTarget() is null)
        {
            return;
        }
        
        // have missile target the tracked object
        pMissile.SetTarget(self.GetTarget());
        
        // spawn a controller for manipulating the missile
        kActor@ pController =
        ActorFactory.Spawn(
            kActor_Misc_BoreController,
            pMissile.Origin(),
            0.0f,
            0.0f,
            0.0f);
            
        if(pController is null)
        {
            return;
        }
        
        // pController.PlayLoopingSound("sounds/shaders/Cerebral Bore Track Loop.ksnd");
        
        // make controller target the missile so it can control it
        pController.SetTarget(pMissile);
        
        // push the controller over the network for client replication
        if(Network.IsNetgame() && Network.IsServer() && !(pMissile.GetTarget() is null))
        {
            NetSend::Clear();
            
            // writes NPN_SCRIPT and then the string of the global function decl
            NetSend::WriteScriptHeader("void NetworkRecvSpawnBoreController(void)");
            NetSend::WriteInt16(pMissile.NetID());
            NetSend::WriteInt16(pMissile.GetTarget().NetID());
            
            // transmit pbuffer
            // when client recieves this, it parses the string which is the decl to the global function and then calls it
            Network.TransmitToAll();
        }
    }
}

/*
==============================================================
TurokQuadMissileLauncher
==============================================================
*/

final class TurokQuadMissileLauncher : TurokWeapon
{   
    TurokQuadMissileLauncher(kWeapon @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        self.Owner().ConsumeAmmo(1);
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pMissile)
    {
        self.PlaySound("sounds/shaders/Scorpion Missile Launch.ksnd");
        
        // target is set if this weapon is able to track things
        if(self.GetTarget() is null)
        {
            return;
        }
        
        // have missile target the tracked object
        pMissile.SetTarget(self.GetTarget());
        
        // spawn a controller for manipulating the missile
        kActor@ pController =
        ActorFactory.Spawn(
            kActor_Misc_MissileController,
            pMissile.Origin(),
            0.0f,
            0.0f,
            0.0f);
            
        if(pController is null)
        {
            return;
        }
        
        // make controller target the missile so it can control it
        pController.SetTarget(pMissile);
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        /*if(self.AnimTrackComponent().PlayingID() == ANIM_WPN_ATK1 && IsMultiplayer())
        {
            self.AnimTrackComponent().ChangeSpeed(2.0f);
        }*/
    }
}

/*
==============================================================
TurokFlameThrower
==============================================================
*/

final class TurokFlameThrower : TurokWeapon
{   
    float m_fTimeBeforeAmmoConsumption;
    
    TurokFlameThrower(kWeapon @actor)
    {
        super(actor);
        m_fTimeBeforeAmmoConsumption = 0;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fTimeBeforeAmmoConsumption);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fTimeBeforeAmmoConsumption);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        self.StopLoopingSounds();
        self.PlayLoopingSound("sounds/shaders/Flame Thrower Loop.ksnd");
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
        
        self.StopLoopingSounds();
        self.PlayLoopingSound("sounds/shaders/Flame Thrower Gas Loop.ksnd");
    }
    
    /*
    ==============================================================
    OnFire
    ==============================================================
    */
    
    void OnFire(void)
    {
        const float fAmount = IsMultiplayer() ? 12 : 6;
        
        m_fTimeBeforeAmmoConsumption += (GAME_DELTA_TIME*fAmount);
        
        if(m_fTimeBeforeAmmoConsumption >= 1.0f)
        {
            self.Owner().ConsumeAmmo(1);
            m_fTimeBeforeAmmoConsumption = 0;
        }
    }
    
    /*
    ==============================================================
    OnLower
    ==============================================================
    */
    
    void OnLower(void)
    {
        self.StopLoopingSounds();
    }
    
    /*
    ==============================================================
    OnRaise
    ==============================================================
    */
    
    void OnRaise(void)
    {
        self.PlayLoopingSound("sounds/shaders/Flame Thrower Gas Loop.ksnd");
    }
}

/*
==============================================================
TurokRazorWind
==============================================================
*/

final class TurokRazorWind : TurokWeapon
{   
    float m_fZOffset;
    float m_fInitialZOffset;
    float m_fSpinAngle;
    float m_fExtraSpinSpeed;
    
    TurokRazorWind(kWeapon @actor)
    {
        super(actor);
        
        m_fInitialZOffset = self.Origin().z;
        m_fZOffset = m_fInitialZOffset;
        m_fSpinAngle = 0.0;
        m_fExtraSpinSpeed = 0.0;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_fZOffset);
        SERIALIZE(m_fInitialZOffset);
        SERIALIZE(m_fSpinAngle);
        SERIALIZE(m_fExtraSpinSpeed);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_fZOffset);
        DESERIALIZE_FLOAT(m_fInitialZOffset);
        DESERIALIZE_FLOAT(m_fSpinAngle);
        DESERIALIZE_FLOAT(m_fExtraSpinSpeed);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        self.RenderMeshComponent().SetRotationOffset(2, m_fSpinAngle, 0, 0, 1);
        m_fSpinAngle += Math::Deg2Rad(10.0f + (m_fExtraSpinSpeed * 20.0)) * 0.5f * GAME_FRAME_TIME;
        m_fExtraSpinSpeed = Math::Max(m_fExtraSpinSpeed - GAME_DELTA_TIME, 0.0f);
        
        self.Origin().y = m_fZOffset;
        
        if(m_fZOffset != m_fInitialZOffset)
        {
            m_fZOffset = (m_fInitialZOffset - m_fZOffset) * 0.1f + m_fZOffset;
        }
        
        if(self.GetTarget() is null)
        {
            return;
        }
        
        if(self.GetTarget().IsStale())
        {
            kActor@ pNull = null;
            
            self.SetTarget(pNull);
            self.RenderMeshComponent().SwapOutMesh(0);
            self.LockControls(false);
            
            m_fZOffset = (5*GAME_SCALE);
            m_fExtraSpinSpeed = 1.0f;
        }
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pBlade)
    {
        self.RenderMeshComponent().AltTexture() = -1;
        self.RenderMeshComponent().SwapOutMesh(1);
        self.LockControls(true);
        
        // spawn a controller for manipulating the blade
        kActor@ pController =
        ActorFactory.Spawn(
            kActor_Misc_BladeController,
            pBlade.Origin(),
            0.0f,
            0.0f,
            0.0f);
            
        if(pController is null)
        {
            return;
        }
        
        // target the controller so we can monitor it's lifetime
        self.SetTarget(pController);
        
        // make controller target the blade so it can control it
        pController.SetTarget(pBlade);
        pController.PlayLoopingSound("sounds/shaders/Razor Wind Fly Loop.ksnd");
    }
}

/*
==============================================================
TurokTorpedoLauncher
==============================================================
*/

final class TurokTorpedoLauncher : TurokWeapon
{   
    float m_spinRotVel;
    float m_spinAngle;
    
    TurokTorpedoLauncher(kWeapon @actor)
    {
        super(actor);
        
        m_spinRotVel = 0;
        m_spinAngle = 0;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_spinRotVel);
        SERIALIZE(m_spinAngle);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_spinRotVel);
        DESERIALIZE_FLOAT(m_spinAngle);
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        float fAccel, fTarget;
        
        if((self.Owner().Buttons() & BC_FORWARD) != 0)
        {
            kVec3 vVel = self.Owner().Actor().MovementComponent().Velocity();
            float fSpeed = vVel.Unit();
            
            fTarget = Math::Deg2Rad(24);
            fAccel = fSpeed * 0.0525f;
            
            self.PlayLoopingSound("sounds/shaders/Torpedo Launcher Motor Loop.ksnd");
        }
        else
        {
            fAccel = Math::Deg2Rad(0.5f);
            fTarget = (m_spinRotVel > 0) ? Math::Deg2Rad(1) : Math::Deg2Rad(-1);
            self.StopLoopingSounds();
        }
        
        m_spinRotVel = Math::IncMax(m_spinRotVel, fAccel, fTarget);
        m_spinAngle += m_spinRotVel;
        
        self.RenderMeshComponent().SetRotationOffset(2, m_spinAngle, 0, 1, 0);
        
        int animID = self.AnimTrackComponent().PlayingID();
        
        // firing animation pauses for a really long time and its annoying.
        // so force the animation to complete half-way through playing
        switch(animID)
        {
        case ANIM_WPN_ATK1:
        case ANIM_WPN_ATK2:
        case ANIM_WPN_ATK3:
        case ANIM_WPN_ATK4:
            if(self.AnimTrackComponent().TrackTime() >= 0.5f)
            {
                self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
            }
            break;
        default:
            break;
        };
    }
    
    /*
    ==============================================================
    OnLower
    ==============================================================
    */
    
    void OnLower(void)
    {
        self.StopLoopingSounds();
        m_spinAngle = 0;
        self.RenderMeshComponent().SetRotationOffset(2, Math::Deg2Rad(0), 0, 1, 0);
    }
    
    /*
    ==============================================================
    OnRaise
    ==============================================================
    */
    
    void OnRaise(void)
    {
        m_spinRotVel = 0;
    }
    
    /*
    ==============================================================
    OnFiredParticleFromAnim
    ==============================================================
    */
    
    void OnFiredParticleFromAnim(kParticle@ pBlade)
    {
        // hack so that we consume ammo only once per fire animation
        if(self.AnimTrackComponent().TrackTime() < 0.3f)
        {
            self.Owner().ConsumeAmmo(1);
        }
    }
}

/*
==============================================================
TurokNuke
==============================================================
*/

final class TurokNuke : TurokWeapon
{   
    float m_spinRotVel;
    float m_spinAngle;
    bool m_bReleased;
    
    TurokNuke(kWeapon @actor)
    {
        super(actor);
        
        m_spinRotVel = 0;
        m_spinAngle = 0;
        m_bReleased = false;
    }
    
    /*
    ==============================================================
    OnSerialize
    ==============================================================
    */
    
    void OnSerialize(kDict& out dict)
    {
        SERIALIZE(m_spinRotVel);
        SERIALIZE(m_spinAngle);
        SERIALIZE(m_bReleased);
    }
    
    /*
    ==============================================================
    OnDeserialize
    ==============================================================
    */
    
    void OnDeserialize(kDict& in dict)
    {
        DESERIALIZE_FLOAT(m_spinRotVel);
        DESERIALIZE_FLOAT(m_spinAngle);
        DESERIALIZE_BOOL(m_bReleased);
    }
    
    /*
    ==============================================================
    OnBeginFire
    ==============================================================
    */
    
    void OnBeginFire(void)
    {
        self.PlayLoopingSound("sounds/shaders/Nuke Windup.ksnd");
    }
    
    /*
    ==============================================================
    OnEndFire
    ==============================================================
    */
    
    void OnEndFire(void)
    {
        self.StopLoopingSounds();
        self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
        
        if(self.AnimTrackComponent().PlayTime() >= 2.0f)
        {
            m_spinRotVel = 2;
            self.RenderMeshComponent().SetRotationOffset(1, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
            self.RenderMeshComponent().SetRotationOffset(12, Math::Deg2Rad(-m_spinAngle*2), 0, 1, 0);
            return;
        }
        
        m_bReleased = true;
        
        // note that this function is called before the engine sets the 'discharge'
        // animation on the weapon. flagging nointerrupt will prevent that from happening,
        // allowing us to set this animation
        self.AnimTrackComponent().Blend(ANIM_WPN_IDLE, 4, 16, ANF_NOINTERRUPT);
    }
    
    /*
    ==============================================================
    OnFire
    ==============================================================
    */
    
    void OnFire(void)
    {
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if(m_bReleased)
        {
            // clear the flag so we can immediately start using the weapon again
            self.AnimTrackComponent().Flags() |= ANF_CYCLECOMPLETED;
            self.AnimTrackComponent().Flags() &= ~ANF_NOINTERRUPT;
            m_bReleased = false;
        }
        
        int animID = self.AnimTrackComponent().PlayingID();
        
        if(animID == ANIM_WPN_CHARGE)
        {
            m_spinRotVel = Math::IncMax(m_spinRotVel, 0.025f, 8);
        }
        else if(animID == ANIM_WPN_IDLE || animID == ANIM_WPN_RUN)
        {
            m_spinRotVel = Math::IncMax(m_spinRotVel, Math::Deg2Rad(0.375f) * GAME_FRAME_UNIT, 2);
        }
        else
        {
            return;
        }
        
        self.RenderMeshComponent().SetRotationOffset(1, Math::Deg2Rad(m_spinAngle), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(12, Math::Deg2Rad(-m_spinAngle*2), 0, 1, 0);
        
        m_spinAngle += m_spinRotVel;
    }
    
    /*
    ==============================================================
    OnLower
    ==============================================================
    */
    
    void OnLower(void)
    {
        m_spinAngle = 0;
        self.RenderMeshComponent().SetRotationOffset(1, Math::Deg2Rad(0), 0, 1, 0);
        self.RenderMeshComponent().SetRotationOffset(12, Math::Deg2Rad(0), 0, 1, 0);
    }
    
    /*
    ==============================================================
    OnRaise
    ==============================================================
    */
    
    void OnRaise(void)
    {
        m_spinRotVel = 0;
    }
}
