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
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;
|
|
}
|