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.
 
 
 
 
 
 

581 lines
18 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "tf_weapon_shotgun.h"
#include "decals.h"
#include "tf_fx_shared.h"
#include "takedamageinfo.h"
#include "tf_gamerules.h"
// Client specific.
#if defined( CLIENT_DLL )
#include "c_tf_player.h"
// Server specific.
#else
#include "tf_player.h"
#include "ilagcompensationmanager.h"
#include "collisionutils.h"
#include "in_buttons.h"
#endif
//=============================================================================
//
// Weapon Shotgun tables.
//
CREATE_SIMPLE_WEAPON_TABLE( TFShotgun, tf_weapon_shotgun_primary )
CREATE_SIMPLE_WEAPON_TABLE( TFShotgun_Soldier, tf_weapon_shotgun_soldier )
CREATE_SIMPLE_WEAPON_TABLE( TFShotgun_HWG, tf_weapon_shotgun_hwg )
CREATE_SIMPLE_WEAPON_TABLE( TFShotgun_Pyro, tf_weapon_shotgun_pyro )
CREATE_SIMPLE_WEAPON_TABLE( TFScatterGun, tf_weapon_scattergun )
CREATE_SIMPLE_WEAPON_TABLE( TFShotgun_Revenge, tf_weapon_sentry_revenge )
CREATE_SIMPLE_WEAPON_TABLE( TFSodaPopper, tf_weapon_soda_popper )
CREATE_SIMPLE_WEAPON_TABLE( TFPEPBrawlerBlaster, tf_weapon_pep_brawler_blaster )
CREATE_SIMPLE_WEAPON_TABLE( TFShotgunBuildingRescue, tf_weapon_shotgun_building_rescue )
#define SCATTERGUN_KNOCKBACK_MIN_DMG 30.0f
#define SCATTERGUN_KNOCKBACK_MIN_RANGE_SQ 160000.0f //400x400
//=============================================================================
//
// Weapon Shotgun functions.
//
bool CanScatterGunKnockBack( CTFWeaponBase *pWeapon, float flDamage, float flDistanceSq )
{
int nBulletKnockBack = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, nBulletKnockBack, set_scattergun_has_knockback );
if ( nBulletKnockBack != 0 )
{
if (flDamage > SCATTERGUN_KNOCKBACK_MIN_DMG && flDistanceSq < SCATTERGUN_KNOCKBACK_MIN_RANGE_SQ )
return true;
float flKnockbackMult = 1.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flKnockbackMult, scattergun_knockback_mult );
if ( flKnockbackMult > 1.0f )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFShotgun::CTFShotgun()
{
m_bReloadsSingly = true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFShotgun::PrimaryAttack()
{
if ( !CanAttack() )
return;
// Set the weapon mode.
m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
BaseClass::PrimaryAttack();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFShotgun::UpdatePunchAngles( CTFPlayer *pPlayer )
{
// Update the player's punch angle.
QAngle angle = pPlayer->GetPunchAngle();
float flPunchAngle = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flPunchAngle;
angle.x -= SharedRandomInt( "ShotgunPunchAngle", ( flPunchAngle - 1 ), ( flPunchAngle + 1 ) );
pPlayer->SetPunchAngle( angle );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFShotgun::PlayWeaponShootSound( void )
{
BaseClass::PlayWeaponShootSound();
if ( TFGameRules()->GameModeUsesUpgrades() )
{
PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" );
}
}
//-----------------------------------------------------------------------------
// CTFShotgun_Revenge
//-----------------------------------------------------------------------------
CTFShotgun_Revenge::CTFShotgun_Revenge()
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFShotgun_Revenge::Precache()
{
int iModelIndex = PrecacheModel( TF_WEAPON_TAUNT_FRONTIER_JUSTICE_GUITAR_MODEL );
PrecacheGibsForModel( iModelIndex );
PrecacheParticleSystem( "blood_impact_backscatter" );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFShotgun_Revenge::PrimaryAttack()
{
if ( !CanAttack() )
return;
BaseClass::PrimaryAttack();
// Do this after the attack, so that we know if we are doing custom damage
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
if ( pOwner )
{
int iRevengeCrits = pOwner->m_Shared.GetRevengeCrits();
pOwner->m_Shared.SetRevengeCrits( iRevengeCrits-1 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFShotgun_Revenge::SentryKilled( int iCrits )
{
int val = 0;
CALL_ATTRIB_HOOK_INT( val, sentry_killed_revenge );
if ( val == 1 )
{
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
if ( pOwner )
{
pOwner->m_Shared.SetRevengeCrits( pOwner->m_Shared.GetRevengeCrits() + iCrits );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFShotgun_Revenge::Holster( CBaseCombatWeapon *pSwitchingTo )
{
#ifdef GAME_DLL
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
if ( pOwner && pOwner->m_Shared.GetRevengeCrits() )
{
pOwner->m_Shared.RemoveCond( TF_COND_CRITBOOSTED );
}
#endif
return BaseClass::Holster( pSwitchingTo );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFShotgun_Revenge::Deploy( void )
{
#ifdef GAME_DLL
CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
if ( pOwner && pOwner->m_Shared.GetRevengeCrits() )
{
pOwner->m_Shared.AddCond( TF_COND_CRITBOOSTED );
}
#endif
return BaseClass::Deploy();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFShotgun_Revenge::GetCustomDamageType() const
{
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
if ( pOwner )
{
int iRevengeCrits = pOwner->m_Shared.GetRevengeCrits();
return iRevengeCrits > 0 ? TF_DMG_CUSTOM_SHOTGUN_REVENGE_CRIT : TF_DMG_CUSTOM_NONE;
}
return TF_DMG_CUSTOM_NONE;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFShotgun_Revenge::GetCount( void )
{
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
if ( pOwner )
{
return pOwner->m_Shared.GetRevengeCrits();
}
return 0;
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
// ----------------------------------------------------------------------------
void CTFShotgun_Revenge::SetWeaponVisible( bool visible )
{
if ( !visible )
{
CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_ENGINEER && pPlayer->m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON )
{
int nModelIndex = modelinfo->GetModelIndex( TF_WEAPON_TAUNT_FRONTIER_JUSTICE_GUITAR_MODEL );
CUtlVector<breakmodel_t> guitarGibs;
BuildGibList( guitarGibs, nModelIndex, 1.0f, COLLISION_GROUP_NONE );
if ( guitarGibs.Count() > 0 )
{
Vector vForward, vRight, vUp;
AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
Vector vecBreakVelocity = Vector(0,0,200);
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
Vector vecOrigin = GetAbsOrigin() + vForward*70 + vUp*10;
QAngle vecAngle = GetAbsAngles();
breakablepropparams_t breakParams( vecOrigin, vecAngle, vecBreakVelocity, angularImpulse );
breakParams.impactEnergyScale = 1.0f;
CreateGibsFromList( guitarGibs, nModelIndex, NULL, breakParams, NULL, -1 , false, true );
}
}
}
BaseClass::SetWeaponVisible( visible );
}
//-----------------------------------------------------------------------------
// Purpose:
// ----------------------------------------------------------------------------
int CTFShotgun_Revenge::GetWorldModelIndex( void )
{
// Engineer guitar support.
CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
if ( pPlayer && pPlayer->GetPlayerClass() && ( pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_ENGINEER ) &&
( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) && ( pPlayer->m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON ) )
{
// While we are taunting, replace our normal world model with the guitar.
m_iWorldModelIndex = modelinfo->GetModelIndex( TF_WEAPON_TAUNT_FRONTIER_JUSTICE_GUITAR_MODEL );
return m_iWorldModelIndex;
}
return BaseClass::GetWorldModelIndex();
}
#endif
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: Reset revenge crits when the shotgun is changed
//-----------------------------------------------------------------------------
void CTFShotgun_Revenge::Detach( void )
{
CTFPlayer *pPlayer = GetTFPlayerOwner();
if ( pPlayer )
{
pPlayer->m_Shared.SetRevengeCrits( 0 );
pPlayer->m_Shared.RemoveCond( TF_COND_CRITBOOSTED );
}
BaseClass::Detach();
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFScatterGun::Reload( void )
{
int iWeaponMod = 0;
CALL_ATTRIB_HOOK_INT( iWeaponMod, set_scattergun_no_reload_single );
if ( iWeaponMod == 1 )
{
m_bReloadsSingly = false;
}
return BaseClass::Reload();
}
#define JUMP_SPEED 268.3281572999747f
extern ConVar tf_player_movement_stun_time;
extern float AirBurstDamageForce( const Vector &size, float damage, float scale );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFScatterGun::FireBullet( CTFPlayer *pPlayer )
{
#ifndef CLIENT_DLL
if ( HasKnockback() )
{
// Perform some knock back.
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
if ( !pOwner )
return;
// No knockback during pre-round freeze.
if ( TFGameRules() && (TFGameRules()->State_Get() == GR_STATE_PREROUND) )
return;
// Knock the firer back!
if ( !(pOwner->GetFlags() & FL_ONGROUND) && !pPlayer->m_bScattergunJump )
{
pPlayer->m_bScattergunJump = true;
pOwner->m_Shared.StunPlayer( 0.3f, 1.f, TF_STUN_MOVEMENT | TF_STUN_MOVEMENT_FORWARD_ONLY );
float flForce = AirBurstDamageForce( pOwner->WorldAlignSize(), 60, 6.f );
Vector vecForward;
AngleVectors( pOwner->EyeAngles(), &vecForward );
Vector vecForce = vecForward * -flForce;
EntityMatrix mtxPlayer;
mtxPlayer.InitFromEntity( pOwner );
Vector vecAbsVelocity = pOwner->GetAbsVelocity();
Vector vecAbsVelocityAsPoint = vecAbsVelocity + pOwner->GetAbsOrigin();
Vector vecLocalVelocity = mtxPlayer.WorldToLocal( vecAbsVelocityAsPoint );
vecLocalVelocity.x = -300;
vecAbsVelocityAsPoint = mtxPlayer.LocalToWorld( vecLocalVelocity );
vecAbsVelocity = vecAbsVelocityAsPoint - pOwner->GetAbsOrigin();
pOwner->SetAbsVelocity( vecAbsVelocity );
// Impulse an additional bit of Z push.
pOwner->ApplyAbsVelocityImpulse( Vector(0,0,50.f) );
// Slow player movement for a brief period of time.
pOwner->RemoveFlag( FL_ONGROUND );
}
}
#endif
BaseClass::FireBullet( pPlayer );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFScatterGun::ApplyPostHitEffects( const CTakeDamageInfo &inputInfo, CTFPlayer *pPlayer )
{
#ifndef CLIENT_DLL
if ( !HasKnockback() )
return;
CTFPlayer *pAttacker = ToTFPlayer( inputInfo.GetAttacker() );
if ( !pAttacker )
return;
CTFPlayer *pTarget = pPlayer;
if ( !pTarget )
return;
if ( pTarget->m_Shared.GetWeaponKnockbackID() > -1 )
return;
float flDam = inputInfo.GetDamage();
Vector vecDir = pAttacker->WorldSpaceCenter() - pTarget->WorldSpaceCenter();
if ( !CanScatterGunKnockBack( this, flDam, vecDir.LengthSqr() ) )
return;
// Immune to pushback/knockback
if ( pTarget->m_Shared.InCond( TF_COND_MEGAHEAL ) )
return;
VectorNormalize( vecDir );
float flKnockbackMult = 3.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( this, flKnockbackMult, scattergun_knockback_mult );
float flForce = AirBurstDamageForce( pTarget->WorldAlignSize(), flDam, flKnockbackMult );
Vector vecForce = vecDir * -flForce;
vecForce.z += JUMP_SPEED;
pTarget->ApplyAirBlastImpulse( vecForce );
pTarget->m_Shared.StunPlayer( 0.3f, 1.f, TF_STUN_MOVEMENT | TF_STUN_MOVEMENT_FORWARD_ONLY, pAttacker );
pTarget->m_Shared.SetWeaponKnockbackID( pAttacker->GetUserID() );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFScatterGun::FinishReload( void )
{
CTFPlayer* pOwner = ToTFPlayer( GetOwner() );
if ( !pOwner )
return;
if ( UsesClipsForAmmo1() && !m_bReloadsSingly )
{
int primary = MIN( GetMaxClip1() - m_iClip1, pOwner->GetAmmoCount(m_iPrimaryAmmoType));
m_iClip1 += primary;
// Takes a whole clip worth of ammo to reload, causing us to lose whatever was chambered.
pOwner->RemoveAmmo( GetMaxClip1(), m_iPrimaryAmmoType);
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFScatterGun::HasKnockback( void )
{
int iWeaponMod = 0;
CALL_ATTRIB_HOOK_INT( iWeaponMod, set_scattergun_has_knockback );
if ( iWeaponMod == 1 )
return true;
else
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Play animation appropriate to ball status.
//-----------------------------------------------------------------------------
bool CTFScatterGun::SendWeaponAnim( int iActivity )
{
CTFPlayer *pPlayer = GetTFPlayerOwner();
if ( !pPlayer )
return BaseClass::SendWeaponAnim( iActivity );
if ( HasKnockback() )
{
// Knockback version uses a different model and animation set.
switch ( iActivity )
{
case ACT_VM_DRAW:
iActivity = ACT_ITEM2_VM_DRAW;
break;
case ACT_VM_HOLSTER:
iActivity = ACT_ITEM2_VM_HOLSTER;
break;
case ACT_VM_IDLE:
iActivity = ACT_ITEM2_VM_IDLE;
break;
case ACT_VM_PULLBACK:
iActivity = ACT_ITEM2_VM_PULLBACK;
break;
case ACT_VM_PRIMARYATTACK:
iActivity = ACT_ITEM2_VM_PRIMARYATTACK;
break;
case ACT_VM_SECONDARYATTACK:
iActivity = ACT_ITEM2_VM_SECONDARYATTACK;
break;
case ACT_VM_RELOAD:
iActivity = ACT_ITEM2_VM_RELOAD;
break;
case ACT_VM_DRYFIRE:
iActivity = ACT_ITEM2_VM_DRYFIRE;
break;
case ACT_VM_IDLE_TO_LOWERED:
iActivity = ACT_ITEM2_VM_IDLE_TO_LOWERED;
break;
case ACT_VM_IDLE_LOWERED:
iActivity = ACT_ITEM2_VM_IDLE_LOWERED;
break;
case ACT_VM_LOWERED_TO_IDLE:
iActivity = ACT_ITEM2_VM_LOWERED_TO_IDLE;
break;
default:
break;
}
}
return BaseClass::SendWeaponAnim( iActivity );
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
void CTFScatterGun::Equip( CBaseCombatCharacter *pOwner )
{
CTFPlayer *pPlayer = dynamic_cast<CTFPlayer*>( pOwner );
if ( pPlayer )
{
pPlayer->m_Shared.SetScoutHypeMeter( 0.0f );
}
BaseClass::Equip( pOwner );
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// CTFSodaPopper
//-----------------------------------------------------------------------------
float CTFSodaPopper::GetProgress( void )
{
CTFPlayer *pPlayer = GetTFPlayerOwner();
if ( !pPlayer )
return 0.f;
return pPlayer->m_Shared.GetScoutHypeMeter() * 0.01f;
}
//-----------------------------------------------------------------------------
void CTFSodaPopper::ItemBusyFrame( void )
{
#ifdef GAME_DLL
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 )
{
// Check here so we can always activate buff when we want (similar to stickies)
SecondaryAttack();
}
#endif
BaseClass::ItemBusyFrame();
}
//-----------------------------------------------------------------------------
void CTFSodaPopper::SecondaryAttack()
{
CTFPlayer *pPlayer = GetTFPlayerOwner( );
if ( !pPlayer || pPlayer->m_Shared.IsHypeBuffed() )
return;
if ( pPlayer->m_Shared.GetScoutHypeMeter() >= 100.f )
{
pPlayer->m_Shared.AddCond( TF_COND_SODAPOPPER_HYPE );
}
}
//-----------------------------------------------------------------------------
float CTFPEPBrawlerBlaster::GetProgress( void )
{
CTFPlayer *pPlayer = GetTFPlayerOwner();
if ( !pPlayer )
return 0.f;
return pPlayer->m_Shared.GetScoutHypeMeter() * 0.01f;
}
//-----------------------------------------------------------------------------
float CTFShotgunBuildingRescue::GetProjectileSpeed( void )
{
return RemapValClamped( 0.75f, 0.0f, 1.f, 1800, 2600 ); // Temp, if we want to ramp.
}
//-----------------------------------------------------------------------------
float CTFShotgunBuildingRescue::GetProjectileGravity( void )
{
return RemapValClamped( 0.75f, 0.0f, 1.f, 0.5f, 0.1f ); // Temp, if we want to ramp.
}
//-----------------------------------------------------------------------------
bool CTFShotgunBuildingRescue::IsViewModelFlipped( void )
{
return !BaseClass::IsViewModelFlipped(); // Invert because arrows are backwards by default.
}