diff options
Diffstat (limited to 'game/shared/tf/tf_fx_shared.cpp')
| -rw-r--r-- | game/shared/tf/tf_fx_shared.cpp | 368 |
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(); +} |