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.

368 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_fx_shared.h"
  8. #include "tf_weaponbase.h"
  9. #include "takedamageinfo.h"
  10. #include "tf_gamerules.h"
  11. // Client specific.
  12. #ifdef CLIENT_DLL
  13. #include "fx_impact.h"
  14. // Server specific.
  15. #else
  16. #include "tf_fx.h"
  17. #include "ilagcompensationmanager.h"
  18. #include "tf_passtime_logic.h"
  19. #endif
  20. 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." );
  21. // Client specific.
  22. #ifdef CLIENT_DLL
  23. class CGroupedSound
  24. {
  25. public:
  26. string_t m_SoundName;
  27. Vector m_vecPos;
  28. };
  29. CUtlVector<CGroupedSound> g_aGroupedSounds;
  30. //-----------------------------------------------------------------------------
  31. // Purpose: Called by the ImpactSound function.
  32. //-----------------------------------------------------------------------------
  33. void ImpactSoundGroup( const char *pSoundName, const Vector &vecEndPos )
  34. {
  35. int iSound = 0;
  36. // Don't play the sound if it's too close to another impact sound.
  37. for ( iSound = 0; iSound < g_aGroupedSounds.Count(); ++iSound )
  38. {
  39. CGroupedSound *pSound = &g_aGroupedSounds[iSound];
  40. if ( pSound )
  41. {
  42. if ( vecEndPos.DistToSqr( pSound->m_vecPos ) < ( 300.0f * 300.0f ) )
  43. {
  44. if ( Q_stricmp( pSound->m_SoundName, pSoundName ) == 0 )
  45. return;
  46. }
  47. }
  48. }
  49. // Ok, play the sound and add it to the list.
  50. CLocalPlayerFilter filter;
  51. C_BaseEntity::EmitSound( filter, NULL, pSoundName, &vecEndPos );
  52. iSound = g_aGroupedSounds.AddToTail();
  53. g_aGroupedSounds[iSound].m_SoundName = pSoundName;
  54. g_aGroupedSounds[iSound].m_vecPos = vecEndPos;
  55. }
  56. //-----------------------------------------------------------------------------
  57. // Purpose: This is a cheap ripoff from CBaseCombatWeapon::WeaponSound().
  58. //-----------------------------------------------------------------------------
  59. void FX_WeaponSound( int iPlayer, WeaponSound_t soundType, const Vector &vecOrigin, CTFWeaponInfo *pWeaponInfo )
  60. {
  61. // If we have some sounds from the weapon classname.txt file, play a random one of them
  62. const char *pShootSound = pWeaponInfo->aShootSounds[soundType];
  63. if ( !pShootSound || !pShootSound[0] )
  64. return;
  65. CBroadcastRecipientFilter filter;
  66. if ( !te->CanPredict() )
  67. return;
  68. CBaseEntity::EmitSound( filter, iPlayer, pShootSound, &vecOrigin );
  69. }
  70. //-----------------------------------------------------------------------------
  71. // Purpose:
  72. //-----------------------------------------------------------------------------
  73. void StartGroupingSounds()
  74. {
  75. Assert( g_aGroupedSounds.Count() == 0 );
  76. SetImpactSoundRoute( ImpactSoundGroup );
  77. }
  78. //-----------------------------------------------------------------------------
  79. // Purpose:
  80. //-----------------------------------------------------------------------------
  81. void EndGroupingSounds()
  82. {
  83. g_aGroupedSounds.Purge();
  84. SetImpactSoundRoute( NULL );
  85. }
  86. // Server specific.
  87. #else
  88. // Server doesn't play sounds.
  89. void FX_WeaponSound ( int iPlayer, WeaponSound_t soundType, const Vector &vecOrigin, CTFWeaponInfo *pWeaponInfo ) {}
  90. void StartGroupingSounds() {}
  91. void EndGroupingSounds() {}
  92. #endif
  93. Vector g_vecFixedWpnSpreadPellets[] =
  94. {
  95. Vector( 0,0,0 ), // First pellet goes down the middle
  96. Vector( 1,0,0 ),
  97. Vector( -1,0,0 ),
  98. Vector( 0,-1,0 ),
  99. Vector( 0,1,0 ),
  100. Vector( 0.85,-0.85,0 ),
  101. Vector( 0.85,0.85,0 ),
  102. Vector( -0.85,-0.85,0 ),
  103. Vector( -0.85,0.85,0 ),
  104. Vector( 0,0,0 ), // last pellet goes down the middle as well to reward fine aim
  105. };
  106. //-----------------------------------------------------------------------------
  107. // Purpose: This runs on both the client and the server. On the server, it
  108. // only does the damage calculations. On the client, it does all the effects.
  109. //-----------------------------------------------------------------------------
  110. void FX_FireBullets( CTFWeaponBase *pWpn, int iPlayer, const Vector &vecOrigin, const QAngle &vecAngles,
  111. int iWeapon, int iMode, int iSeed, float flSpread, float flDamage /* = -1.0f */, bool bCritical /* = false*/ )
  112. {
  113. // Get the weapon information.
  114. const char *pszWeaponAlias = WeaponIdToAlias( iWeapon );
  115. if ( !pszWeaponAlias )
  116. {
  117. DevMsg( 1, "FX_FireBullets: weapon alias for ID %i not found\n", iWeapon );
  118. return;
  119. }
  120. WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pszWeaponAlias );
  121. if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
  122. {
  123. DevMsg( 1, "FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", pszWeaponAlias );
  124. return;
  125. }
  126. CTFWeaponInfo *pWeaponInfo = static_cast<CTFWeaponInfo*>( GetFileWeaponInfoFromHandle( hWpnInfo ) );
  127. if( !pWeaponInfo )
  128. return;
  129. bool bDoEffects = false;
  130. #ifdef CLIENT_DLL
  131. C_TFPlayer *pPlayer = ToTFPlayer( ClientEntityList().GetBaseEntity( iPlayer ) );
  132. #else
  133. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
  134. #endif
  135. if ( !pPlayer )
  136. return;
  137. // Client specific.
  138. #ifdef CLIENT_DLL
  139. bDoEffects = true;
  140. // The minigun has custom sound & animation code to deal with its windup/down.
  141. if ( !pPlayer->IsLocalPlayer()
  142. && iWeapon != TF_WEAPON_MINIGUN )
  143. {
  144. // Fire the animation event.
  145. if ( pPlayer && !pPlayer->IsDormant() )
  146. {
  147. if ( iMode == TF_WEAPON_PRIMARY_MODE )
  148. {
  149. pPlayer->m_PlayerAnimState->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
  150. }
  151. else
  152. {
  153. pPlayer->m_PlayerAnimState->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );
  154. }
  155. }
  156. //FX_WeaponSound( pPlayer->entindex(), SINGLE, vecOrigin, pWeaponInfo );
  157. }
  158. // Server specific.
  159. #else
  160. // If this is server code, send the effect over to client as temp entity and
  161. // dispatch one message for all the bullet impacts and sounds.
  162. TE_FireBullets( pPlayer->entindex(), vecOrigin, vecAngles, iWeapon, iMode, iSeed, flSpread, bCritical );
  163. // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
  164. pPlayer->NoteWeaponFired();
  165. #endif
  166. // Fire bullets, calculate impacts & effects.
  167. StartGroupingSounds();
  168. #if !defined (CLIENT_DLL)
  169. // Move other players back to history positions based on local player's lag
  170. lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
  171. // PASSTIME custom lag compensation for the ball; see also tf_weapon_flamethrower.cpp
  172. // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
  173. if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
  174. {
  175. g_pPasstimeLogic->GetBall()->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
  176. }
  177. #endif
  178. // Get the shooting angles.
  179. Vector vecShootForward, vecShootRight, vecShootUp;
  180. AngleVectors( vecAngles, &vecShootForward, &vecShootRight, &vecShootUp );
  181. // Initialize the static firing information.
  182. FireBulletsInfo_t fireInfo;
  183. fireInfo.m_vecSrc = vecOrigin;
  184. if ( flDamage < 0.0f )
  185. {
  186. fireInfo.m_flDamage = pWeaponInfo->GetWeaponData( iMode ).m_nDamage;
  187. }
  188. else
  189. {
  190. fireInfo.m_flDamage = flDamage;
  191. }
  192. fireInfo.m_flDistance = pWeaponInfo->GetWeaponData( iMode ).m_flRange;
  193. fireInfo.m_iShots = 1;
  194. fireInfo.m_vecSpread.Init( flSpread, flSpread, 0.0f );
  195. fireInfo.m_iAmmoType = pWeaponInfo->iAmmoType;
  196. // Ammo override
  197. int iModUseMetalOverride = 0;
  198. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWpn, iModUseMetalOverride, mod_use_metal_ammo_type );
  199. if ( iModUseMetalOverride )
  200. {
  201. fireInfo.m_iAmmoType = TF_AMMO_METAL;
  202. }
  203. // Setup the bullet damage type & roll for crit.
  204. int nDamageType = DMG_GENERIC;
  205. int nCustomDamageType = TF_DMG_CUSTOM_NONE;
  206. CTFWeaponBase *pWeapon = pPlayer->GetActiveTFWeapon(); // FIXME: Should this be pWpn?
  207. if ( pWeapon )
  208. {
  209. nDamageType = pWeapon->GetDamageType();
  210. if ( pWeapon->IsCurrentAttackACrit() || bCritical )
  211. {
  212. nDamageType |= DMG_CRITICAL;
  213. }
  214. nCustomDamageType = pWeapon->GetCustomDamageType();
  215. }
  216. if ( iWeapon != TF_WEAPON_MINIGUN )
  217. {
  218. fireInfo.m_iTracerFreq = 2;
  219. }
  220. // Reset multi-damage structures.
  221. ClearMultiDamage();
  222. #if !defined (CLIENT_DLL)
  223. // If this weapon fires multiple projectiles per shot, and can penetrate multiple
  224. // targets, aggregate CTakeDamageInfo events and send them off as one event
  225. CDmgAccumulator *pDmgAccumulator = pWpn ? pWpn->GetDmgAccumulator() : NULL;
  226. if ( pDmgAccumulator )
  227. {
  228. pDmgAccumulator->Start();
  229. }
  230. #endif // !CLIENT
  231. int nBulletsPerShot = pWeaponInfo->GetWeaponData( iMode ).m_nBulletsPerShot;
  232. bool bFixedSpread = ( nDamageType & DMG_BUCKSHOT ) && ( nBulletsPerShot > 1 ) && IsFixedWeaponSpreadEnabled();
  233. if ( pWeapon )
  234. {
  235. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, nBulletsPerShot, mult_bullets_per_shot );
  236. }
  237. for ( int iBullet = 0; iBullet < nBulletsPerShot; ++iBullet )
  238. {
  239. // Initialize random system with this seed.
  240. RandomSeed( iSeed );
  241. // Get circular gaussian spread. Under some cases we fire a bullet right down the crosshair:
  242. // - The first bullet of a spread weapon (except for rapid fire spread weapons like the minigun)
  243. // - The first bullet of a non-spread weapon if it's been >1.25 second since firing
  244. bool bFirePerfect = false;
  245. if ( iBullet == 0 && pWpn )
  246. {
  247. float flTimeSinceLastShot = (gpGlobals->curtime - pWpn->m_flLastFireTime );
  248. if ( nBulletsPerShot > 1 && flTimeSinceLastShot > 0.25 )
  249. {
  250. bFirePerfect = true;
  251. }
  252. else if ( nBulletsPerShot == 1 && flTimeSinceLastShot > 1.25 )
  253. {
  254. bFirePerfect = true;
  255. }
  256. }
  257. float x,y;
  258. if ( bFixedSpread )
  259. {
  260. int iSpread = iBullet;
  261. while ( iSpread >= ARRAYSIZE(g_vecFixedWpnSpreadPellets) )
  262. {
  263. iSpread -= ARRAYSIZE(g_vecFixedWpnSpreadPellets);
  264. }
  265. float flScalar = 0.5;
  266. x = g_vecFixedWpnSpreadPellets[iSpread].x * flScalar;
  267. y = g_vecFixedWpnSpreadPellets[iSpread].y * flScalar;
  268. }
  269. else if ( bFirePerfect )
  270. {
  271. x = y = 0;
  272. }
  273. else
  274. {
  275. x = RandomFloat( -0.5, 0.5 ) + RandomFloat( -0.5, 0.5 );
  276. y = RandomFloat( -0.5, 0.5 ) + RandomFloat( -0.5, 0.5 );
  277. }
  278. // Initialize the varialbe firing information.
  279. fireInfo.m_vecDirShooting = vecShootForward + ( x * flSpread * vecShootRight ) + ( y * flSpread * vecShootUp );
  280. fireInfo.m_vecDirShooting.NormalizeInPlace();
  281. fireInfo.m_bUseServerRandomSeed = pWpn && pWpn->UseServerRandomSeed();
  282. // Fire a bullet.
  283. pPlayer->FireBullet( pWpn, fireInfo, bDoEffects, nDamageType, nCustomDamageType );
  284. // Use new seed for next bullet.
  285. ++iSeed;
  286. }
  287. #if !defined (CLIENT_DLL)
  288. if ( pDmgAccumulator )
  289. {
  290. pDmgAccumulator->Process();
  291. }
  292. #endif // !CLIENT
  293. // Apply damage if any.
  294. ApplyMultiDamage();
  295. #if !defined (CLIENT_DLL)
  296. lagcompensation->FinishLagCompensation( pPlayer );
  297. // PASSTIME custom lag compensation for the ball; see also tf_weapon_flamethrower.cpp
  298. // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
  299. if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
  300. {
  301. g_pPasstimeLogic->GetBall()->FinishLagCompensation( pPlayer );
  302. }
  303. #endif
  304. EndGroupingSounds();
  305. }
  306. //-----------------------------------------------------------------------------
  307. // Purpose: Should we make this a per-weapon property?
  308. //-----------------------------------------------------------------------------
  309. bool IsFixedWeaponSpreadEnabled( void )
  310. {
  311. const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
  312. if ( pMatchDesc )
  313. return pMatchDesc->m_params.m_bFixedWeaponSpread;
  314. return tf_use_fixed_weaponspreads.GetBool();
  315. }