//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Weapon Base Gun // //============================================================================= #include "cbase.h" #include "tf_weaponbase_gun.h" #include "tf_fx_shared.h" #include "effect_dispatch_data.h" #include "takedamageinfo.h" #include "tf_projectile_nail.h" #include "tf_weapon_jar.h" #include "tf_weapon_flaregun.h" #include "tf_projectile_energy_ring.h" #if !defined( CLIENT_DLL ) // Server specific. #include "tf_gamestats.h" #include "tf_player.h" #include "tf_fx.h" #include "te_effect_dispatch.h" #include "tf_projectile_flare.h" #include "tf_projectile_rocket.h" #include "tf_projectile_arrow.h" #include "tf_projectile_energy_ball.h" #include "tf_weapon_grenade_pipebomb.h" #include "te.h" #else // Client specific. #include "c_tf_player.h" #include "c_te_effect_dispatch.h" #include "c_tf_gamestats.h" #endif //============================================================================= // // TFWeaponBase Gun tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBaseGun, DT_TFWeaponBaseGun ) BEGIN_NETWORK_TABLE( CTFWeaponBaseGun, DT_TFWeaponBaseGun ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFWeaponBaseGun ) END_PREDICTION_DATA() // Server specific. #if !defined( CLIENT_DLL ) BEGIN_DATADESC( CTFWeaponBaseGun ) DEFINE_THINKFUNC( ZoomOutIn ), DEFINE_THINKFUNC( ZoomOut ), DEFINE_THINKFUNC( ZoomIn ), END_DATADESC() #endif //============================================================================= // // TFWeaponBase Gun functions. // //----------------------------------------------------------------------------- // Purpose: Constructor. //----------------------------------------------------------------------------- CTFWeaponBaseGun::CTFWeaponBaseGun() { m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; m_iAmmoToAdd = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::PrimaryAttack( void ) { float flUberChargeAmmoPerShot = UberChargeAmmoPerShot(); if ( flUberChargeAmmoPerShot > 0.0f ) { if ( !HasPrimaryAmmo() ) return; } // Check for ammunition. if ( m_iClip1 <= 0 && m_iClip1 != -1 ) return; // Are we capable of firing again? if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return; // Get the player owning the weapon. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; if ( !CanAttack() ) return; float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay ); CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flFireDelay, hwn_mult_postfiredelay ); // Some weapons change fire delay based on player's health float flReducedHealthBonus = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flReducedHealthBonus, mult_postfiredelay_with_reduced_health ); if ( flReducedHealthBonus != 1.0f ) { flReducedHealthBonus = RemapValClamped( pPlayer->HealthFraction(), 0.2f, 0.9f, flReducedHealthBonus, 1.0f ); flFireDelay *= flReducedHealthBonus; } if ( pPlayer->m_Shared.InCond( TF_COND_BLASTJUMPING ) ) { CALL_ATTRIB_HOOK_FLOAT( flFireDelay, rocketjump_attackrate_bonus ); } else { CALL_ATTRIB_HOOK_FLOAT( flFireDelay, mul_nonrocketjump_attackrate ); } if ( m_iPrimaryAmmoType == TF_AMMO_METAL ) { if ( GetOwner() && GetAmmoPerShot() > GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) ) { WeaponSound( EMPTY ); m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; return; } } CalcIsAttackCritical(); #ifndef CLIENT_DLL if ( pPlayer->m_Shared.IsStealthed() && ShouldRemoveInvisibilityOnPrimaryAttack() ) { pPlayer->RemoveInvisibility(); } // Minigun has custom handling if ( GetWeaponID() != TF_WEAPON_MINIGUN ) { pPlayer->SpeakWeaponFire(); } CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); #else C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); #endif // Minigun has custom handling if ( GetWeaponID() != TF_WEAPON_MINIGUN ) { // Set the weapon mode. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; } SendWeaponAnim( ACT_VM_PRIMARYATTACK ); pPlayer->SetAnimation( PLAYER_ATTACK1 ); CBaseEntity* pProj = FireProjectile( pPlayer ); ModifyProjectile( pProj ); if ( !UsesClipsForAmmo1() ) { // Sniper rifles and such don't actually reload, so we hook reduced reload here float flBaseFireDelay = flFireDelay; CALL_ATTRIB_HOOK_FLOAT( flFireDelay, fast_reload ); float flPlaybackRate = flFireDelay == 0.f ? 0.f : flBaseFireDelay / flFireDelay; if ( pPlayer->GetViewModel( 0 ) ) { pPlayer->GetViewModel( 0 )->SetPlaybackRate( flPlaybackRate ); } if ( pPlayer->GetViewModel( 1 ) ) { pPlayer->GetViewModel( 1 )->SetPlaybackRate( flPlaybackRate ); } } // Set next attack times. m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; // Don't push out secondary attack, because our secondary fire // systems are all separate from primary fire (sniper zooming, demoman pipebomb detonating, etc) //m_flNextSecondaryAttack = gpGlobals->curtime + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; // Set the idle animation times based on the sequence duration, so that we play full fire animations // that last longer than the refire rate may allow. if ( Clip1() > 0 ) { SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); } else { SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); } // Check the reload mode and behave appropriately. if ( m_bReloadsSingly ) { m_iReloadMode.Set( TF_RELOAD_START ); } #ifdef STAGING_ONLY // Remove Cond if I attack if ( pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) ) { pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST ); } #endif m_flLastPrimaryAttackTime = gpGlobals->curtime; if ( ShouldRemoveDisguiseOnPrimaryAttack() ) { pPlayer->RemoveDisguise(); } } bool CTFWeaponBaseGun::ShouldRemoveDisguiseOnPrimaryAttack() const { int iAttr = 0; CALL_ATTRIB_HOOK_INT( iAttr, keep_disguise_on_attack ); if ( iAttr ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::SecondaryAttack( void ) { // semi-auto behaviour if ( m_bInAttack2 ) return; // Get the player owning the weapon. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; pPlayer->DoClassSpecialSkill(); m_bInAttack2 = true; #ifdef STAGING_ONLY // Remove Cond if I attack if ( pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) ) { pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST ); } #endif m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; } CBaseEntity *CTFWeaponBaseGun::FireProjectile( CTFPlayer *pPlayer ) { // New behavior: allow weapons to have attributes to specify what sort of // projectile they fire. int iProjectile = 0; CALL_ATTRIB_HOOK_INT( iProjectile, override_projectile_type ); // Previous default behavior: ask the weapon type for what sort of projectile // to launch. if ( iProjectile == 0 ) { iProjectile = GetWeaponProjectileType(); } CBaseEntity *pProjectile = NULL; // Anyone ever hear of a factory? This is a disgrace. switch( iProjectile ) { case TF_PROJECTILE_BULLET: FireBullet( pPlayer ); //pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_ROCKET: pProjectile = FireRocket( pPlayer, iProjectile ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_SYRINGE: #ifdef STAGING_ONLY case TF_PROJECTILE_TRANQ: #endif // STAGING_ONLY pProjectile = FireNail( pPlayer, iProjectile ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_FLARE: pProjectile = FireFlare( pPlayer ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_PIPEBOMB: case TF_PROJECTILE_PIPEBOMB_REMOTE: case TF_PROJECTILE_PIPEBOMB_PRACTICE: case TF_PROJECTILE_CANNONBALL: pProjectile = FirePipeBomb( pPlayer, iProjectile ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_JAR: case TF_PROJECTILE_JAR_MILK: case TF_PROJECTILE_CLEAVER: case TF_PROJECTILE_THROWABLE: case TF_PROJECTILE_FESTIVE_JAR: case TF_PROJECTILE_BREADMONSTER_JARATE: case TF_PROJECTILE_BREADMONSTER_MADMILK: pProjectile = FireJar( pPlayer ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_ARROW: case TF_PROJECTILE_HEALING_BOLT: case TF_PROJECTILE_BUILDING_REPAIR_BOLT: case TF_PROJECTILE_FESTIVE_ARROW: case TF_PROJECTILE_FESTIVE_HEALING_BOLT: #ifdef STAGING_ONLY case TF_PROJECTILE_SNIPERBULLET: case TF_PROJECTILE_MILK_BOLT: #endif case TF_PROJECTILE_GRAPPLINGHOOK: pProjectile = FireArrow( pPlayer, ProjectileType_t( iProjectile ) ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_FLAME_ROCKET: pProjectile = FireFlameRocket( pPlayer ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY ); break; case TF_PROJECTILE_ENERGY_BALL: pProjectile = FireEnergyBall( pPlayer ); if ( ShouldPlayFireAnim() ) { pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); } break; case TF_PROJECTILE_ENERGY_RING: pProjectile = FireEnergyBall( pPlayer, true ); if ( ShouldPlayFireAnim() ) { pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); } break; case TF_PROJECTILE_NONE: default: // do nothing! DevMsg( "Weapon does not have a projectile type set\n" ); break; } RemoveProjectileAmmo( pPlayer ); m_flLastFireTime = gpGlobals->curtime; DoFireEffects(); UpdatePunchAngles( pPlayer ); #ifdef GAME_DLL // Some game modes may allow any class to have stealth. Continuous-fire weapons like the // minigun might be firing when stealth is applied, so we try removing it from here, too. if ( pPlayer->m_Shared.IsStealthed() && ShouldRemoveInvisibilityOnPrimaryAttack() ) { pPlayer->RemoveInvisibility(); } #endif // GAME_DLL return pProjectile; } //----------------------------------------------------------------------------- void CTFWeaponBaseGun::RemoveProjectileAmmo( CTFPlayer *pPlayer ) { if ( m_iClip1 != -1 ) { m_iClip1 -= GetAmmoPerShot(); } else { if ( m_iWeaponMode == TF_WEAPON_PRIMARY_MODE ) { pPlayer->RemoveAmmo( GetAmmoPerShot(), m_iPrimaryAmmoType ); #ifndef CLIENT_DLL // delayed ammo adding for the onhit attribute if ( m_iAmmoToAdd > 0 ) { pPlayer->GiveAmmo( m_iAmmoToAdd, m_iPrimaryAmmoType ); m_iAmmoToAdd = 0; } #endif } else { pPlayer->RemoveAmmo( GetAmmoPerShot(), m_iSecondaryAmmoType ); #ifndef CLIENT_DLL // delayed ammo adding for the onhit attribute if ( m_iAmmoToAdd > 0 ) { pPlayer->GiveAmmo( m_iAmmoToAdd, m_iSecondaryAmmoType ); m_iAmmoToAdd = 0; } #endif } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBaseGun::HasPrimaryAmmo( void ) { if ( m_iPrimaryAmmoType == TF_AMMO_METAL ) { if ( GetOwner() && ( GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) < GetAmmoPerShot() ) ) return false; } return BaseClass::HasPrimaryAmmo(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBaseGun::CanDeploy( void ) { if ( m_iPrimaryAmmoType == TF_AMMO_METAL ) { if ( GetOwner() && ( GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) < GetAmmoPerShot() ) ) return false; } return BaseClass::CanDeploy(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBaseGun::CanBeSelected( void ) { if ( m_iPrimaryAmmoType == TF_AMMO_METAL ) { if ( GetOwner() && ( GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) < GetAmmoPerShot() ) ) return false; } return BaseClass::CanBeSelected(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFWeaponBaseGun::GetAmmoPerShot( void ) { if ( IsEnergyWeapon() ) return 0; else { int iAmmoPerShot = 0; CALL_ATTRIB_HOOK_INT( iAmmoPerShot, mod_ammo_per_shot ); if ( iAmmoPerShot > 0 ) return iAmmoPerShot; return m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_iAmmoPerShot; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::UpdatePunchAngles( CTFPlayer *pPlayer ) { // Update the player's punch angle. QAngle angle = pPlayer->GetPunchAngle(); float flPunchAngle = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flPunchAngle; if ( flPunchAngle > 0 ) { angle.x -= SharedRandomInt( "ShotgunPunchAngle", ( flPunchAngle - 1 ), ( flPunchAngle + 1 ) ); pPlayer->SetPunchAngle( angle ); } } //----------------------------------------------------------------------------- // Purpose: Fire a bullet! //----------------------------------------------------------------------------- void CTFWeaponBaseGun::FireBullet( CTFPlayer *pPlayer ) { PlayWeaponShootSound(); FX_FireBullets( this, pPlayer->entindex(), pPlayer->Weapon_ShootPosition(), pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), GetWeaponID(), m_iWeaponMode, CBaseEntity::GetPredictionRandomSeed( UseServerRandomSeed() ) & 255, GetWeaponSpread(), GetProjectileDamage(), IsCurrentAttackACrit() ); } //----------------------------------------------------------------------------- // Purpose: Fire a rocket //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireRocket( CTFPlayer *pPlayer, int iRocketType ) { PlayWeaponShootSound(); // Server only - create the rocket. #ifdef GAME_DLL Vector vecSrc; QAngle angForward; Vector vecOffset( 23.5f, 12.0f, -3.0f ); if ( pPlayer->GetFlags() & FL_DUCKING ) { vecOffset.z = 8.0f; } GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false ); trace_t trace; Vector vecEye = pPlayer->EyePosition(); CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE ); UTIL_TraceLine( vecEye, vecSrc, MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); CTFProjectile_Rocket *pProjectile = CTFProjectile_Rocket::Create( this, trace.endpos, angForward, pPlayer, pPlayer ); if ( pProjectile ) { pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetDamage( GetProjectileDamage() ); } return pProjectile; #endif return NULL; } //----------------------------------------------------------------------------- // Purpose: Fire an energy ball //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireEnergyBall( CTFPlayer *pPlayer, bool bRing ) { PlayWeaponShootSound(); Vector vecSrc; QAngle angForward; Vector vecOffset( 23.5f, -8.0f, -3.0f ); if ( pPlayer->GetFlags() & FL_DUCKING ) { vecOffset.z = 8.0f; } GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false ); trace_t trace; Vector vecEye = pPlayer->EyePosition(); CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE ); UTIL_TraceLine( vecEye, vecSrc, MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); if ( bRing ) { CTFProjectile_EnergyRing* pProjectile = CTFProjectile_EnergyRing::Create( this, trace.endpos, angForward, GetProjectileSpeed(), GetProjectileGravity(), pPlayer, pPlayer, GetParticleColor(1), GetParticleColor(2), IsCurrentAttackACrit() ); if ( pProjectile ) { pProjectile->SetWeaponID( GetWeaponID() ); pProjectile->SetCritical( IsCurrentAttackACrit() ); #ifdef GAME_DLL pProjectile->SetDamage( GetProjectileDamage() ); #endif } return pProjectile; } else { #ifdef GAME_DLL CTFProjectile_EnergyBall* pProjectile = CTFProjectile_EnergyBall::Create( trace.endpos, angForward, GetProjectileSpeed(), GetProjectileGravity(), pPlayer, pPlayer ); if ( pProjectile ) { pProjectile->SetLauncher( this ); pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetDamage( GetProjectileDamage() ); } return pProjectile; #endif } return NULL; } //----------------------------------------------------------------------------- // Purpose: Fire a projectile nail //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireNail( CTFPlayer *pPlayer, int iSpecificNail ) { PlayWeaponShootSound(); Vector vecSrc; QAngle angForward; // Add some spread float flSpread = 1.5; flSpread += GetProjectileSpread(); CTFBaseProjectile *pProjectile = NULL; switch( iSpecificNail ) { case TF_PROJECTILE_SYRINGE: { Vector vecOffset( 16, 6, -8 ); GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward ); angForward.x += RandomFloat( -flSpread, flSpread ); angForward.y += RandomFloat( -flSpread, flSpread ); pProjectile = CTFProjectile_Syringe::Create( vecSrc, angForward, this, pPlayer, pPlayer, IsCurrentAttackACrit() ); } break; #ifdef STAGING_ONLY case TF_PROJECTILE_TRANQ: { Vector vecOffset( 16, 6, 0 ); GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward ); pProjectile = CTFProjectile_Tranq::Create( vecSrc, angForward, this, pPlayer, pPlayer, IsCurrentAttackACrit() ); } break; #endif // STAGING_ONLY default: Assert(0); } if ( pProjectile ) { pProjectile->SetWeaponID( GetWeaponID() ); pProjectile->SetCritical( IsCurrentAttackACrit() ); #ifdef GAME_DLL pProjectile->SetLauncher( this ); pProjectile->SetDamage( GetProjectileDamage() ); #endif } return pProjectile; } //----------------------------------------------------------------------------- // Purpose: Fire a pipe bomb //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FirePipeBomb( CTFPlayer *pPlayer, int iPipeBombType ) { PlayWeaponShootSound(); #ifdef GAME_DLL QAngle angEyes = pPlayer->EyeAngles(); float flSpreadAngle = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flSpreadAngle, projectile_spread_angle ); if ( flSpreadAngle > 0.0f ) { QAngle angSpread = RandomAngle( -flSpreadAngle, flSpreadAngle ); angSpread.z = 0.0f; angEyes += angSpread; DevMsg( "Fire bomb at %f %f %f\n", XYZ(angEyes) ); } Vector vecForward, vecRight, vecUp; AngleVectors( angEyes, &vecForward, &vecRight, &vecUp ); // Create grenades here!! float fRight = 8.f; if ( IsViewModelFlipped() ) { fRight *= -1; } Vector vecSrc = pPlayer->Weapon_ShootPosition(); vecSrc += vecForward * 16.0f + vecRight * fRight + vecUp * -6.0f; trace_t trace; Vector vecEye = pPlayer->EyePosition(); CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE ); UTIL_TraceHull( vecEye, vecSrc, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); // If we started in solid, don't let them fire at all if ( trace.startsolid ) return NULL; float flLaunchSpeed = GetProjectileSpeed(); CALL_ATTRIB_HOOK_FLOAT( flLaunchSpeed, mult_projectile_range ); Vector vecVelocity = ( vecForward * flLaunchSpeed ) + ( vecUp * 200.0f ) + ( random->RandomFloat( -10.0f, 10.0f ) * vecRight ) + ( random->RandomFloat( -10.0f, 10.0f ) * vecUp ); float flMultDmg = 1.f; CALL_ATTRIB_HOOK_FLOAT( flMultDmg, mult_dmg ); // no spin for loch-n-load Vector angImpulse = AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ); int iNoSpin = 0; CALL_ATTRIB_HOOK_INT( iNoSpin, grenade_no_spin ); if ( iNoSpin ) { angImpulse.Zero(); } CTFGrenadePipebombProjectile *pProjectile = CTFGrenadePipebombProjectile::Create( trace.endpos, angEyes, vecVelocity, angImpulse, pPlayer, GetTFWpnData(), iPipeBombType, flMultDmg ); if ( pProjectile ) { pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetLauncher( this ); //float flFizzle = 0; //CALL_ATTRIB_HOOK_FLOAT( flFizzle, stickybomb_fizzle_time ); //if ( flFizzle ) //{ // pProjectile->SetDetonateTimerLength( flFizzle ); //} CAttribute_String attrCustomModelName; GetCustomProjectileModel( &attrCustomModelName ); if ( attrCustomModelName.has_value() ) { pProjectile->SetModel( attrCustomModelName.value().c_str() ); } } return pProjectile; #endif return NULL; } //----------------------------------------------------------------------------- // Purpose: Fire a flare //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireFlare( CTFPlayer *pPlayer ) { PlayWeaponShootSound(); // Server only - create the flare. #ifdef GAME_DLL Vector vecSrc; QAngle angForward; Vector vecOffset( 23.5f, 12.0f, -3.0f ); if ( pPlayer->GetFlags() & FL_DUCKING ) { vecOffset.z = 8.0f; } GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false ); CTFProjectile_Flare *pProjectile = CTFProjectile_Flare::Create( this, vecSrc, angForward, pPlayer, pPlayer ); if ( pProjectile ) { pProjectile->SetLauncher( this ); pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetDamage( GetProjectileDamage() ); CTFFlareGun *pFlareGun = dynamic_cast( this ); if ( pFlareGun && pFlareGun->GetFlareGunType() == FLAREGUN_DETONATE ) { pFlareGun->AddFlare( pProjectile ); } } return pProjectile; #endif return NULL; } //----------------------------------------------------------------------------- // Purpose: Fire an arrow //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireArrow( CTFPlayer *pPlayer, ProjectileType_t projectileType ) { PlayWeaponShootSound(); // Server only - create the rocket. #ifdef GAME_DLL Vector vecSrc; QAngle angForward; Vector vecOffset( 23.5f, -8.0f, -3.0f ); #ifdef STAGING_ONLY if ( projectileType == TF_PROJECTILE_SNIPERBULLET ) { // Center the bullet while zoomed, otherwise flip the arrow cause arrows are dumb if ( pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) { vecOffset = Vector( 32, 0, -2.0f ); } else { vecOffset.y = 8.0f; } } #endif // STAGING_ONLY GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false ); CTFProjectile_Arrow *pProjectile = CTFProjectile_Arrow::Create( vecSrc, angForward, GetProjectileSpeed(), GetProjectileGravity(), projectileType, pPlayer, pPlayer ); if ( pProjectile ) { pProjectile->SetLauncher( this ); pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetDamage( GetProjectileDamage() ); int iPenetrate = 0; CALL_ATTRIB_HOOK_INT( iPenetrate, projectile_penetration ); if ( iPenetrate == 1 ) { pProjectile->SetPenetrate( true ); } pProjectile->SetCollisionGroup( TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ); } return pProjectile; #endif return NULL; } //----------------------------------------------------------------------------- // Purpose: Toss a Jar...of something... //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireJar( CTFPlayer *pPlayer ) { return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireFlameRocket( CTFPlayer *pPlayer ) { return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::PlayWeaponShootSound( void ) { if ( IsCurrentAttackACrit() ) { WeaponSound( BURST ); } else { WeaponSound( SINGLE ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFWeaponBaseGun::GetWeaponSpread( void ) { float fSpread = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flSpread; CALL_ATTRIB_HOOK_FLOAT( fSpread, mult_spread_scale ); CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( pPlayer ) { if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) { if ( GetWeaponID() == TF_WEAPON_MINIGUN ) { fSpread *= 0.4f; } else { fSpread *= 0.1f; } } // Some weapons change fire delay based on player's health float flReducedHealthBonus = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flReducedHealthBonus, panic_attack_negative ); if ( flReducedHealthBonus != 1.0f ) { flReducedHealthBonus = RemapValClamped( pPlayer->HealthFraction(), 0.2f, 0.9f, flReducedHealthBonus, 1.0f ); fSpread *= flReducedHealthBonus; } } return fSpread; } //----------------------------------------------------------------------------- void CTFWeaponBaseGun::GetCustomProjectileModel( CAttribute_String *attrCustomProjModel ) { // Must still add these to a precache somewhere // ie CTFGrenadePipebombProjectile::Precache() static CSchemaAttributeDefHandle pAttrDef_ProjectileEntityName( "custom projectile model" ); CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pAttrDef_ProjectileEntityName && pItem ) { pItem->FindAttribute( pAttrDef_ProjectileEntityName, attrCustomProjModel ); } } //----------------------------------------------------------------------------- // Purpose: Accessor for damage, so sniper etc can modify damage //----------------------------------------------------------------------------- float CTFWeaponBaseGun::GetProjectileDamage( void ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); float flDamage = (float)m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage; CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg ); // Some weapons mod dmg when not disguised bool bDisguised = pPlayer && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ); if ( bDisguised ) { CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg_disguised ); } if ( pPlayer && ( pPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) || pPlayer->IsPlayerClass( TF_CLASS_PYRO ) ) ) { float flRageDamage = 1.f; CALL_ATTRIB_HOOK_FLOAT( flRageDamage, rage_damage ); if ( flRageDamage > 1.f ) { float flRageRatio = pPlayer->m_Shared.GetRageMeter() / 100.f; flRageDamage = (flRageDamage - 1.f) * flRageRatio; flDamage *= 1.f + flRageDamage; } } #ifdef GAME_DLL float flMedicHealDamageBonus = 1.f; CALL_ATTRIB_HOOK_FLOAT( flMedicHealDamageBonus, medic_healed_damage_bonus ); if ( flMedicHealDamageBonus > 1.f ) { if ( pPlayer ) { int numHealers = pPlayer->m_Shared.GetNumHealers(); bool bHealedByMedic = false; for ( int i=0; im_Shared.GetHealerByIndex( i ) ) != NULL ) { bHealedByMedic = true; } } if ( bHealedByMedic ) { flDamage *= flMedicHealDamageBonus; } } } if ( GetWeaponProjectileType() == TF_PROJECTILE_BULLET ) { float flScaleDamage = 1.f; CALL_ATTRIB_HOOK_FLOAT( flScaleDamage, accuracy_scales_damage ); if ( flScaleDamage > 1.f ) { // Bullets fired vs hit ratio over last x.x second(s) if ( gpGlobals->curtime < GetLastHitTime() + 0.7f ) { float flRatio = (float)m_iHitsInTime / (float)m_iFiredInTime; float flDmgMod = RemapValClamped( flRatio, 0.f, 1.f, 1.f, flScaleDamage ); // DevMsg( "A: %f - D: %f\n", flRatio, flDmgMod ); // DevMsg( "H: %d - F: %d\n", m_iHitsInTime, m_iFiredInTime ); flDamage *= flDmgMod; } else { m_iHitsInTime = 1; m_iFiredInTime = 1; } } } #endif return flDamage; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBaseGun::Holster( CBaseCombatWeapon *pSwitchingTo ) { // Server specific. #if !defined( CLIENT_DLL ) // Make sure to zoom out before we holster the weapon. ZoomOut(); SetContextThink( NULL, 0, ZOOM_CONTEXT ); #endif return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: // NOTE: Should this be put into fire gun //----------------------------------------------------------------------------- void CTFWeaponBaseGun::DoFireEffects() { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; if ( ShouldDoMuzzleFlash() ) { pPlayer->DoMuzzleFlash(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ToggleZoom( void ) { // Toggle the zoom. CBasePlayer *pPlayer = GetPlayerOwner(); if ( pPlayer ) { if( pPlayer->GetFOV() >= 75 ) { ZoomIn(); } else { ZoomOut(); } } // Get the zoom animation time. m_flNextSecondaryAttack = gpGlobals->curtime + 1.2; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ZoomIn( void ) { // The the owning player. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; // Set the weapon zoom. // TODO: The weapon fov should be gotten from the script file. float fBaseZoom = TF_WEAPON_ZOOM_FOV; // Disabled this for now, because we have no attributes using it //CALL_ATTRIB_HOOK_FLOAT( fBaseZoom, mult_zoom_fov ); pPlayer->SetFOV( pPlayer, fBaseZoom, 0.1f ); pPlayer->m_Shared.AddCond( TF_COND_ZOOMED ); #if defined( CLIENT_DLL ) // Doing this allows us to show/hide the player viewmodel/localmodel pPlayer->UpdateVisibility(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ZoomOut( void ) { // The the owning player. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; if ( pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) { // Set the FOV to 0 set the default FOV. pPlayer->SetFOV( pPlayer, 0, 0.1f ); pPlayer->m_Shared.RemoveCond( TF_COND_ZOOMED ); } #if defined( CLIENT_DLL ) // Doing this allows us to show/hide the player viewmodel/localmodel pPlayer->UpdateVisibility(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ZoomOutIn( void ) { //Zoom out, set think to zoom back in. ZoomOut(); SetContextThink( &CTFWeaponBaseGun::ZoomIn, gpGlobals->curtime + ZOOM_REZOOM_TIME, ZOOM_CONTEXT ); } //----------------------------------------------------------------------------- bool CTFWeaponBaseGun::HasLastShotCritical( void ) { if ( m_iClip1 == 1 ) { int iAttr = 0; CALL_ATTRIB_HOOK_INT( iAttr, last_shot_crits ); if ( iAttr ) { return true; } } return false; }