summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_grenadelauncher.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_weapon_grenadelauncher.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/tf/tf_weapon_grenadelauncher.cpp')
-rw-r--r--game/shared/tf/tf_weapon_grenadelauncher.cpp718
1 files changed, 718 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_grenadelauncher.cpp b/game/shared/tf/tf_weapon_grenadelauncher.cpp
new file mode 100644
index 0000000..793161a
--- /dev/null
+++ b/game/shared/tf/tf_weapon_grenadelauncher.cpp
@@ -0,0 +1,718 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_weapon_grenadelauncher.h"
+#include "tf_fx_shared.h"
+#include "tf_weapon_grenade_pipebomb.h"
+#include "tf_gamerules.h"
+#include "in_buttons.h"
+#include "tf_weaponbase_gun.h"
+
+// Client specific.
+#ifdef CLIENT_DLL
+#include "c_tf_player.h"
+#include "c_tf_gamestats.h"
+#include "bone_setup.h"
+
+// Server specific.
+#else
+#include "tf_player.h"
+#include "tf_gamestats.h"
+#include "tf_fx.h"
+#endif
+
+ConVar tf_double_donk_window( "tf_double_donk_window", "0.5", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "How long after an impact from a cannonball that an explosion will count as a double-donk." );
+
+#define TF_TUBE_COUNT 6
+
+// X is time as a fraction of cProceduralBarrelRotationTime, which is in seconds.
+// Y is rotation in degrees
+// Z is slope at Y.
+// These are hermite spline control points that match maya.
+const Vector cProceduralBarrelRotationAnimationPoints[] =
+{
+ Vector( 0, 0, 0 ),
+ Vector( 0.7519f, 63.546f, 0 ),
+ Vector( 1.0f, 60, 0 )
+};
+
+static_assert( ARRAYSIZE( cProceduralBarrelRotationAnimationPoints ) > 1, "cProceduralBarrelRotationAnimationPoints must have at least two elements." );
+
+const float cProceduralBarrelRotationTime = 0.2666f;
+
+//=============================================================================
+//
+// Weapon Grenade Launcher tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFGrenadeLauncher, DT_WeaponGrenadeLauncher )
+
+BEGIN_NETWORK_TABLE( CTFGrenadeLauncher, DT_WeaponGrenadeLauncher )
+#ifdef CLIENT_DLL
+ RecvPropFloat( RECVINFO( m_flDetonateTime ) ),
+ RecvPropInt( RECVINFO( m_iCurrentTube ) ),
+ RecvPropInt( RECVINFO( m_iGoalTube ) ),
+#else
+ SendPropFloat( SENDINFO( m_flDetonateTime ) ),
+ SendPropInt( SENDINFO( m_iCurrentTube ) ),
+ SendPropInt( SENDINFO( m_iGoalTube ) ),
+#endif
+END_NETWORK_TABLE()
+
+#ifdef CLIENT_DLL
+BEGIN_PREDICTION_DATA( CTFGrenadeLauncher )
+ DEFINE_FIELD( m_flDetonateTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iCurrentTube, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iGoalTube, FIELD_INTEGER )
+END_PREDICTION_DATA()
+#endif
+
+LINK_ENTITY_TO_CLASS( tf_weapon_grenadelauncher, CTFGrenadeLauncher );
+PRECACHE_WEAPON_REGISTER( tf_weapon_grenadelauncher );
+
+CREATE_SIMPLE_WEAPON_TABLE( TFCannon, tf_weapon_cannon )
+
+// Server specific.
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CTFGrenadeLauncher )
+END_DATADESC()
+#endif
+
+#define TF_GRENADE_LAUNCER_MIN_VEL 1200
+
+#define TF_DETONATE_MODE_AIR 2
+
+#define TF_WEAPON_CANNON_CHARGE_SOUND "Weapon_LooseCannon.Charge"
+
+//=============================================================================
+//
+// Weapon Grenade Launcher functions.
+//
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+CTFGrenadeLauncher::CTFGrenadeLauncher()
+{
+ m_bReloadsSingly = true;
+
+#ifdef CLIENT_DLL
+ m_pCannonFuseSparkEffect = NULL;
+ m_pCannonCharge = NULL;
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+CTFGrenadeLauncher::~CTFGrenadeLauncher()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::Spawn( void )
+{
+ m_iAltFireHint = HINT_ALTFIRE_GRENADELAUNCHER;
+ BaseClass::Spawn();
+
+ ResetDetonateTime();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset the charge when we holster
+//-----------------------------------------------------------------------------
+bool CTFGrenadeLauncher::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ ResetDetonateTime();
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset the charge when we deploy
+//-----------------------------------------------------------------------------
+bool CTFGrenadeLauncher::Deploy( void )
+{
+ ResetDetonateTime();
+ return BaseClass::Deploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFGrenadeLauncher::GetMaxClip1( void ) const
+{
+#ifdef _X360
+ return TF_GRENADE_LAUNCHER_XBOX_CLIP;
+#endif
+
+ return BaseClass::GetMaxClip1();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFGrenadeLauncher::GetDefaultClip1( void ) const
+{
+#ifdef _X360
+ return TF_GRENADE_LAUNCHER_XBOX_CLIP;
+#endif
+
+ return BaseClass::GetDefaultClip1();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::PrimaryAttack( void )
+{
+ // Check for ammunition.
+ if ( m_iClip1 <= 0 && m_iClip1 != -1 )
+ return;
+
+ // Are we capable of firing again?
+ if ( m_flNextPrimaryAttack > gpGlobals->curtime )
+ return;
+
+ if ( !CanAttack() )
+ {
+ ResetDetonateTime();
+ return;
+ }
+
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+
+ if ( CanCharge() )
+ {
+ if ( m_flDetonateTime == 0.f )
+ {
+ m_flDetonateTime = gpGlobals->curtime + GetMortarDetonateTimeLength();
+ SendWeaponAnim( ACT_VM_PULLBACK );
+#ifdef CLIENT_DLL
+ EmitSound( TF_WEAPON_CANNON_CHARGE_SOUND );
+#endif // CLIENT_DLL
+ }
+ else
+ {
+#ifdef CLIENT_DLL
+ StartChargeEffects();
+#endif // CLIENT_DLL
+ }
+ }
+ else
+ {
+ LaunchGrenade();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::ItemPostFrame( void )
+{
+ BaseClass::ItemPostFrame();
+
+ if ( m_flDetonateTime > 0.f )
+ {
+ if ( m_flDetonateTime > gpGlobals->curtime )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ // If we're not holding down the attack button, launch our grenade
+ if ( m_iClip1 > 0 && !(pPlayer->m_nButtons & IN_ATTACK) )
+ {
+ LaunchGrenade();
+ }
+ }
+ else
+ {
+ Misfire();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::Misfire( void )
+{
+ BaseClass::Misfire();
+
+ LaunchGrenade();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::WeaponIdle( void )
+{
+ BaseClass::WeaponIdle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::FireProjectileInternal( CTFPlayer* pTFPlayer )
+{
+#ifdef GAME_DLL
+ CTFGrenadePipebombProjectile *pProjectile = static_cast<CTFGrenadePipebombProjectile*>( FireProjectile( pTFPlayer ) );
+ if ( pProjectile )
+ {
+ if ( GetDetonateMode() == TF_DETONATE_MODE_AIR )
+ {
+ pProjectile->m_bWallShatter = true;
+ }
+
+ if ( m_flDetonateTime > 0.f )
+ {
+ float flDetonateTimeLength = ( gpGlobals->curtime - GetChargeBeginTime() );
+ pProjectile->SetDetonateTimerLength( flDetonateTimeLength );
+ if ( flDetonateTimeLength == 0.f )
+ {
+ trace_t tr;
+ UTIL_TraceLine( pProjectile->GetAbsOrigin(), pTFPlayer->EyePosition(), MASK_SOLID, pProjectile, COLLISION_GROUP_NONE, &tr );
+ pProjectile->Explode( &tr, GetDamageType() );
+ }
+ }
+
+ float flDetonationPenalty = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flDetonationPenalty, grenade_detonation_damage_penalty );
+ if ( flDetonationPenalty != 1.0f )
+ {
+ // Setting the initial damage of a grenade lower will set its fused time damage lower
+ // on contact detonations reset the damage to max
+ pProjectile->SetDamage( pProjectile->GetDamage() * flDetonationPenalty );
+ }
+ }
+#else
+ FireProjectile( pTFPlayer );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::WeaponReset( void )
+{
+ BaseClass::WeaponReset();
+
+ ResetDetonateTime();
+
+ m_iCurrentTube = 0;
+ m_iGoalTube = 0;
+ m_bCurrentAndGoalTubeEqual = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFGrenadeLauncher::SendWeaponAnim( int iActivity )
+{
+ // Client procedurally animates the barrel bone
+ if ( iActivity == ACT_VM_PRIMARYATTACK )
+ {
+ m_iGoalTube = ( m_iCurrentTube + 1 ) % TF_TUBE_COUNT;
+ m_flBarrelRotateBeginTime = gpGlobals->curtime;
+ }
+
+ // When we start firing, play the startup firing anim first
+ if ( iActivity == ACT_VM_PRIMARYATTACK )
+ {
+ // If we're already playing the fire anim, let it continue. It loops.
+ if ( GetActivity() == ACT_VM_PRIMARYATTACK )
+ return true;
+
+ // Otherwise, play the start it
+ return BaseClass::SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ }
+
+ return BaseClass::SendWeaponAnim( iActivity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::PostFire()
+{
+ // Set next attack times.
+ float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
+
+ m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
+
+ SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() );
+
+ // Check the reload mode and behave appropriately.
+ if ( m_bReloadsSingly )
+ {
+ m_iReloadMode.Set( TF_RELOAD_START );
+ }
+
+#ifndef CLIENT_DLL
+ if ( CanCharge() )
+ {
+ Vector vPosition;
+ QAngle qAngles;
+ if ( GetAttachment( "muzzle", vPosition, qAngles ) )
+ {
+ CPVSFilter filter( vPosition );
+ TE_TFParticleEffect( filter, 0.f, "loose_cannon_bang", PATTACH_POINT, this, "muzzle" );
+ }
+ }
+#endif
+
+ ResetDetonateTime();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::LaunchGrenade( void )
+{
+ // Get the player owning the weapon.
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ CalcIsAttackCritical();
+
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+
+ pPlayer->SetAnimation( PLAYER_ATTACK1 );
+ pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
+
+ if ( !AutoFiresFullClipAllAtOnce() )
+ {
+ FireProjectileInternal( pPlayer );
+ }
+ else
+ {
+ int nCurrentClipSize = m_iClip1;
+ m_nLauncherSlot = 0;
+ int iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
+ QAngle punchAngle = pPlayer->GetPunchAngle();
+ for ( int i=0; i<nCurrentClipSize; ++i, ++iSeed )
+ {
+ RandomSeed( iSeed );
+ FireProjectileInternal( pPlayer );
+ if ( i == 0 )
+ {
+ punchAngle = pPlayer->GetPunchAngle();
+ }
+ }
+ pPlayer->SetPunchAngle( punchAngle );
+ }
+
+#ifdef CLIENT_DLL
+ C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+ StopSound( TF_WEAPON_CANNON_CHARGE_SOUND );
+#else
+ pPlayer->SpeakWeaponFire();
+ CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+#endif
+
+ PostFire();
+
+ if ( TFGameRules()->GameModeUsesUpgrades() )
+ {
+ PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" );
+ }
+}
+
+void CTFGrenadeLauncher::AddDonkVictim( const CBaseEntity* pVictim )
+{
+ // Clear out old donk victims
+ FOR_EACH_VEC_BACK( m_vecDonkVictims, i )
+ {
+ if( m_vecDonkVictims[i].m_flExpireTime <= gpGlobals->curtime )
+ {
+ m_vecDonkVictims.Remove( i );
+ }
+ }
+
+ // Add new donk victim
+ Donks_t& donk = m_vecDonkVictims[ m_vecDonkVictims.AddToTail() ];
+ donk.m_hVictim.Set( pVictim );
+ donk.m_flExpireTime = gpGlobals->curtime + tf_double_donk_window.GetFloat();
+}
+
+
+bool CTFGrenadeLauncher::IsDoubleDonk( const CBaseEntity* pVictim ) const
+{
+ if( GetWeaponID() != TF_WEAPON_CANNON )
+ return false;
+
+ // Check each donk victim to see if we've donked them recently enough to
+ // score a "double-donk"
+ FOR_EACH_VEC( m_vecDonkVictims, i )
+ {
+ if( gpGlobals->curtime < m_vecDonkVictims[i].m_flExpireTime && m_vecDonkVictims[i].m_hVictim.Get() == pVictim )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+float CTFGrenadeLauncher::GetProjectileSpeed( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+
+ if ( pOwner && pOwner->m_Shared.GetCarryingRuneType() == RUNE_PRECISION )
+ return 3000.f;
+
+ float flLaunchSpeed = TF_GRENADE_LAUNCER_MIN_VEL;
+ CALL_ATTRIB_HOOK_FLOAT( flLaunchSpeed, mult_projectile_speed );
+ return flLaunchSpeed;
+}
+
+int CTFGrenadeLauncher::GetDetonateMode( void ) const
+{
+ int iMode = 0;
+ CALL_ATTRIB_HOOK_INT( iMode, set_detonate_mode );
+ return iMode;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detonate this demoman's pipebombs
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::SecondaryAttack( void )
+{
+#ifdef GAME_DLL
+
+ if ( !CanAttack() )
+ return;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ pOwner->DoClassSpecialSkill();
+
+#endif
+}
+
+bool CTFGrenadeLauncher::Reload( void )
+{
+ return BaseClass::Reload();
+}
+
+
+void CTFGrenadeLauncher::FireFullClipAtOnce( void )
+{
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+
+ LaunchGrenade();
+}
+
+
+bool CTFGrenadeLauncher::CanCharge( void )
+{
+ if ( GetWeaponID() == TF_WEAPON_CANNON )
+ {
+ return GetMortarDetonateTimeLength() > 0.f;
+ }
+
+ return false;
+}
+
+
+float CTFGrenadeLauncher::GetChargeBeginTime( void )
+{
+ // Inverse begin time logic to get charge bar to decrease from a full bar instead of increase from an empty bar
+ float flMortarDetonateTimeLength = GetMortarDetonateTimeLength();
+ float flModDetonateTimeLength = flMortarDetonateTimeLength;
+ if ( m_flDetonateTime > 0.f )
+ {
+ flModDetonateTimeLength = Clamp( m_flDetonateTime - gpGlobals->curtime, 0.f, flMortarDetonateTimeLength );
+ }
+
+ return gpGlobals->curtime - flModDetonateTimeLength;
+}
+
+
+float CTFGrenadeLauncher::GetChargeMaxTime( void )
+{
+ return GetMortarDetonateTimeLength();
+}
+
+
+void CTFGrenadeLauncher::ResetDetonateTime()
+{
+ m_flDetonateTime = 0.f;
+
+#ifdef CLIENT_DLL
+ StopChargeEffects();
+#endif // CLIENT_DLL
+}
+
+
+float CTFGrenadeLauncher::GetMortarDetonateTimeLength()
+{
+ float flMortarDetonateTimeLength = 0.f;
+ CALL_ATTRIB_HOOK_FLOAT( flMortarDetonateTimeLength, grenade_launcher_mortar_mode );
+ return flMortarDetonateTimeLength;
+}
+
+
+#ifdef CLIENT_DLL
+void CTFGrenadeLauncher::StartChargeEffects()
+{
+ if ( !m_pCannonFuseSparkEffect )
+ {
+ m_pCannonFuseSparkEffect = ParticleProp()->Create( "loose_cannon_sparks", PATTACH_POINT_FOLLOW, "cannon_fuse" );
+ }
+ if ( !m_pCannonCharge )
+ {
+ m_pCannonCharge = ParticleProp()->Create( "loose_cannon_buildup_smoke3", PATTACH_POINT_FOLLOW, "muzzle" );
+ }
+}
+
+
+void CTFGrenadeLauncher::StopChargeEffects()
+{
+ if ( m_pCannonFuseSparkEffect )
+ {
+ ParticleProp()->StopEmission( m_pCannonFuseSparkEffect );
+ m_pCannonFuseSparkEffect = NULL;
+ }
+ if ( m_pCannonCharge )
+ {
+ ParticleProp()->StopEmission( m_pCannonCharge );
+ m_pCannonCharge = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStudioHdr *CTFGrenadeLauncher::OnNewModel( void )
+{
+ CStudioHdr *hdr = BaseClass::OnNewModel();
+
+ m_iBarrelBone = LookupBone( "procedural_chamber" );
+
+ return hdr;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask )
+{
+ BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask );
+
+ if (m_iBarrelBone != -1)
+ {
+ UpdateBarrelMovement();
+
+ AngleQuaternion( RadianEuler( 0, 0, m_flBarrelAngle ), q[m_iBarrelBone] );
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For third person weapons.
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::OnDataChanged( DataUpdateType_t type )
+{
+ if ( m_bCurrentAndGoalTubeEqual && m_iCurrentTube != m_iGoalTube )
+ m_flBarrelRotateBeginTime = gpGlobals->curtime;
+
+ m_bCurrentAndGoalTubeEqual = ( m_iCurrentTube == m_iGoalTube );
+
+ BaseClass::OnDataChanged( type );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the velocity and position of the rotating barrel
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::UpdateBarrelMovement( void )
+{
+ if ( m_iGoalTube != m_iCurrentTube )
+ {
+ float flPartialRotationDeg = 0.0f;
+
+ const float tVal = ( gpGlobals->curtime - m_flBarrelRotateBeginTime ) / cProceduralBarrelRotationTime;
+
+ if ( tVal < 1.0f )
+ {
+ Assert( cProceduralBarrelRotationAnimationPoints[ 0 ].x == 0.0f );
+ Assert( cProceduralBarrelRotationAnimationPoints[ ARRAYSIZE( cProceduralBarrelRotationAnimationPoints ) - 1 ].x == 1.0f );
+
+ const Vector* pFirst = NULL;
+ const Vector* pSecond = NULL;
+
+ for ( int i = 1; i < ARRAYSIZE( cProceduralBarrelRotationAnimationPoints ); ++i )
+ {
+ // Need to be increasing in time, or we won't find the right span.
+ Assert( cProceduralBarrelRotationAnimationPoints[ i - 1 ].x < cProceduralBarrelRotationAnimationPoints[ i ].x );
+
+ if ( tVal <= cProceduralBarrelRotationAnimationPoints[ i ].x )
+ {
+ pFirst = &cProceduralBarrelRotationAnimationPoints[ i - 1 ];
+ pSecond = &cProceduralBarrelRotationAnimationPoints[ i ];
+ break;
+ }
+ }
+
+ Assert( pFirst && pSecond );
+ float flPartialT = ( tVal - pFirst->x ) / ( pSecond->x - pFirst->x );
+ flPartialRotationDeg = Hermite_Spline( pFirst->y, pSecond->y, pFirst->z, pSecond->z, flPartialT );
+ }
+ else
+ {
+ m_iCurrentTube = m_iGoalTube;
+ m_bCurrentAndGoalTubeEqual = true;
+ }
+
+ const float flBaseDeg = 60.0f * m_iCurrentTube;
+ m_flBarrelAngle = DEG2RAD( flBaseDeg + flPartialRotationDeg );
+ }
+}
+
+void CTFGrenadeLauncher::ViewModelAttachmentBlending( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask )
+{
+ int iBarrelBone = Studio_BoneIndexByName( hdr, "procedural_chamber" );
+
+ // Assert( iBarrelBone != -1 );
+
+ if ( iBarrelBone != -1 )
+ {
+ if ( hdr->boneFlags( iBarrelBone ) & boneMask )
+ {
+ RadianEuler a;
+ QuaternionAngles( q[ iBarrelBone ], a );
+
+ a.z = m_flBarrelAngle;
+
+ AngleQuaternion( a, q[ iBarrelBone ] );
+ }
+ }
+
+}
+
+#endif //CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// won't be called for w_ version of the model, so this isn't getting updated twice
+//-----------------------------------------------------------------------------
+void CTFGrenadeLauncher::ItemPreFrame( void )
+{
+#ifdef CLIENT_DLL
+ UpdateBarrelMovement();
+#endif
+
+#ifdef GAME_DLL
+ if ( gpGlobals->curtime > m_flBarrelRotateBeginTime + cProceduralBarrelRotationTime )
+ m_iCurrentTube = m_iGoalTube;
+#endif
+
+ BaseClass::ItemPreFrame();
+}