diff options
Diffstat (limited to 'game/shared/tf/tf_weapon_grenadelauncher.cpp')
| -rw-r--r-- | game/shared/tf/tf_weapon_grenadelauncher.cpp | 718 |
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(); +} |