Team Fortress 2 Source Code as on 22/4/2020
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

//========= 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();
}