Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1133 lines
31 KiB

//========= 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<CTFFlareGun *>( 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; i<numHealers; i++ )
{
if ( ToTFPlayer( pPlayer->m_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;
}