//========= Copyright Valve Corporation, All rights reserved. ============// // // //============================================================================= #include "cbase.h" #include "tf_weapon_compound_bow.h" #include "tf_fx_shared.h" #include "tf_gamerules.h" #include "in_buttons.h" // Client specific. #ifdef CLIENT_DLL #include "c_tf_player.h" #include "c_tf_gamestats.h" #include "prediction.h" // Server specific. #else #include "tf_player.h" #include "tf_gamestats.h" #include "tf_projectile_arrow.h" #endif //============================================================================= // // Weapon tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFCompoundBow, DT_WeaponCompoundBow ) BEGIN_NETWORK_TABLE( CTFCompoundBow, DT_WeaponCompoundBow ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bArrowAlight ) ), RecvPropBool( RECVINFO( m_bNoFire ) ), #else SendPropBool( SENDINFO( m_bArrowAlight ) ), SendPropBool( SENDINFO( m_bNoFire ) ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFCompoundBow ) #ifdef CLIENT_DLL DEFINE_PRED_FIELD( m_flChargeBeginTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bNoFire, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), #endif END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_compound_bow, CTFCompoundBow ); PRECACHE_WEAPON_REGISTER( tf_weapon_compound_bow ); // Server specific. #ifndef CLIENT_DLL BEGIN_DATADESC( CTFCompoundBow ) END_DATADESC() #endif #define TF_ARROW_MAX_CHARGE_TIME 5.0f //============================================================================= // // Weapon functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFCompoundBow::CTFCompoundBow() { m_flLastDenySoundTime = 0.0f; m_bNoFire = false; m_bReloadsSingly = false; } void CTFCompoundBow::Precache( void ) { PrecacheScriptSound( "Weapon_CompoundBow.SinglePull" ); PrecacheScriptSound( "ArrowLight" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::WeaponReset( void ) { BaseClass::WeaponReset(); // m_flChargeBeginTime = 0; m_bArrowAlight = false; m_bNoAutoRelease = true; m_bNoFire = false; } #ifdef GAME_DLL #ifdef STAGING_ONLY void CTFCompoundBow::CreateExtraArrow( CTFProjectile_Arrow* pMainArrow, const QAngle& qSpreadAngles, float flSpeed ) { CTFProjectile_Arrow* pExtraArrow = CTFProjectile_Arrow::Create( pMainArrow->GetAbsOrigin(), qSpreadAngles, flSpeed, GetProjectileGravity(), (ProjectileType_t)GetWeaponProjectileType(), pMainArrow->GetOwnerEntity(), pMainArrow->GetOwnerEntity() ); if ( pExtraArrow ) { pExtraArrow->SetLauncher( this ); pExtraArrow->SetCritical( IsCurrentAttackACrit() ); pExtraArrow->SetDamage( 0.5f * GetProjectileDamage() ); if ( pMainArrow->CanPenetrate() ) { pExtraArrow->SetPenetrate( true ); } pExtraArrow->SetCollisionGroup( pMainArrow->GetCollisionGroup() ); } } ConVar sv_arrow_spread_angle( "sv_arrow_spread_angle", "5.f" ); ConVar sv_arrow_max_random_spread_angle( "sv_arrow_random_spread_angle", "5.f" ); float CTFCompoundBow::GetRandomSpreadOffset( int iLevel ) { float flMaxRandomSpread = sv_arrow_max_random_spread_angle.GetFloat(); float flRandom = RemapValClamped( gpGlobals->curtime - m_flChargeBeginTime, 0.f, GetChargeMaxTime(), RandomFloat( -flMaxRandomSpread, flMaxRandomSpread ), 0.f ); return sv_arrow_spread_angle.GetFloat() * iLevel + flRandom; } #endif // STAGING_ONLY #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::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 ); m_bWantsToShoot = false; #ifdef GAME_DLL CTFProjectile_Arrow *pMainArrow = assert_cast( FireProjectile( pPlayer ) ); if ( pMainArrow ) { pMainArrow->SetArrowAlight( m_bArrowAlight ); #ifdef STAGING_ONLY if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) { Vector vecMainVelocity = pMainArrow->GetAbsVelocity(); float flMainSpeed = vecMainVelocity.Length(); int iArrowMastery = 0; CALL_ATTRIB_HOOK_INT( iArrowMastery, arrow_mastery ); for ( int i=0; iGetAbsAngles() + QAngle( 0, GetRandomSpreadOffset( i + 1 ), 0 ); CreateExtraArrow( pMainArrow, qOffset1, flMainSpeed ); QAngle qOffset2 = pMainArrow->GetAbsAngles() + QAngle( 0, -GetRandomSpreadOffset( i + 1 ), 0 ); CreateExtraArrow( pMainArrow, qOffset2, flMainSpeed ); } } #endif } #else FireProjectile( pPlayer ); #endif #if !defined( CLIENT_DLL ) pPlayer->SpeakWeaponFire(); CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); #endif #ifdef CLIENT_DLL C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); #endif // Set next attack times. float flBaseFireDelay = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; float flFireDelay = ApplyFireDelay( flBaseFireDelay ); ApplyRefireSpeedModifications( flFireDelay ); float flRateMultiplyer = flBaseFireDelay / flFireDelay; // Speed up the reload animation built in to firing if ( pPlayer->GetViewModel(0) ) { pPlayer->GetViewModel(0)->SetPlaybackRate( flRateMultiplyer ); } if ( pPlayer->GetViewModel(1) ) { pPlayer->GetViewModel(1)->SetPlaybackRate( flRateMultiplyer ); } m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; m_flLastDenySoundTime = gpGlobals->curtime; float flIdleDelay = 0.5f * flRateMultiplyer; SetWeaponIdleTime( m_flNextPrimaryAttack + flIdleDelay ); pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); pPlayer->TeamFortress_SetSpeed(); m_flChargeBeginTime = 0; m_bArrowAlight = false; // The bow doesn't actually reload, it instead uses the AE_WPN_INCREMENTAMMO anim event in the fire to reload the clip. // We need to reset this bool each time we fire so that anim event works. m_bReloadedThroughAnimEvent = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::PrimaryAttack( void ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; // Check for ammunition. if ( m_iClip1 <= 0 && m_iClip1 != -1 ) return; // Are we capable of firing again? if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return; if ( m_bNoFire ) return; if ( !CanAttack() ) { m_flChargeBeginTime = 0; return; } if ( m_flChargeBeginTime <= 0 ) { // Set the weapon mode. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; // save that we had the attack button down m_flChargeBeginTime = gpGlobals->curtime; SendWeaponAnim( ACT_VM_PULLBACK ); float flRateMultiplyer = ApplyFireDelay( 1.0f ); ApplyRefireSpeedModifications( flRateMultiplyer ); if ( flRateMultiplyer > 0.0f ) { flRateMultiplyer = 1.0f / flRateMultiplyer; } // Speed up the reload animation built in to firing if ( pPlayer->GetViewModel(0) ) { pPlayer->GetViewModel(0)->SetPlaybackRate( flRateMultiplyer ); } if ( pPlayer->GetViewModel(1) ) { pPlayer->GetViewModel(1)->SetPlaybackRate( flRateMultiplyer ); } bool bPlaySound = true; #ifdef CLIENT_DLL bPlaySound = prediction->IsFirstTimePredicted(); #endif if ( bPlaySound ) { // Increase the pitch of the pull sound when the fire rate is higher CSoundParameters params; if ( CBaseEntity::GetParametersForSound( "Weapon_CompoundBow.SinglePull", params, NULL ) ) { CPASAttenuationFilter filter( pPlayer->GetAbsOrigin(), params.soundlevel ); #ifdef GAME_DLL filter.RemoveRecipient( pPlayer ); #endif EmitSound_t ep( params ); ep.m_nPitch *= flRateMultiplyer; pPlayer->EmitSound( filter, pPlayer->entindex(), ep ); } } // Slow down movement speed while the bow is pulled back. pPlayer->m_Shared.AddCond( TF_COND_AIMING ); pPlayer->TeamFortress_SetSpeed(); } else { float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime; if ( flTotalChargeTime >= GetChargeMaxTime() ) { flTotalChargeTime = GetChargeMaxTime(); // LaunchGrenade(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCompoundBow::GetChargeMaxTime( void ) { // It takes less time to charge if the fire rate is higher float flChargeMaxTime = ApplyFireDelay( 1.0f ); ApplyRefireSpeedModifications( flChargeMaxTime ); return flChargeMaxTime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCompoundBow::GetCurrentCharge( void ) { if ( m_flChargeBeginTime == 0 ) return 0; else return MIN( gpGlobals->curtime - m_flChargeBeginTime, 1.f ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCompoundBow::GetProjectileDamage( void ) { float flDamage = BaseClass::GetProjectileDamage(); float flBaseDamage = 50.f; float flScale = MIN( GetCurrentCharge() / GetChargeMaxTime(), 1.f); float flScaleDamage = flDamage * flScale; return (flBaseDamage + flScaleDamage); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCompoundBow::GetProjectileSpeed( void ) { return RemapValClamped( GetCurrentCharge(), 0.0f, 1.f, 1800, 2600 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFCompoundBow::GetProjectileGravity( void ) { return RemapValClamped( GetCurrentCharge(), 0.0f, 1.f, 0.5, 0.1 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::AddPipeBomb( CTFGrenadePipebombProjectile *pBomb ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::SecondaryAttack( void ) { LowerBow(); } //----------------------------------------------------------------------------- // Purpose: Un-nocks a ready arrow. //----------------------------------------------------------------------------- void CTFCompoundBow::LowerBow( void ) { if ( GetCurrentCharge() == 0.f ) return; // No arrow nocked. m_flChargeBeginTime = 0; CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( pPlayer ) { pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); pPlayer->TeamFortress_SetSpeed(); } m_flNextPrimaryAttack = gpGlobals->curtime + 1.f; m_bNoFire = true; m_bWantsToShoot = false; SendWeaponAnim( ACT_ITEM2_VM_DRYFIRE ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCompoundBow::DetonateRemotePipebombs( bool bFizzle ) { return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCompoundBow::OwnerCanJump( void ) { if ( GetCurrentCharge() > 0.f ) return false; else return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCompoundBow::Holster( CBaseCombatWeapon *pSwitchingTo ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( pPlayer ) { pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); pPlayer->TeamFortress_SetSpeed(); } m_bNoFire = false; SetArrowAlight( false ); return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: Play animation appropriate to ball status. //----------------------------------------------------------------------------- bool CTFCompoundBow::SendWeaponAnim( int iActivity ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return BaseClass::SendWeaponAnim( iActivity ); if ( iActivity == ACT_VM_PULLBACK ) { iActivity = ACT_ITEM2_VM_CHARGE; } float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime; if ( GetCurrentCharge() > 0 ) { switch ( iActivity ) { case ACT_VM_IDLE: if ( flTotalChargeTime >= TF_ARROW_MAX_CHARGE_TIME ) { int iAct = GetActivity(); if ( iAct == ACT_ITEM2_VM_IDLE_3 || iAct == ACT_ITEM2_VM_CHARGE_IDLE_3 ) { iActivity = ACT_ITEM2_VM_IDLE_3; } else { iActivity = ACT_ITEM2_VM_CHARGE_IDLE_3; } } else { iActivity = ACT_ITEM2_VM_IDLE_2; } break; default: break; } } return BaseClass::SendWeaponAnim( iActivity ); } //----------------------------------------------------------------------------- // Purpose: Play animation appropriate to ball status. //----------------------------------------------------------------------------- void CTFCompoundBow::ItemPostFrame( void ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( !pOwner ) return; if ( !CanAttack() ) { LowerBow(); } // If we just fired, and we're past the point at which we tried to reload ourselves, // and we don't have any ammo in the clip, switch away to another weapon to stop us // from playing the "draw another arrow from the quiver" animation. if ( m_bReloadedThroughAnimEvent && m_iClip1 <= 0 && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) { g_pGameRules->SwitchToNextBestWeapon( pOwner, this ); return; } BaseClass::ItemPostFrame(); if ( !(pOwner->m_nButtons & IN_ATTACK) && !(pOwner->m_nButtons & IN_ATTACK2) ) { // Both buttons released. The player can draw the bow again. m_bNoFire = false; if ( GetActivity() == ACT_ITEM2_VM_PRIMARYATTACK && IsViewModelSequenceFinished() ) { SendWeaponAnim( ACT_VM_IDLE ); } } if ( GetCurrentCharge() == 1.f && IsViewModelSequenceFinished() ) { SendWeaponAnim( ACT_VM_IDLE ); } if ( m_bNoFire ) { WeaponIdle(); } } //----------------------------------------------------------------------------- // Purpose: Held the arrow drawn too long. Give up & play a fail animation. //----------------------------------------------------------------------------- void CTFCompoundBow::ForceLaunchGrenade( void ) { // LowerBow(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::GetProjectileFireSetup( CTFPlayer *pPlayer, Vector vecOffset, Vector *vecSrc, QAngle *angForward, bool bHitTeammates, float flEndDist ) { BaseClass::GetProjectileFireSetup( pPlayer, vecOffset, vecSrc, angForward, bHitTeammates, flEndDist ); float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime; if ( flTotalChargeTime >= TF_ARROW_MAX_CHARGE_TIME ) { // We want to fire a really inaccurate shot. float frand = (float) rand() / VALVE_RAND_MAX; angForward->x += -6 + frand*12.f; frand = (float) rand() / VALVE_RAND_MAX; angForward->y += -6 + frand*12.f; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::ApplyRefireSpeedModifications( float &flBaseRef ) { CALL_ATTRIB_HOOK_FLOAT( flBaseRef, fast_reload ); // Prototype hack CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer ) { int iMaster = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iMaster, ability_master_sniper ); if ( iMaster ) { flBaseRef *= RemapValClamped( iMaster, 1, 2, 0.6f, 0.3f ); } else if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) { flBaseRef *= 0.4f; } } } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::StartBurningEffect( void ) { // clear any old effect before adding a new one if ( m_pBurningArrowEffect ) { StopBurningEffect(); } const char *pszEffect; m_hParticleEffectOwner = GetWeaponForEffect(); if ( m_hParticleEffectOwner ) { if ( m_hParticleEffectOwner != this ) { // We're on the viewmodel pszEffect = "v_flaming_arrow"; } else { pszEffect = "flaming_arrow"; } m_pBurningArrowEffect = m_hParticleEffectOwner->ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, "muzzle" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::StopBurningEffect( void ) { if ( m_pBurningArrowEffect ) { if ( m_hParticleEffectOwner && m_hParticleEffectOwner->ParticleProp() ) { m_hParticleEffectOwner->ParticleProp()->StopEmission( m_pBurningArrowEffect ); } m_pBurningArrowEffect = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::UpdateOnRemove( void ) { StopBurningEffect(); BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); // Handle particle effect creation / destruction if ( m_bArrowAlight && !m_pBurningArrowEffect ) { StartBurningEffect(); EmitSound( "ArrowLight" ); } else if ( !m_bArrowAlight && m_pBurningArrowEffect ) { StopBurningEffect(); } } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCompoundBow::Reload( void ) { if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return false; return BaseClass::Reload(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFCompoundBow::CalcIsAttackCriticalHelper() { CTFPlayer *pPlayer = GetTFPlayerOwner(); // Crit boosted players fire all crits if ( pPlayer && pPlayer->m_Shared.IsCritBoosted() ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFCompoundBow::SetArrowAlight( bool bAlight ) { // Don't light arrows if we're still firing one. if (GetActivity() != ACT_ITEM2_VM_PRIMARYATTACK ) { m_bArrowAlight = bAlight; } }