//========= 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 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( 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. }