summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_fx_shared.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_fx_shared.cpp')
-rw-r--r--game/shared/tf/tf_fx_shared.cpp368
1 files changed, 368 insertions, 0 deletions
diff --git a/game/shared/tf/tf_fx_shared.cpp b/game/shared/tf/tf_fx_shared.cpp
new file mode 100644
index 0000000..fe2180d
--- /dev/null
+++ b/game/shared/tf/tf_fx_shared.cpp
@@ -0,0 +1,368 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_fx_shared.h"
+#include "tf_weaponbase.h"
+#include "takedamageinfo.h"
+#include "tf_gamerules.h"
+
+// Client specific.
+#ifdef CLIENT_DLL
+#include "fx_impact.h"
+// Server specific.
+#else
+#include "tf_fx.h"
+#include "ilagcompensationmanager.h"
+#include "tf_passtime_logic.h"
+#endif
+
+ConVar tf_use_fixed_weaponspreads( "tf_use_fixed_weaponspreads", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "If set to 1, weapons that fire multiple pellets per shot will use a non-random pellet distribution." );
+
+// Client specific.
+#ifdef CLIENT_DLL
+
+class CGroupedSound
+{
+public:
+ string_t m_SoundName;
+ Vector m_vecPos;
+};
+
+CUtlVector<CGroupedSound> g_aGroupedSounds;
+
+//-----------------------------------------------------------------------------
+// Purpose: Called by the ImpactSound function.
+//-----------------------------------------------------------------------------
+void ImpactSoundGroup( const char *pSoundName, const Vector &vecEndPos )
+{
+ int iSound = 0;
+
+ // Don't play the sound if it's too close to another impact sound.
+ for ( iSound = 0; iSound < g_aGroupedSounds.Count(); ++iSound )
+ {
+ CGroupedSound *pSound = &g_aGroupedSounds[iSound];
+ if ( pSound )
+ {
+ if ( vecEndPos.DistToSqr( pSound->m_vecPos ) < ( 300.0f * 300.0f ) )
+ {
+ if ( Q_stricmp( pSound->m_SoundName, pSoundName ) == 0 )
+ return;
+ }
+ }
+ }
+
+ // Ok, play the sound and add it to the list.
+ CLocalPlayerFilter filter;
+ C_BaseEntity::EmitSound( filter, NULL, pSoundName, &vecEndPos );
+
+ iSound = g_aGroupedSounds.AddToTail();
+ g_aGroupedSounds[iSound].m_SoundName = pSoundName;
+ g_aGroupedSounds[iSound].m_vecPos = vecEndPos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is a cheap ripoff from CBaseCombatWeapon::WeaponSound().
+//-----------------------------------------------------------------------------
+void FX_WeaponSound( int iPlayer, WeaponSound_t soundType, const Vector &vecOrigin, CTFWeaponInfo *pWeaponInfo )
+{
+ // If we have some sounds from the weapon classname.txt file, play a random one of them
+ const char *pShootSound = pWeaponInfo->aShootSounds[soundType];
+ if ( !pShootSound || !pShootSound[0] )
+ return;
+
+ CBroadcastRecipientFilter filter;
+ if ( !te->CanPredict() )
+ return;
+
+ CBaseEntity::EmitSound( filter, iPlayer, pShootSound, &vecOrigin );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void StartGroupingSounds()
+{
+ Assert( g_aGroupedSounds.Count() == 0 );
+ SetImpactSoundRoute( ImpactSoundGroup );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void EndGroupingSounds()
+{
+ g_aGroupedSounds.Purge();
+ SetImpactSoundRoute( NULL );
+}
+
+// Server specific.
+#else
+
+// Server doesn't play sounds.
+void FX_WeaponSound ( int iPlayer, WeaponSound_t soundType, const Vector &vecOrigin, CTFWeaponInfo *pWeaponInfo ) {}
+void StartGroupingSounds() {}
+void EndGroupingSounds() {}
+
+#endif
+
+Vector g_vecFixedWpnSpreadPellets[] =
+{
+ Vector( 0,0,0 ), // First pellet goes down the middle
+ Vector( 1,0,0 ),
+ Vector( -1,0,0 ),
+ Vector( 0,-1,0 ),
+ Vector( 0,1,0 ),
+ Vector( 0.85,-0.85,0 ),
+ Vector( 0.85,0.85,0 ),
+ Vector( -0.85,-0.85,0 ),
+ Vector( -0.85,0.85,0 ),
+ Vector( 0,0,0 ), // last pellet goes down the middle as well to reward fine aim
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: This runs on both the client and the server. On the server, it
+// only does the damage calculations. On the client, it does all the effects.
+//-----------------------------------------------------------------------------
+void FX_FireBullets( CTFWeaponBase *pWpn, int iPlayer, const Vector &vecOrigin, const QAngle &vecAngles,
+ int iWeapon, int iMode, int iSeed, float flSpread, float flDamage /* = -1.0f */, bool bCritical /* = false*/ )
+{
+ // Get the weapon information.
+ const char *pszWeaponAlias = WeaponIdToAlias( iWeapon );
+ if ( !pszWeaponAlias )
+ {
+ DevMsg( 1, "FX_FireBullets: weapon alias for ID %i not found\n", iWeapon );
+ return;
+ }
+
+ WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pszWeaponAlias );
+ if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
+ {
+ DevMsg( 1, "FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", pszWeaponAlias );
+ return;
+ }
+
+ CTFWeaponInfo *pWeaponInfo = static_cast<CTFWeaponInfo*>( GetFileWeaponInfoFromHandle( hWpnInfo ) );
+ if( !pWeaponInfo )
+ return;
+
+ bool bDoEffects = false;
+
+#ifdef CLIENT_DLL
+ C_TFPlayer *pPlayer = ToTFPlayer( ClientEntityList().GetBaseEntity( iPlayer ) );
+#else
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
+#endif
+ if ( !pPlayer )
+ return;
+
+// Client specific.
+#ifdef CLIENT_DLL
+ bDoEffects = true;
+
+ // The minigun has custom sound & animation code to deal with its windup/down.
+ if ( !pPlayer->IsLocalPlayer()
+ && iWeapon != TF_WEAPON_MINIGUN )
+ {
+ // Fire the animation event.
+ if ( pPlayer && !pPlayer->IsDormant() )
+ {
+ if ( iMode == TF_WEAPON_PRIMARY_MODE )
+ {
+ pPlayer->m_PlayerAnimState->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
+ }
+ else
+ {
+ pPlayer->m_PlayerAnimState->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );
+ }
+ }
+
+ //FX_WeaponSound( pPlayer->entindex(), SINGLE, vecOrigin, pWeaponInfo );
+ }
+
+// Server specific.
+#else
+ // If this is server code, send the effect over to client as temp entity and
+ // dispatch one message for all the bullet impacts and sounds.
+ TE_FireBullets( pPlayer->entindex(), vecOrigin, vecAngles, iWeapon, iMode, iSeed, flSpread, bCritical );
+
+ // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
+ pPlayer->NoteWeaponFired();
+
+#endif
+
+ // Fire bullets, calculate impacts & effects.
+ StartGroupingSounds();
+
+#if !defined (CLIENT_DLL)
+ // Move other players back to history positions based on local player's lag
+ lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
+
+ // PASSTIME custom lag compensation for the ball; see also tf_weapon_flamethrower.cpp
+ // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
+ if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
+ {
+ g_pPasstimeLogic->GetBall()->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
+ }
+#endif
+
+ // Get the shooting angles.
+ Vector vecShootForward, vecShootRight, vecShootUp;
+ AngleVectors( vecAngles, &vecShootForward, &vecShootRight, &vecShootUp );
+
+ // Initialize the static firing information.
+ FireBulletsInfo_t fireInfo;
+ fireInfo.m_vecSrc = vecOrigin;
+ if ( flDamage < 0.0f )
+ {
+ fireInfo.m_flDamage = pWeaponInfo->GetWeaponData( iMode ).m_nDamage;
+ }
+ else
+ {
+ fireInfo.m_flDamage = flDamage;
+ }
+ fireInfo.m_flDistance = pWeaponInfo->GetWeaponData( iMode ).m_flRange;
+ fireInfo.m_iShots = 1;
+ fireInfo.m_vecSpread.Init( flSpread, flSpread, 0.0f );
+ fireInfo.m_iAmmoType = pWeaponInfo->iAmmoType;
+
+ // Ammo override
+ int iModUseMetalOverride = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWpn, iModUseMetalOverride, mod_use_metal_ammo_type );
+ if ( iModUseMetalOverride )
+ {
+ fireInfo.m_iAmmoType = TF_AMMO_METAL;
+ }
+
+ // Setup the bullet damage type & roll for crit.
+ int nDamageType = DMG_GENERIC;
+ int nCustomDamageType = TF_DMG_CUSTOM_NONE;
+ CTFWeaponBase *pWeapon = pPlayer->GetActiveTFWeapon(); // FIXME: Should this be pWpn?
+ if ( pWeapon )
+ {
+ nDamageType = pWeapon->GetDamageType();
+ if ( pWeapon->IsCurrentAttackACrit() || bCritical )
+ {
+ nDamageType |= DMG_CRITICAL;
+ }
+
+ nCustomDamageType = pWeapon->GetCustomDamageType();
+ }
+
+ if ( iWeapon != TF_WEAPON_MINIGUN )
+ {
+ fireInfo.m_iTracerFreq = 2;
+ }
+
+ // Reset multi-damage structures.
+ ClearMultiDamage();
+
+#if !defined (CLIENT_DLL)
+ // If this weapon fires multiple projectiles per shot, and can penetrate multiple
+ // targets, aggregate CTakeDamageInfo events and send them off as one event
+ CDmgAccumulator *pDmgAccumulator = pWpn ? pWpn->GetDmgAccumulator() : NULL;
+ if ( pDmgAccumulator )
+ {
+ pDmgAccumulator->Start();
+ }
+#endif // !CLIENT
+
+ int nBulletsPerShot = pWeaponInfo->GetWeaponData( iMode ).m_nBulletsPerShot;
+ bool bFixedSpread = ( nDamageType & DMG_BUCKSHOT ) && ( nBulletsPerShot > 1 ) && IsFixedWeaponSpreadEnabled();
+ if ( pWeapon )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, nBulletsPerShot, mult_bullets_per_shot );
+ }
+ for ( int iBullet = 0; iBullet < nBulletsPerShot; ++iBullet )
+ {
+ // Initialize random system with this seed.
+ RandomSeed( iSeed );
+
+ // Get circular gaussian spread. Under some cases we fire a bullet right down the crosshair:
+ // - The first bullet of a spread weapon (except for rapid fire spread weapons like the minigun)
+ // - The first bullet of a non-spread weapon if it's been >1.25 second since firing
+ bool bFirePerfect = false;
+ if ( iBullet == 0 && pWpn )
+ {
+ float flTimeSinceLastShot = (gpGlobals->curtime - pWpn->m_flLastFireTime );
+ if ( nBulletsPerShot > 1 && flTimeSinceLastShot > 0.25 )
+ {
+ bFirePerfect = true;
+ }
+ else if ( nBulletsPerShot == 1 && flTimeSinceLastShot > 1.25 )
+ {
+ bFirePerfect = true;
+ }
+ }
+
+ float x,y;
+ if ( bFixedSpread )
+ {
+ int iSpread = iBullet;
+ while ( iSpread >= ARRAYSIZE(g_vecFixedWpnSpreadPellets) )
+ {
+ iSpread -= ARRAYSIZE(g_vecFixedWpnSpreadPellets);
+ }
+ float flScalar = 0.5;
+ x = g_vecFixedWpnSpreadPellets[iSpread].x * flScalar;
+ y = g_vecFixedWpnSpreadPellets[iSpread].y * flScalar;
+ }
+ else if ( bFirePerfect )
+ {
+ x = y = 0;
+ }
+ else
+ {
+ x = RandomFloat( -0.5, 0.5 ) + RandomFloat( -0.5, 0.5 );
+ y = RandomFloat( -0.5, 0.5 ) + RandomFloat( -0.5, 0.5 );
+ }
+
+ // Initialize the varialbe firing information.
+ fireInfo.m_vecDirShooting = vecShootForward + ( x * flSpread * vecShootRight ) + ( y * flSpread * vecShootUp );
+ fireInfo.m_vecDirShooting.NormalizeInPlace();
+ fireInfo.m_bUseServerRandomSeed = pWpn && pWpn->UseServerRandomSeed();
+
+ // Fire a bullet.
+ pPlayer->FireBullet( pWpn, fireInfo, bDoEffects, nDamageType, nCustomDamageType );
+
+ // Use new seed for next bullet.
+ ++iSeed;
+ }
+
+#if !defined (CLIENT_DLL)
+ if ( pDmgAccumulator )
+ {
+ pDmgAccumulator->Process();
+ }
+#endif // !CLIENT
+
+ // Apply damage if any.
+ ApplyMultiDamage();
+
+#if !defined (CLIENT_DLL)
+ lagcompensation->FinishLagCompensation( pPlayer );
+
+ // PASSTIME custom lag compensation for the ball; see also tf_weapon_flamethrower.cpp
+ // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
+ if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
+ {
+ g_pPasstimeLogic->GetBall()->FinishLagCompensation( pPlayer );
+ }
+#endif
+
+ EndGroupingSounds();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Should we make this a per-weapon property?
+//-----------------------------------------------------------------------------
+bool IsFixedWeaponSpreadEnabled( void )
+{
+ const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
+ if ( pMatchDesc )
+ return pMatchDesc->m_params.m_bFixedWeaponSpread;
+
+ return tf_use_fixed_weaponspreads.GetBool();
+}