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