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.

451 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "cbase.h"
  7. #include "decals.h"
  8. #include "materialsystem/imaterialvar.h"
  9. #include "IEffects.h"
  10. #include "fx.h"
  11. #include "fx_impact.h"
  12. #include "view.h"
  13. #ifdef TF_CLIENT_DLL
  14. #include "cdll_util.h"
  15. #include "tf_gamerules.h"
  16. #endif
  17. #include "engine/IStaticPropMgr.h"
  18. #include "c_impact_effects.h"
  19. #include "tier0/vprof.h"
  20. // memdbgon must be the last include file in a .cpp file!!!
  21. #include "tier0/memdbgon.h"
  22. static ConVar r_drawflecks( "r_drawflecks", "1", FCVAR_ALLOWED_IN_COMPETITIVE );
  23. extern ConVar r_drawmodeldecals;
  24. ImpactSoundRouteFn g_pImpactSoundRouteFn = NULL;
  25. //==========================================================================================================================
  26. // RAGDOLL ENUMERATOR
  27. //==========================================================================================================================
  28. CRagdollEnumerator::CRagdollEnumerator( Ray_t& shot, int iDamageType )
  29. {
  30. m_rayShot = shot;
  31. m_iDamageType = iDamageType;
  32. m_bHit = false;
  33. }
  34. IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity )
  35. {
  36. C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() );
  37. if ( pEnt == NULL )
  38. return ITERATION_CONTINUE;
  39. C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt );
  40. // If the ragdoll was created on this tick, then the forces were already applied on the server
  41. if ( pModel == NULL || WasRagdollCreatedOnCurrentTick( pEnt ) )
  42. return ITERATION_CONTINUE;
  43. IPhysicsObject *pPhysicsObject = pModel->VPhysicsGetObject();
  44. if ( pPhysicsObject == NULL )
  45. return ITERATION_CONTINUE;
  46. trace_t tr;
  47. enginetrace->ClipRayToEntity( m_rayShot, MASK_SHOT, pModel, &tr );
  48. if ( tr.fraction < 1.0 )
  49. {
  50. pModel->ImpactTrace( &tr, m_iDamageType, NULL );
  51. m_bHit = true;
  52. //FIXME: Yes? No?
  53. return ITERATION_STOP;
  54. }
  55. return ITERATION_CONTINUE;
  56. }
  57. //-----------------------------------------------------------------------------
  58. // Purpose:
  59. //-----------------------------------------------------------------------------
  60. bool FX_AffectRagdolls( Vector vecOrigin, Vector vecStart, int iDamageType )
  61. {
  62. // don't do this when lots of ragdolls are simulating
  63. if ( s_RagdollLRU.CountRagdolls(true) > 1 )
  64. return false;
  65. Ray_t shotRay;
  66. shotRay.Init( vecStart, vecOrigin );
  67. CRagdollEnumerator ragdollEnum( shotRay, iDamageType );
  68. ::partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum );
  69. return ragdollEnum.Hit();
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Purpose:
  73. // Input : &data -
  74. //-----------------------------------------------------------------------------
  75. void RagdollImpactCallback( const CEffectData &data )
  76. {
  77. FX_AffectRagdolls( data.m_vOrigin, data.m_vStart, data.m_nDamageType );
  78. }
  79. DECLARE_CLIENT_EFFECT( "RagdollImpact", RagdollImpactCallback );
  80. //-----------------------------------------------------------------------------
  81. // Purpose:
  82. //-----------------------------------------------------------------------------
  83. bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType, int iHitbox, C_BaseEntity *pEntity, trace_t &tr, int nFlags, int maxLODToDecal )
  84. {
  85. VPROF( "Impact" );
  86. Assert ( pEntity );
  87. // Clear out the trace
  88. memset( &tr, 0, sizeof(trace_t));
  89. tr.fraction = 1.0f;
  90. // Setup our shot information
  91. Vector shotDir = vecOrigin - vecStart;
  92. float flLength = VectorNormalize( shotDir );
  93. Vector traceExt;
  94. VectorMA( vecStart, flLength + 8.0f, shotDir, traceExt );
  95. // Attempt to hit ragdolls
  96. bool bHitRagdoll = false;
  97. if ( !pEntity->IsClientCreated() )
  98. {
  99. bHitRagdoll = FX_AffectRagdolls( vecOrigin, vecStart, iDamageType );
  100. }
  101. if ( (nFlags & IMPACT_NODECAL) == 0 )
  102. {
  103. const char *pchDecalName = GetImpactDecal( pEntity, iMaterial, iDamageType );
  104. int decalNumber = decalsystem->GetDecalIndexForName( pchDecalName );
  105. if ( decalNumber == -1 )
  106. return false;
  107. bool bSkipDecal = false;
  108. #ifdef TF_CLIENT_DLL
  109. // Don't show blood decals if we're filtering them out (Pyro Goggles)
  110. if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) || UTIL_IsLowViolence() || ( TFGameRules() && TFGameRules()->IsTruceActive() ) )
  111. {
  112. if ( V_strstr( pchDecalName, "Flesh" ) )
  113. {
  114. bSkipDecal = true;
  115. }
  116. }
  117. #endif
  118. if ( !bSkipDecal )
  119. {
  120. if ( (pEntity->entindex() == 0) && (iHitbox != 0) )
  121. {
  122. staticpropmgr->AddDecalToStaticProp( vecStart, traceExt, iHitbox - 1, decalNumber, true, tr );
  123. }
  124. else if ( pEntity )
  125. {
  126. // Here we deal with decals on entities.
  127. pEntity->AddDecal( vecStart, traceExt, vecOrigin, iHitbox, decalNumber, true, tr, maxLODToDecal );
  128. }
  129. }
  130. }
  131. else
  132. {
  133. // Perform the trace ourselves
  134. Ray_t ray;
  135. ray.Init( vecStart, traceExt );
  136. if ( (pEntity->entindex() == 0) && (iHitbox != 0) )
  137. {
  138. // Special case for world entity with hitbox (that's a static prop)
  139. ICollideable *pCollideable = staticpropmgr->GetStaticPropByIndex( iHitbox - 1 );
  140. enginetrace->ClipRayToCollideable( ray, MASK_SHOT, pCollideable, &tr );
  141. }
  142. else
  143. {
  144. if ( !pEntity )
  145. return false;
  146. enginetrace->ClipRayToEntity( ray, MASK_SHOT, pEntity, &tr );
  147. }
  148. }
  149. // If we found the surface, emit debris flecks
  150. bool bReportRagdollImpacts = (nFlags & IMPACT_REPORT_RAGDOLL_IMPACTS) != 0;
  151. if ( ( tr.fraction == 1.0f ) || ( bHitRagdoll && !bReportRagdollImpacts ) )
  152. return false;
  153. return true;
  154. }
  155. //-----------------------------------------------------------------------------
  156. // Purpose:
  157. //-----------------------------------------------------------------------------
  158. char const *GetImpactDecal( C_BaseEntity *pEntity, int iMaterial, int iDamageType )
  159. {
  160. char const *decalName;
  161. if ( !pEntity )
  162. {
  163. decalName = "Impact.Concrete";
  164. }
  165. else
  166. {
  167. decalName = pEntity->DamageDecal( iDamageType, iMaterial );
  168. }
  169. // See if we need to offset the decal for material type
  170. return decalsystem->TranslateDecalForGameMaterial( decalName, iMaterial );
  171. }
  172. //-----------------------------------------------------------------------------
  173. // Purpose: Perform custom effects based on the Decal index
  174. //-----------------------------------------------------------------------------
  175. static ConVar cl_new_impact_effects( "cl_new_impact_effects", "0" );
  176. struct ImpactEffect_t
  177. {
  178. const char *m_pName;
  179. const char *m_pNameNoFlecks;
  180. };
  181. static ImpactEffect_t s_pImpactEffect[26] =
  182. {
  183. { "impact_antlion", NULL }, // CHAR_TEX_ANTLION
  184. { NULL, NULL }, // CHAR_TEX_BLOODYFLESH
  185. { "impact_concrete", "impact_concrete_noflecks" }, // CHAR_TEX_CONCRETE
  186. { "impact_dirt", NULL }, // CHAR_TEX_DIRT
  187. { NULL, NULL }, // CHAR_TEX_EGGSHELL
  188. { NULL, NULL }, // CHAR_TEX_FLESH
  189. { NULL, NULL }, // CHAR_TEX_GRATE
  190. { NULL, NULL }, // CHAR_TEX_ALIENFLESH
  191. { NULL, NULL }, // CHAR_TEX_CLIP
  192. { NULL, NULL }, // CHAR_TEX_UNUSED
  193. { NULL, NULL }, // CHAR_TEX_UNUSED
  194. { NULL, NULL }, // CHAR_TEX_PLASTIC
  195. { "impact_metal", NULL }, // CHAR_TEX_METAL
  196. { "impact_dirt", NULL }, // CHAR_TEX_SAND
  197. { NULL, NULL }, // CHAR_TEX_FOLIAGE
  198. { "impact_computer", NULL }, // CHAR_TEX_COMPUTER
  199. { NULL, NULL }, // CHAR_TEX_UNUSED
  200. { NULL, NULL }, // CHAR_TEX_UNUSED
  201. { NULL, NULL }, // CHAR_TEX_SLOSH
  202. { "impact_concrete", "impact_concrete_noflecks" }, // CHAR_TEX_TILE
  203. { NULL, NULL }, // CHAR_TEX_UNUSED
  204. { "impact_metal", NULL }, // CHAR_TEX_VENT
  205. { "impact_wood", "impact_wood_noflecks" }, // CHAR_TEX_WOOD
  206. { NULL, NULL }, // CHAR_TEX_UNUSED
  207. { "impact_glass", NULL }, // CHAR_TEX_GLASS
  208. { "warp_shield_impact", NULL }, // CHAR_TEX_WARPSHIELD
  209. };
  210. static void SetImpactControlPoint( CNewParticleEffect *pEffect, int nPoint, const Vector &vecImpactPoint, const Vector &vecForward, C_BaseEntity *pEntity )
  211. {
  212. Vector vecImpactY, vecImpactZ;
  213. VectorVectors( vecForward, vecImpactY, vecImpactZ );
  214. vecImpactY *= -1.0f;
  215. pEffect->SetControlPoint( nPoint, vecImpactPoint );
  216. pEffect->SetControlPointOrientation( nPoint, vecForward, vecImpactY, vecImpactZ );
  217. pEffect->SetControlPointEntity( nPoint, pEntity );
  218. }
  219. static void PerformNewCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags )
  220. {
  221. bool bNoFlecks = !r_drawflecks.GetBool();
  222. if ( !bNoFlecks )
  223. {
  224. bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 );
  225. }
  226. // Compute the impact effect name
  227. const ImpactEffect_t &effect = s_pImpactEffect[ iMaterial - 'A' ];
  228. const char *pImpactName = effect.m_pName;
  229. if ( bNoFlecks && effect.m_pNameNoFlecks )
  230. {
  231. pImpactName = effect.m_pNameNoFlecks;
  232. }
  233. if ( !pImpactName )
  234. return;
  235. CSmartPtr<CNewParticleEffect> pEffect = CNewParticleEffect::Create( NULL, pImpactName );
  236. if ( !pEffect->IsValid() )
  237. return;
  238. Vector vecReflect;
  239. float flDot = DotProduct( shotDir, tr.plane.normal );
  240. VectorMA( shotDir, -2.0f * flDot, tr.plane.normal, vecReflect );
  241. Vector vecShotBackward;
  242. VectorMultiply( shotDir, -1.0f, vecShotBackward );
  243. Vector vecImpactPoint = ( tr.fraction != 1.0f ) ? tr.endpos : vecOrigin;
  244. Assert( VectorsAreEqual( vecOrigin, tr.endpos, 1e-1 ) );
  245. SetImpactControlPoint( pEffect.GetObject(), 0, vecImpactPoint, tr.plane.normal, tr.m_pEnt );
  246. SetImpactControlPoint( pEffect.GetObject(), 1, vecImpactPoint, vecReflect, tr.m_pEnt );
  247. SetImpactControlPoint( pEffect.GetObject(), 2, vecImpactPoint, vecShotBackward, tr.m_pEnt );
  248. pEffect->SetControlPoint( 3, Vector( iScale, iScale, iScale ) );
  249. if ( pEffect->m_pDef->ReadsControlPoint( 4 ) )
  250. {
  251. Vector vecColor;
  252. GetColorForSurface( &tr, &vecColor );
  253. pEffect->SetControlPoint( 4, vecColor );
  254. }
  255. }
  256. void PerformCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags )
  257. {
  258. // Throw out the effect if any of these are true
  259. if ( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) )
  260. return;
  261. if ( cl_new_impact_effects.GetInt() )
  262. {
  263. PerformNewCustomEffects( vecOrigin, tr, shotDir, iMaterial, iScale, nFlags );
  264. return;
  265. }
  266. bool bNoFlecks = !r_drawflecks.GetBool();
  267. if ( !bNoFlecks )
  268. {
  269. bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 );
  270. }
  271. // Cement and wood have dust and flecks
  272. if ( ( iMaterial == CHAR_TEX_CONCRETE ) || ( iMaterial == CHAR_TEX_TILE ) )
  273. {
  274. FX_DebrisFlecks( vecOrigin, &tr, iMaterial, iScale, bNoFlecks );
  275. }
  276. else if ( iMaterial == CHAR_TEX_WOOD )
  277. {
  278. FX_DebrisFlecks( vecOrigin, &tr, iMaterial, iScale, bNoFlecks );
  279. }
  280. else if ( ( iMaterial == CHAR_TEX_DIRT ) || ( iMaterial == CHAR_TEX_SAND ) )
  281. {
  282. FX_DustImpact( vecOrigin, &tr, iScale );
  283. }
  284. else if ( iMaterial == CHAR_TEX_ANTLION )
  285. {
  286. FX_AntlionImpact( vecOrigin, &tr );
  287. }
  288. else if ( ( iMaterial == CHAR_TEX_METAL ) || ( iMaterial == CHAR_TEX_VENT ) )
  289. {
  290. Vector reflect;
  291. float dot = shotDir.Dot( tr.plane.normal );
  292. reflect = shotDir + ( tr.plane.normal * ( dot*-2.0f ) );
  293. reflect[0] += random->RandomFloat( -0.2f, 0.2f );
  294. reflect[1] += random->RandomFloat( -0.2f, 0.2f );
  295. reflect[2] += random->RandomFloat( -0.2f, 0.2f );
  296. FX_MetalSpark( vecOrigin, reflect, tr.plane.normal, iScale );
  297. }
  298. else if ( iMaterial == CHAR_TEX_COMPUTER )
  299. {
  300. Vector offset = vecOrigin + ( tr.plane.normal * 1.0f );
  301. g_pEffects->Sparks( offset );
  302. }
  303. else if ( iMaterial == CHAR_TEX_WARPSHIELD )
  304. {
  305. QAngle vecAngles;
  306. VectorAngles( -shotDir, vecAngles );
  307. DispatchParticleEffect( "warp_shield_impact", vecOrigin, vecAngles );
  308. }
  309. }
  310. //-----------------------------------------------------------------------------
  311. // Purpose: Play a sound for an impact. If tr contains a valid hit, use that.
  312. // If not, use the passed in origin & surface.
  313. //-----------------------------------------------------------------------------
  314. void PlayImpactSound( CBaseEntity *pEntity, trace_t &tr, Vector &vecServerOrigin, int nServerSurfaceProp )
  315. {
  316. VPROF( "PlayImpactSound" );
  317. surfacedata_t *pdata;
  318. Vector vecOrigin;
  319. // If the client-side trace hit a different entity than the server, or
  320. // the server didn't specify a surfaceprop, then use the client-side trace
  321. // material if it's valid.
  322. if ( tr.DidHit() && (pEntity != tr.m_pEnt || nServerSurfaceProp == 0) )
  323. {
  324. nServerSurfaceProp = tr.surface.surfaceProps;
  325. }
  326. pdata = physprops->GetSurfaceData( nServerSurfaceProp );
  327. if ( tr.fraction < 1.0 )
  328. {
  329. vecOrigin = tr.endpos;
  330. }
  331. else
  332. {
  333. vecOrigin = vecServerOrigin;
  334. }
  335. // Now play the esound
  336. if ( pdata->sounds.bulletImpact )
  337. {
  338. const char *pbulletImpactSoundName = physprops->GetString( pdata->sounds.bulletImpact );
  339. if ( g_pImpactSoundRouteFn )
  340. {
  341. g_pImpactSoundRouteFn( pbulletImpactSoundName, vecOrigin );
  342. }
  343. else
  344. {
  345. CLocalPlayerFilter filter;
  346. C_BaseEntity::EmitSound( filter, NULL, pbulletImpactSoundName, pdata->soundhandles.bulletImpact, &vecOrigin );
  347. }
  348. return;
  349. }
  350. #ifdef _DEBUG
  351. Msg("***ERROR: PlayImpactSound() on a surface with 0 bulletImpactCount!\n");
  352. #endif //_DEBUG
  353. }
  354. void SetImpactSoundRoute( ImpactSoundRouteFn fn )
  355. {
  356. g_pImpactSoundRouteFn = fn;
  357. }
  358. //-----------------------------------------------------------------------------
  359. // Purpose: Pull the impact data out
  360. // Input : &data -
  361. // *vecOrigin -
  362. // *vecAngles -
  363. // *iMaterial -
  364. // *iDamageType -
  365. // *iHitbox -
  366. // *iEntIndex -
  367. //-----------------------------------------------------------------------------
  368. C_BaseEntity *ParseImpactData( const CEffectData &data, Vector *vecOrigin, Vector *vecStart,
  369. Vector *vecShotDir, short &nSurfaceProp, int &iMaterial, int &iDamageType, int &iHitbox )
  370. {
  371. C_BaseEntity *pEntity = data.GetEntity( );
  372. *vecOrigin = data.m_vOrigin;
  373. *vecStart = data.m_vStart;
  374. nSurfaceProp = data.m_nSurfaceProp;
  375. iDamageType = data.m_nDamageType;
  376. iHitbox = data.m_nHitBox;
  377. *vecShotDir = (*vecOrigin - *vecStart);
  378. VectorNormalize( *vecShotDir );
  379. // Get the material from the surfaceprop
  380. surfacedata_t *psurfaceData = physprops->GetSurfaceData( data.m_nSurfaceProp );
  381. iMaterial = psurfaceData->game.material;
  382. return pEntity;
  383. }