//========= Copyright Valve Corporation, All rights reserved. ============// // // TF Rocket Launcher // //============================================================================= #include "cbase.h" #include "tf_weapon_rocketlauncher.h" #include "tf_fx_shared.h" #include "tf_weaponbase_rocket.h" #include "in_buttons.h" #include "tf_gamerules.h" // Client specific. #ifdef CLIENT_DLL #include "c_tf_player.h" #include #include #include "soundenvelope.h" #include "particle_property.h" // Server specific. #else #include "tf_player.h" #include "tf_obj_sentrygun.h" #include "tf_projectile_arrow.h" #endif #define BOMBARDMENT_ROCKET_MODEL "models/buildables/sentry3_rockets.mdl" //============================================================================= // // Weapon Rocket Launcher tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher, DT_WeaponRocketLauncher ) BEGIN_NETWORK_TABLE( CTFRocketLauncher, DT_WeaponRocketLauncher ) #ifndef CLIENT_DLL // SendPropInt( SENDINFO( m_iSecondaryShotsFired ) ), #else // RecvPropInt( RECVINFO( m_iSecondaryShotsFired ) ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFRocketLauncher ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher, CTFRocketLauncher ); PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher ); // Server specific. #ifndef CLIENT_DLL BEGIN_DATADESC( CTFRocketLauncher ) END_DATADESC() #endif //============================================================================= // // Direct Hit tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher_DirectHit, DT_WeaponRocketLauncher_DirectHit ) BEGIN_NETWORK_TABLE( CTFRocketLauncher_DirectHit, DT_WeaponRocketLauncher_DirectHit ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFRocketLauncher_DirectHit ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher_directhit, CTFRocketLauncher_DirectHit ); PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher_directhit ); // Server specific. #ifndef CLIENT_DLL BEGIN_DATADESC( CTFRocketLauncher_DirectHit ) END_DATADESC() #endif //============================================================================= // // AIRSTRIKE BEGIN IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher_AirStrike, DT_WeaponRocketLauncher_AirStrike ) BEGIN_NETWORK_TABLE( CTFRocketLauncher_AirStrike, DT_WeaponRocketLauncher_AirStrike ) #ifndef CLIENT_DLL // SendPropInt( SENDINFO( m_iRocketKills ) ), #else // RecvPropInt( RECVINFO( m_iRocketKills ) ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFRocketLauncher_AirStrike ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher_airstrike, CTFRocketLauncher_AirStrike ); PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher_airstrike ); // Server specific. #ifndef CLIENT_DLL BEGIN_DATADESC( CTFRocketLauncher_AirStrike ) END_DATADESC() #endif // AIRSTRIKE END //CREATE_SIMPLE_WEAPON_TABLE( TFRocketLauncher_AirStrike, tf_weapon_rocketlauncher_airstrike ) //CREATE_SIMPLE_WEAPON_TABLE( TFRocketLauncher_Mortar, tf_weapon_rocketlauncher_mortar ) //============================================================================= // // Mortar tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher_Mortar, DT_WeaponRocketLauncher_Mortar ) BEGIN_NETWORK_TABLE( CTFRocketLauncher_Mortar, DT_WeaponRocketLauncher_Mortar ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFRocketLauncher_Mortar ) END_PREDICTION_DATA() #ifdef STAGING_ONLY LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher_mortar, CTFRocketLauncher_Mortar ); PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher_mortar ); #endif // STAGING_ONLY // Server specific. #ifndef CLIENT_DLL BEGIN_DATADESC( CTFRocketLauncher_Mortar ) END_DATADESC() #endif //============================================================================= // // Crossbow tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFCrossbow, DT_Crossbow ) BEGIN_NETWORK_TABLE( CTFCrossbow, DT_Crossbow ) #ifdef CLIENT_DLL RecvPropFloat( RECVINFO( m_flRegenerateDuration ) ), RecvPropFloat( RECVINFO( m_flLastUsedTimestamp ) ), #else SendPropFloat( SENDINFO( m_flRegenerateDuration ), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO( m_flLastUsedTimestamp ), 0, SPROP_NOSCALE ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFCrossbow ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_crossbow, CTFCrossbow ); PRECACHE_WEAPON_REGISTER( tf_weapon_crossbow ); // Server specific. #ifndef CLIENT_DLL BEGIN_DATADESC( CTFCrossbow ) END_DATADESC() #endif #ifdef STAGING_ONLY ConVar tf_airstrike_dmg_scale( "tf_airstrike_dmg_scale", "0.65", FCVAR_REPLICATED, "How much damage the mini rockets do compared to regular rocket" ); ConVar tf_mortar_allow_fulltracking( "tf_mortar_allow_fulltracking", "0.0", FCVAR_REPLICATED, "Enable to allow full tracking / infinte redirects for Mortar Launcher" ); #endif // STAGING_ONLY //----------------------------------------------------------------------------- // Purpose: // Input : - //----------------------------------------------------------------------------- CTFRocketLauncher::CTFRocketLauncher() { m_bReloadsSingly = true; m_nReloadPitchStep = 0; #ifdef GAME_DLL m_bIsOverloading = false; #endif //GAME_DLL } //----------------------------------------------------------------------------- // Purpose: // Input : - //----------------------------------------------------------------------------- CTFRocketLauncher::~CTFRocketLauncher() { } #ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRocketLauncher::Precache() { BaseClass::Precache(); PrecacheParticleSystem( "rocketbackblast" ); // FIXME: DO WE STILL NEED THESE?? PrecacheScriptSound( "MVM.GiantSoldierRocketShoot" ); PrecacheScriptSound( "MVM.GiantSoldierRocketShootCrit" ); PrecacheScriptSound( "MVM.GiantSoldierRocketExplode" ); PrecacheScriptSound( "Weapon_Airstrike.AltFire" ); PrecacheScriptSound( "Weapon_Airstrike.Fail" ); //Building_Sentrygun.FireRocket } #endif void CTFRocketLauncher::ModifyEmitSoundParams( EmitSound_t ¶ms ) { bool bBaseReloadSound = V_strcmp( params.m_pSoundName, "Weapon_RPG.Reload" ) == 0; if ( AutoFiresFullClip() && ( bBaseReloadSound || V_strcmp( params.m_pSoundName, "Weapon_DumpsterRocket.Reload" ) == 0 ) ) { float fMaxAmmoInClip = GetMaxClip1(); float fAmmoPercentage = static_cast< float >( m_nReloadPitchStep ) / fMaxAmmoInClip; // Play a sound that gets higher pitched as more ammo is added if ( bBaseReloadSound ) { params.m_pSoundName = "Weapon_DumpsterRocket.Reload_FP"; } else { params.m_pSoundName = "Weapon_DumpsterRocket.Reload"; } params.m_nPitch *= RemapVal( fAmmoPercentage, 0.0f, ( fMaxAmmoInClip - 1.0f ) / fMaxAmmoInClip, 0.79f, 1.19f ); params.m_nFlags |= SND_CHANGE_PITCH; m_nReloadPitchStep = MIN( GetMaxClip1() - 1, m_nReloadPitchStep + 1 ); // The last rocket goes in right when this sound happens so that you can launch it before a misfire IncrementAmmo(); m_bReloadedThroughAnimEvent = true; } } void CTFRocketLauncher::Misfire( void ) { BaseClass::Misfire(); #ifdef GAME_DLL if ( CanOverload() ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; CTFBaseRocket *pRocket = dynamic_cast< CTFBaseRocket* >( BaseClass::FireProjectile( pPlayer ) ); if ( pRocket ) { trace_t tr; UTIL_TraceLine( pRocket->GetAbsOrigin(), pPlayer->EyePosition(), MASK_SOLID, pRocket, COLLISION_GROUP_NONE, &tr ); pRocket->Explode( &tr, pPlayer ); } } #endif } //----------------------------------------------------------------------------- bool CTFRocketLauncher::CheckReloadMisfire( void ) { if ( !CanOverload() ) return false; #ifdef GAME_DLL CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( m_bIsOverloading ) { if ( Clip1() > 0 ) { Misfire(); return true; } else { m_bIsOverloading = false; } } else if ( Clip1() >= GetMaxClip1() || ( Clip1() > 0 && pPlayer && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) == 0 ) ) { Misfire(); m_bIsOverloading = true; return true; } #endif // GAME_DLL return false; } //----------------------------------------------------------------------------- bool CTFRocketLauncher::ShouldBlockPrimaryFire() { return !AutoFiresFullClip(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *CTFRocketLauncher::FireProjectile( CTFPlayer *pPlayer ) { m_flShowReloadHintAt = gpGlobals->curtime + 30; CBaseEntity *pRocket = BaseClass::FireProjectile( pPlayer ); m_nReloadPitchStep = MAX( 0, m_nReloadPitchStep - 1 ); #ifdef GAME_DLL int iProjectile = 0; CALL_ATTRIB_HOOK_INT( iProjectile, override_projectile_type ); if ( iProjectile == 0 ) { iProjectile = GetWeaponProjectileType(); } if ( pPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) && IsCurrentAttackARandomCrit() && ( iProjectile == TF_PROJECTILE_ROCKET ) ) { // Track consecutive crit shots for achievements m_iConsecutiveCrits++; if ( m_iConsecutiveCrits == 2 ) { pPlayer->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_SHOOT_MULT_CRITS ); } } else { m_iConsecutiveCrits = 0; } m_bIsOverloading = false; #endif if ( TFGameRules()->GameModeUsesUpgrades() ) { PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" ); } #ifdef STAGING_ONLY #ifdef GAME_DLL if ( pRocket && pPlayer && pPlayer->RocketJumped() ) { int iRocketsApplyImpuse = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iRocketsApplyImpuse, mod_rocket_launch_impulse ); if ( iRocketsApplyImpuse ) { // Apply force in opposite direction of rocket Vector vecDir = pRocket->GetAbsVelocity(); Vector vecFlightDir = -vecDir; VectorNormalize( vecFlightDir ); // Apply more force if looking down QAngle angEye = EyeAngles(); float flForce = ( angEye.x > 60.f ) ? 700.f : 400.f; Vector vecForce = vecFlightDir * flForce; // DevMsg( "x.Ang: %f\tForce: %f\n", angEye.x, flForce ); // Prevent insane speeds float flSpeed = vecForce.NormalizeInPlace(); const float flLimit = Min( 800.f, flSpeed ); pPlayer->ApplyAbsVelocityImpulse( flLimit * vecForce ); } } #endif // GAME_DLL #endif // STAGING_ONLY return pRocket; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRocketLauncher::ItemPostFrame( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( !pOwner ) return; BaseClass::ItemPostFrame(); #ifdef GAME_DLL if ( m_flShowReloadHintAt && m_flShowReloadHintAt < gpGlobals->curtime ) { if ( Clip1() < GetMaxClip1() ) { pOwner->HintMessage( HINT_SOLDIER_RPG_RELOAD ); } m_flShowReloadHintAt = 0; } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFRocketLauncher::DefaultReload( int iClipSize1, int iClipSize2, int iActivity ) { m_flShowReloadHintAt = 0; return BaseClass::DefaultReload( iClipSize1, iClipSize2, iActivity ); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRocketLauncher::CreateMuzzleFlashEffects( C_BaseEntity *pAttachEnt, int nIndex ) { BaseClass::CreateMuzzleFlashEffects( pAttachEnt, nIndex ); // Don't do backblast effects in first person C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( pOwner->IsLocalPlayer() ) return; ParticleProp()->Init( this ); ParticleProp()->Create( "rocketbackblast", PATTACH_POINT_FOLLOW, "backblast" ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFRocketLauncher::GetWeaponProjectileType( void ) const { return BaseClass::GetWeaponProjectileType(); } //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CTFRocketLauncher_AirStrike BEGIN //---------------------------------------------------------------------------------------------------------------------------------------------------------- CTFRocketLauncher_AirStrike::CTFRocketLauncher_AirStrike() { //m_iSecondaryShotsFired = 0; } #ifdef GAME_DLL //---------------------------------------------------------------------------------------------------------------------------------------------------------- void CTFRocketLauncher_AirStrike::OnPlayerKill( CTFPlayer *pVictim, const CTakeDamageInfo &info ) { BaseClass::OnPlayerKill( pVictim, info ); CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return; int iDecap = pOwner->m_Shared.GetDecapitations() + 1; if ( pVictim ) { iDecap += pVictim->m_Shared.GetDecapitations(); } pOwner->m_Shared.SetDecapitations( iDecap ); int iClipSizeOnKills = 0; CALL_ATTRIB_HOOK_INT( iClipSizeOnKills, clipsize_increase_on_kill ); if ( iClipSizeOnKills && ( iDecap >= iClipSizeOnKills ) ) { pOwner->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_AIRSTRIKE_MAX_CLIP ); } } //---------------------------------------------------------------------------------------------------------------------------------------------------------- #endif //----------------------------------------------------------------------------- int CTFRocketLauncher_AirStrike::GetCount( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return 0; return pOwner->m_Shared.GetDecapitations(); } ////---------------------------------------------------------------------------------------------------------------------------------------------------------- //void CTFRocketLauncher_AirStrike::PrimaryAttack( void ) //{ // CTFPlayer *pPlayer = GetTFPlayerOwner(); // if ( !pPlayer ) // return; // // // If the player is blast jumping and hasn't fired a shot yet, we can initiate // if ( pPlayer->m_Shared.InCond( TF_COND_BLASTJUMPING ) && m_iSecondaryShotsFired == 0 ) // { // FireSecondaryRockets(); // } // else // { // BaseClass::PrimaryAttack(); // } //} ////---------------------------------------------------------------------------------------------------------------------------------------------------------- //bool CTFRocketLauncher_AirStrike::CanHolster( void ) //{ // if ( m_iSecondaryShotsFired > 0 ) // return false; // // return BaseClass::CanHolster(); //} ////----------------------------------------------------------------------------- //void CTFRocketLauncher_AirStrike::ItemPostFrame( void ) //{ // // If allowed // FireSecondaryRockets(); // BaseClass::ItemPostFrame(); //} ////----------------------------------------------------------------------------- //void CTFRocketLauncher_AirStrike::ItemBusyFrame( void ) //{ // // If allowed // FireSecondaryRockets(); // BaseClass::ItemBusyFrame(); //} // ////----------------------------------------------------------------------------- //void CTFRocketLauncher_AirStrike::FireSecondaryRockets() //{ //#ifdef STAGING_ONLY // if ( m_flNextPrimaryAttack >= gpGlobals->curtime ) // return; // // CTFPlayer *pPlayer = GetTFPlayerOwner(); // if ( !pPlayer ) // return; // // if ( !( pPlayer->m_nButtons & IN_ATTACK ) && m_iSecondaryShotsFired == 0 ) // return; // // int iAirBombardment = 0; // CALL_ATTRIB_HOOK_INT( iAirBombardment, rj_air_bombardment ); // if ( !iAirBombardment ) // return; // // // This function and its checks are only on the server // if ( !pPlayer->m_Shared.InCond( TF_COND_BLASTJUMPING ) ) // { //#ifdef CLIENT_DLL // // play fail sound locally // //pPlayer->EmitSound( "Weapon_Airstrike.Fail" ); //#endif // m_iSecondaryShotsFired = 0; // return; // } // // if ( m_iClip1 <= 0 && m_iSecondaryShotsFired == 0 ) // return; // // if ( m_bReloadsSingly ) // { // m_iReloadMode.Set( TF_RELOAD_START ); // } // // float flFireDelay = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; // flFireDelay += GetFireDelay(); // CALL_ATTRIB_HOOK_FLOAT( flFireDelay, mult_postfiredelay ); // CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flFireDelay, hwn_mult_postfiredelay ); // // SendWeaponAnim( ACT_VM_PRIMARYATTACK ); // pPlayer->SetAnimation( PLAYER_ATTACK1 ); // pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); // m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay / 2.0f; // // // Want a different sound // pPlayer->EmitSound( "Weapon_Airstrike.AltFire" ); // //#ifdef GAME_DLL // // Server only - create the rocket. // Vector vecSrc; // QAngle angForward; // Vector vecOffset( 23.5f, 12.0f, -3.0f ); // GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false ); // // CTFProjectile_SentryRocket *pProjectile = CTFProjectile_SentryRocket::Create( vecSrc, angForward, this, pPlayer ); // // if ( pProjectile ) // { // pProjectile->SetCritical( IsCurrentAttackACrit() ); // pProjectile->SetDamage( GetProjectileDamage() * tf_airstrike_dmg_scale.GetFloat() ); // pProjectile->SetDamageForceScale( tf_airstrike_dmg_scale.GetFloat() ); // } // // if ( m_iSecondaryShotsFired == 0 ) // { // RemoveProjectileAmmo( pPlayer ); // } // // m_iSecondaryShotsFired++; // if ( m_iSecondaryShotsFired >= 3 ) // { // // Decrement ammo and reset // m_iSecondaryShotsFired = 0; // // Give normal delay between shots here // m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; // } //#endif // //#endif //} //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CTFRocketLauncher_Mortar BEGIN //---------------------------------------------------------------------------------------------------------------------------------------------------------- //CTFRocketLauncher_Mortar::CTFRocketLauncher_Mortar() //{ // //} //---------------------------------------------------------------------------------------------------------------------------------------------------------- CBaseEntity *CTFRocketLauncher_Mortar::FireProjectile( CTFPlayer *pPlayer ) { // Fire the rocket CBaseEntity* pRocket = BaseClass::FireProjectile( pPlayer ); // Add it to my list #ifdef GAME_DLL m_vecRockets.AddToTail( pRocket ); #endif return pRocket; } //---------------------------------------------------------------------------------------------------------------------------------------------------------- void CTFRocketLauncher_Mortar::SecondaryAttack( void ) { RedirectRockets(); } //----------------------------------------------------------------------------- void CTFRocketLauncher_Mortar::ItemPostFrame( void ) { #ifdef GAME_DLL CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 ) { // If allowed RedirectRockets(); } #endif BaseClass::ItemPostFrame(); } //----------------------------------------------------------------------------- void CTFRocketLauncher_Mortar::ItemBusyFrame( void ) { #ifdef GAME_DLL CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 ) { // If allowed RedirectRockets(); } #endif BaseClass::ItemBusyFrame(); } //----------------------------------------------------------------------------- void CTFRocketLauncher_Mortar::RedirectRockets( void ) { #ifdef GAME_DLL if ( m_vecRockets.Count() <= 0 ) return; CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( !pOwner ) return; Vector vecEye = pOwner->EyePosition(); Vector vecForward, vecRight, vecUp; AngleVectors( pOwner->EyeAngles(), &vecForward, &vecRight, &vecUp ); trace_t tr; UTIL_TraceLine( vecEye, vecEye + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); float flVel = 1100.0f; FOR_EACH_VEC_BACK( m_vecRockets, i ) { CBaseEntity* pRocket = m_vecRockets[i].Get(); // Remove targets that have disappeared if ( !pRocket || pRocket->GetOwnerEntity() != GetOwnerEntity() ) { m_vecRockets.Remove( i ); continue; } // Give the rocket a new target Vector vecDir = pRocket->WorldSpaceCenter() - tr.endpos; VectorNormalize( vecDir ); Vector vecVel = pRocket->GetAbsVelocity(); vecVel = -flVel * vecDir; pRocket->SetAbsVelocity( vecVel ); QAngle newAngles; VectorAngles( -vecDir, newAngles ); pRocket->SetAbsAngles( newAngles ); #ifdef STAGING_ONLY if ( !tf_mortar_allow_fulltracking.GetBool() ) { // only allow a single redirect m_vecRockets.Remove( i ); } #else m_vecRockets.Remove( i ); #endif } #endif } //---------------------------------------------------------------------------------------------------------------------------------------------------------- // CROSSBOW BEGIN //---------------------------------------------------------------------------------------------------------------------------------------------------------- bool CTFCrossbow::Holster( CBaseCombatWeapon *pSwitchingTo ) { // Allow Crossbow to silently reload like the flaregun if ( m_iClip1 == 0 ) { // These Values need to match the anim times since all this stuff is actually driven by animation sequence time in the base code float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay ); float flReloadTime = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeReload; CALL_ATTRIB_HOOK_FLOAT( flReloadTime, mult_reload_time ); CALL_ATTRIB_HOOK_FLOAT( flReloadTime, mult_reload_time_hidden ); CALL_ATTRIB_HOOK_FLOAT( flReloadTime, fast_reload ); float flIdleTime = GetLastPrimaryAttackTime() + flFireDelay + flReloadTime; if ( GetWeaponIdleTime() < flIdleTime ) { SetWeaponIdleTime( flIdleTime ); m_flNextPrimaryAttack = flIdleTime; } IncrementAmmo(); } return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- void CTFCrossbow::SecondaryAttack( void ) { // If this is the jarate bolt crossbow, make sure we are allowed to do it int iMilkBolt = 0; CALL_ATTRIB_HOOK_INT( iMilkBolt, fires_milk_bolt ); if ( iMilkBolt ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; if ( !CanAttack() ) return; if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return; // Can we attack if ( GetProgress() >= 1.0f ) { // Call Primary Attack and modify the projectile m_bMilkNextAttack = true; PrimaryAttack(); m_flRegenerateDuration = iMilkBolt; m_flLastUsedTimestamp = gpGlobals->curtime; } } } //----------------------------------------------------------------------------- void CTFCrossbow::ModifyProjectile( CBaseEntity* pProj ) { #ifdef GAME_DLL if ( m_bMilkNextAttack ) { CTFProjectile_Arrow* pMainArrow = assert_cast( pProj ); if ( pMainArrow ) { pMainArrow->SetApplyMilkOnHit(); } } #endif m_bMilkNextAttack = false; } //----------------------------------------------------------------------------- void CTFCrossbow::ItemPostFrame( void ) { BaseClass::ItemPostFrame(); m_bMilkNextAttack = false; } //----------------------------------------------------------------------------- float CTFCrossbow::GetProjectileSpeed( void ) { return RemapValClamped( 0.75f, 0.0f, 1.f, 1800, 2600 ); // Temp, if we want to ramp. } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCrossbow::GetProjectileGravity( void ) { return RemapValClamped( 0.75f, 0.0f, 1.f, 0.5, 0.1 ); // Temp, if we want to ramp. } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCrossbow::IsViewModelFlipped( void ) { return !BaseClass::IsViewModelFlipped(); // Invert because arrows are backwards by default. } //----------------------------------------------------------------------------- void CTFCrossbow::WeaponRegenerate( void ) { BaseClass::WeaponRegenerate(); m_flLastUsedTimestamp = 0; } //----------------------------------------------------------------------------- inline float CTFCrossbow::GetProgress( void ) { int iMilkBolt = 0; CALL_ATTRIB_HOOK_INT( iMilkBolt, fires_milk_bolt ); if ( iMilkBolt == 0 ) return 0; float meltedTime = gpGlobals->curtime - m_flLastUsedTimestamp; return meltedTime / m_flRegenerateDuration; }