You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
718 lines
19 KiB
718 lines
19 KiB
//========= 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();
|
|
}
|