Counter Strike : Global Offensive Source Code
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.

519 lines
16 KiB

  1. //===== Copyright � 1996-2005, 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. #include "engine/IStaticPropMgr.h"
  14. #include "datacache/imdlcache.h"
  15. #include "debugoverlay_shared.h"
  16. #include "c_impact_effects.h"
  17. #include "tier0/vprof.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. static ConVar r_drawflecks( "r_drawflecks", "1" );
  21. static ConVar r_impacts_alt_orientation ( "r_impacts_alt_orientation", "1" );
  22. extern ConVar r_drawmodeldecals;
  23. ImpactSoundRouteFn g_pImpactSoundRouteFn = NULL;
  24. //==========================================================================================================================
  25. // RAGDOLL ENUMERATOR
  26. //==========================================================================================================================
  27. CRagdollEnumerator::CRagdollEnumerator( Ray_t& shot, int iDamageType )
  28. {
  29. m_rayShot = shot;
  30. m_iDamageType = iDamageType;
  31. m_bHit = false;
  32. }
  33. IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity )
  34. {
  35. C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() );
  36. if ( pEnt == NULL )
  37. return ITERATION_CONTINUE;
  38. C_BaseAnimating *pModel = static_cast< C_BaseAnimating * >( pEnt );
  39. // If the ragdoll was created on this tick, then the forces were already applied on the server
  40. if ( pModel == NULL || WasRagdollCreatedOnCurrentTick( pEnt ) )
  41. return ITERATION_CONTINUE;
  42. IPhysicsObject *pPhysicsObject = pModel->VPhysicsGetObject();
  43. if ( pPhysicsObject == NULL )
  44. return ITERATION_CONTINUE;
  45. trace_t tr;
  46. enginetrace->ClipRayToEntity( m_rayShot, MASK_SHOT, pModel, &tr );
  47. if ( tr.fraction < 1.0 )
  48. {
  49. pModel->ImpactTrace( &tr, m_iDamageType, NULL );
  50. m_bHit = true;
  51. //FIXME: Yes? No?
  52. return ITERATION_STOP;
  53. }
  54. return ITERATION_CONTINUE;
  55. }
  56. //-----------------------------------------------------------------------------
  57. // Purpose:
  58. //-----------------------------------------------------------------------------
  59. bool FX_AffectRagdolls( Vector vecOrigin, Vector vecStart, int iDamageType )
  60. {
  61. // don't do this when lots of ragdolls are simulating
  62. if ( s_RagdollLRU.CountRagdolls(true) > 1 )
  63. return false;
  64. Ray_t shotRay;
  65. shotRay.Init( vecStart, vecOrigin );
  66. CRagdollEnumerator ragdollEnum( shotRay, iDamageType );
  67. ::partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum );
  68. return ragdollEnum.Hit();
  69. }
  70. //-----------------------------------------------------------------------------
  71. // Purpose:
  72. // Input : &data -
  73. //-----------------------------------------------------------------------------
  74. void RagdollImpactCallback( const CEffectData &data )
  75. {
  76. FX_AffectRagdolls( data.m_vOrigin, data.m_vStart, data.m_nDamageType );
  77. }
  78. DECLARE_CLIENT_EFFECT( RagdollImpact, RagdollImpactCallback );
  79. //-----------------------------------------------------------------------------
  80. // Purpose:
  81. //-----------------------------------------------------------------------------
  82. bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType, int iHitbox, C_BaseEntity *pEntity, trace_t &tr, int nFlags, int maxLODToDecal )
  83. {
  84. VPROF( "Impact" );
  85. Assert ( pEntity );
  86. MDLCACHE_CRITICAL_SECTION();
  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. int decalNumber = decalsystem->GetDecalIndexForName( GetImpactDecal( pEntity, iMaterial, iDamageType ) );
  104. if ( decalNumber == -1 )
  105. return false;
  106. if ( (pEntity->entindex() == 0) && (iHitbox != 0) )
  107. {
  108. staticpropmgr->AddDecalToStaticProp( vecStart, traceExt, iHitbox - 1, decalNumber, true, tr );
  109. }
  110. else if ( pEntity )
  111. {
  112. // Here we deal with decals on entities.
  113. pEntity->AddDecal( vecStart, traceExt, vecOrigin, iHitbox, decalNumber, true, tr, maxLODToDecal );
  114. }
  115. }
  116. else
  117. {
  118. // Perform the trace ourselves
  119. Ray_t ray;
  120. ray.Init( vecStart, traceExt );
  121. if ( (pEntity->entindex() == 0) && (iHitbox != 0) )
  122. {
  123. // Special case for world entity with hitbox (that's a static prop)
  124. ICollideable *pCollideable = staticpropmgr->GetStaticPropByIndex( iHitbox - 1 );
  125. enginetrace->ClipRayToCollideable( ray, MASK_SHOT, pCollideable, &tr );
  126. }
  127. else
  128. {
  129. if ( !pEntity )
  130. return false;
  131. enginetrace->ClipRayToEntity( ray, MASK_SHOT, pEntity, &tr );
  132. }
  133. }
  134. // If we found the surface, emit debris flecks
  135. bool bReportRagdollImpacts = (nFlags & IMPACT_REPORT_RAGDOLL_IMPACTS) != 0;
  136. if ( ( tr.fraction == 1.0f ) || ( bHitRagdoll && !bReportRagdollImpacts ) )
  137. return false;
  138. return true;
  139. }
  140. //-----------------------------------------------------------------------------
  141. // Purpose:
  142. //-----------------------------------------------------------------------------
  143. char const *GetImpactDecal( C_BaseEntity *pEntity, int iMaterial, int iDamageType )
  144. {
  145. char const *decalName;
  146. if ( !pEntity )
  147. {
  148. decalName = "Impact.Concrete";
  149. }
  150. else
  151. {
  152. decalName = pEntity->DamageDecal( iDamageType, iMaterial );
  153. }
  154. // See if we need to offset the decal for material type
  155. return decalsystem->TranslateDecalForGameMaterial( decalName, iMaterial );
  156. }
  157. //-----------------------------------------------------------------------------
  158. // Purpose: Perform custom effects based on the Decal index
  159. //-----------------------------------------------------------------------------
  160. struct ImpactEffect_t
  161. {
  162. const char *m_pName;
  163. const char *m_pNameNoFlecks;
  164. };
  165. static ImpactEffect_t s_pImpactEffect[26] =
  166. {
  167. #ifndef DOTA_DLL
  168. { NULL, NULL }, // CHAR_TEX_ANTLION
  169. { NULL, NULL }, // CHAR_TEX_BLOODYFLESH
  170. { "impact_concrete", "impact_concrete" }, // CHAR_TEX_CONCRETE
  171. { "impact_dirt", "impact_dirt" }, // CHAR_TEX_DIRT
  172. { NULL, NULL }, // CHAR_TEX_EGGSHELL
  173. { NULL, NULL }, // CHAR_TEX_FLESH
  174. { "impact_metal", "impact_metal" }, // CHAR_TEX_GRATE
  175. { NULL, NULL }, // CHAR_TEX_ALIENFLESH
  176. { NULL, NULL }, // CHAR_TEX_CLIP
  177. { "impact_grass", "impact_grass" }, // CHAR_TEX_GRASS
  178. { "impact_snow", "impact_snow" }, // CHAR_TEX_SNOW
  179. { "impact_plastic", "impact_plastic" }, // CHAR_TEX_PLASTIC
  180. { "impact_metal", "impact_metal" }, // CHAR_TEX_METAL
  181. { "impact_sand", "impact_sand" }, // CHAR_TEX_SAND
  182. { "impact_leaves", "impact_leaves" }, // CHAR_TEX_FOLIAGE
  183. { "impact_computer", "impact_computer" }, // CHAR_TEX_COMPUTER
  184. { "impact_asphalt", "impact_asphalt" }, // CHAR_TEX_ASPHALT
  185. { "impact_brick", "impact_brick" }, // CHAR_TEX_BRICK
  186. { "impact_wet", "impact_wet" }, // CHAR_TEX_SLOSH
  187. { "impact_tile", "impact_tile" }, // CHAR_TEX_TILE
  188. { "impact_cardboard", "impact_cardboard" }, // CHAR_TEX_CARDBOARD
  189. { "impact_metal", "impact_metal" }, // CHAR_TEX_VENT
  190. { "impact_wood", "impact_wood" }, // CHAR_TEX_WOOD
  191. { NULL, NULL }, // CHAR_TEX_FAKE
  192. { "impact_glass", "impact_glass" }, // CHAR_TEX_GLASS
  193. { NULL, NULL }, // CHAR_TEX_WARPSHIELD
  194. #endif
  195. };
  196. static ImpactEffect_t s_pImpactEffect2[12] =
  197. {
  198. #ifndef DOTA_DLL
  199. { "impact_clay", "impact_clay" }, // CHAR_TEX_CLAY
  200. { "impact_plaster", "impact_plaster" }, // CHAR_TEX_PLASTER
  201. { "impact_rock", "impact_rock" }, // CHAR_TEX_ROCK
  202. { "impact_rubber", "impact_rubber" }, // CHAR_TEX_RUBBER
  203. { "impact_sheetrock", "impact_sheetrock" }, // CHAR_TEX_SHEETROCK
  204. { "impact_cloth", "impact_cloth" }, // CHAR_TEX_CLOTH
  205. { "impact_carpet", "impact_carpet" }, // CHAR_TEX_CARPET
  206. { "impact_paper", "impact_paper" }, // CHAR_TEX_PAPER
  207. { "impact_upholstery", "impact_upholstery" }, // CHAR_TEX_UPHOLSTERY
  208. { "impact_puddle", "impact_puddle" }, // CHAR_TEX_PUDDLE
  209. { "impact_mud", "impact_mud" }, // CHAR_TEX_MUD
  210. { "impact_sandbarrel", "impact_sandbarrel" }, // CHAR_TEX_SANDBARREL
  211. #endif
  212. };
  213. static int s_pImpactEffectIndex[ ARRAYSIZE( s_pImpactEffect ) ][2];
  214. static int s_pImpactEffect2Index[ ARRAYSIZE( s_pImpactEffect2 ) ][2];
  215. PRECACHE_REGISTER_BEGIN( GLOBAL, PrecacheImpacts )
  216. for ( int i = 0; i < ARRAYSIZE( s_pImpactEffect ); ++i )
  217. {
  218. if ( s_pImpactEffect[i].m_pName )
  219. {
  220. PRECACHE_INDEX( PARTICLE_SYSTEM, s_pImpactEffect[i].m_pName, s_pImpactEffectIndex[i][0] );
  221. }
  222. if ( s_pImpactEffect[i].m_pNameNoFlecks )
  223. {
  224. PRECACHE_INDEX( PARTICLE_SYSTEM, s_pImpactEffect[i].m_pNameNoFlecks, s_pImpactEffectIndex[i][1] );
  225. }
  226. }
  227. for ( int i = 0; i < ARRAYSIZE( s_pImpactEffect2 ); ++i )
  228. {
  229. if ( s_pImpactEffect2[i].m_pName )
  230. {
  231. PRECACHE_INDEX( PARTICLE_SYSTEM, s_pImpactEffect2[i].m_pName, s_pImpactEffect2Index[i][0] );
  232. }
  233. if ( s_pImpactEffect2[i].m_pNameNoFlecks )
  234. {
  235. PRECACHE_INDEX( PARTICLE_SYSTEM, s_pImpactEffect2[i].m_pNameNoFlecks, s_pImpactEffect2Index[i][1] );
  236. }
  237. }
  238. PRECACHE_REGISTER_END()
  239. static void SetImpactControlPoint( CNewParticleEffect *pEffect, int nPoint, const Vector &vecImpactPoint, const Vector &vecForward, C_BaseEntity *pEntity )
  240. {
  241. Vector vecImpactY, vecImpactZ;
  242. VectorVectors( vecForward, vecImpactY, vecImpactZ );
  243. vecImpactY *= -1.0f;
  244. pEffect->SetControlPoint( nPoint, vecImpactPoint );
  245. if ( r_impacts_alt_orientation.GetBool() )
  246. pEffect->SetControlPointOrientation( nPoint, vecImpactZ, vecImpactY, vecForward );
  247. else
  248. pEffect->SetControlPointOrientation( nPoint, vecForward, vecImpactY, vecImpactZ );
  249. pEffect->SetControlPointEntity( nPoint, pEntity );
  250. }
  251. static void PerformNewCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags )
  252. {
  253. bool bNoFlecks = !r_drawflecks.GetBool();
  254. if ( !bNoFlecks )
  255. {
  256. bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0 );
  257. }
  258. // Compute the impact effect name
  259. ImpactEffect_t *pEffectList;
  260. int *pEffectIndex;
  261. int nOffset;
  262. if ( iMaterial >= FIRST_L4D_CHAR_TEX && iMaterial <= LAST_L4D_CHAR_TEX )
  263. {
  264. pEffectList = s_pImpactEffect2;
  265. nOffset = 1;
  266. pEffectIndex = s_pImpactEffect2Index[iMaterial - nOffset];
  267. }
  268. else if ( ( iMaterial >= FIRST_CHAR_TEX ) && ( iMaterial <= LAST_CHAR_TEX ) )
  269. {
  270. pEffectList = s_pImpactEffect;
  271. nOffset = FIRST_CHAR_TEX;
  272. pEffectIndex = s_pImpactEffectIndex[iMaterial - nOffset];
  273. }
  274. else
  275. {
  276. DevMsg( "Invalid surface property. Double-check surfaceproperties_manifest.txt\n" );
  277. return;
  278. }
  279. const ImpactEffect_t &effect = pEffectList[ iMaterial - nOffset ];
  280. const char *pImpactName = effect.m_pName;
  281. int nEffectIndex = pEffectIndex[0];
  282. if ( bNoFlecks && effect.m_pNameNoFlecks )
  283. {
  284. pImpactName = effect.m_pNameNoFlecks;
  285. nEffectIndex = pEffectIndex[1];
  286. }
  287. if ( !pImpactName )
  288. return;
  289. Vector vecReflect;
  290. float flDot = DotProduct( shotDir, tr.plane.normal );
  291. VectorMA( shotDir, -2.0f * flDot, tr.plane.normal, vecReflect );
  292. Vector vecShotBackward;
  293. VectorMultiply( shotDir, -1.0f, vecShotBackward );
  294. Vector vecImpactPoint = ( tr.fraction != 1.0f ) ? tr.endpos : vecOrigin;
  295. #ifdef _DEBUG
  296. if(!VectorsAreEqual( vecOrigin, tr.endpos, 1e-1 ))
  297. {
  298. Warning("Impact decal drawn too far from the surface impacted." );
  299. }
  300. #endif
  301. CSmartPtr<CNewParticleEffect> pEffect = CNewParticleEffect::CreateOrAggregatePrecached( NULL, nEffectIndex, vecImpactPoint );
  302. if ( !pEffect->IsValid() )
  303. return;
  304. SetImpactControlPoint( pEffect.GetObject(), 0, vecImpactPoint, tr.plane.normal, tr.m_pEnt );
  305. SetImpactControlPoint( pEffect.GetObject(), 1, vecImpactPoint, vecReflect, tr.m_pEnt );
  306. SetImpactControlPoint( pEffect.GetObject(), 2, vecImpactPoint, vecShotBackward, tr.m_pEnt );
  307. pEffect->SetControlPoint( 3, Vector( iScale, iScale, iScale ) );
  308. if ( pEffect->m_pDef->ReadsControlPoint( 4 ) )
  309. {
  310. Vector vecColor;
  311. GetColorForSurface( &tr, &vecColor );
  312. pEffect->SetControlPoint( 4, vecColor );
  313. }
  314. }
  315. void PerformCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags )
  316. {
  317. // Throw out the effect if any of these are true
  318. const int noEffectsFlags = (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP);
  319. if ( tr.surface.flags & noEffectsFlags )
  320. return;
  321. PerformNewCustomEffects( vecOrigin, tr, shotDir, iMaterial, iScale, nFlags );
  322. }
  323. //-----------------------------------------------------------------------------
  324. // Purpose: Play a sound for an impact. If tr contains a valid hit, use that.
  325. // If not, use the passed in origin & surface.
  326. //-----------------------------------------------------------------------------
  327. void PlayImpactSound( CBaseEntity *pEntity, trace_t &tr, Vector &vecServerOrigin, int nServerSurfaceProp )
  328. {
  329. VPROF( "PlayImpactSound" );
  330. surfacedata_t *pdata;
  331. Vector vecOrigin;
  332. if (pEntity->IsDormant())
  333. return;
  334. // If the client-side trace hit a different entity than the server, or
  335. // the server didn't specify a surfaceprop, then use the client-side trace
  336. // material if it's valid.
  337. if ( tr.DidHit() && (pEntity != tr.m_pEnt || nServerSurfaceProp == 0) )
  338. {
  339. nServerSurfaceProp = tr.surface.surfaceProps;
  340. }
  341. pdata = physprops->GetSurfaceData( nServerSurfaceProp );
  342. if ( tr.fraction < 1.0 )
  343. {
  344. vecOrigin = tr.endpos;
  345. }
  346. else
  347. {
  348. vecOrigin = vecServerOrigin;
  349. }
  350. // Now play the esound
  351. if ( pdata->sounds.bulletImpact )
  352. {
  353. const char *pbulletImpactSoundName = physprops->GetString( pdata->sounds.bulletImpact );
  354. if ( g_pImpactSoundRouteFn )
  355. {
  356. g_pImpactSoundRouteFn( pbulletImpactSoundName, vecOrigin );
  357. }
  358. else
  359. {
  360. CLocalPlayerFilter filter;
  361. C_BaseEntity::EmitSound( filter, NULL, pbulletImpactSoundName, pdata->soundhandles.bulletImpact, &vecOrigin );
  362. }
  363. #if defined( CSTRIKE15 )
  364. // play a ricochet based on the material
  365. float flRicoChance = 0.0f;
  366. switch( pdata->game.material )
  367. {
  368. case CHAR_TEX_METAL:
  369. case CHAR_TEX_CONCRETE:
  370. case CHAR_TEX_COMPUTER:
  371. case CHAR_TEX_BRICK:
  372. case CHAR_TEX_TILE:
  373. case CHAR_TEX_ROCK:
  374. case CHAR_TEX_ASPHALT:
  375. flRicoChance = 5.0;
  376. break;
  377. case CHAR_TEX_GRATE:
  378. case CHAR_TEX_VENT:
  379. case CHAR_TEX_WOOD:
  380. case CHAR_TEX_RUBBER:
  381. case CHAR_TEX_SHEETROCK:
  382. case CHAR_TEX_CARPET:
  383. flRicoChance = 3.0;
  384. break;
  385. case CHAR_TEX_DIRT:
  386. case CHAR_TEX_PLASTIC:
  387. case CHAR_TEX_CLAY:
  388. case CHAR_TEX_PLASTER:
  389. flRicoChance = 1.0;
  390. break;
  391. }
  392. if ( RandomFloat(0, 10) <= flRicoChance )
  393. {
  394. FX_RicochetSound( vecOrigin );
  395. }
  396. #endif
  397. return;
  398. }
  399. #ifdef _DEBUG
  400. Msg("***ERROR: PlayImpactSound() on a surface with 0 bulletImpactCount!\n");
  401. #endif //_DEBUG
  402. }
  403. void SetImpactSoundRoute( ImpactSoundRouteFn fn )
  404. {
  405. g_pImpactSoundRouteFn = fn;
  406. }
  407. //-----------------------------------------------------------------------------
  408. // Purpose: Pull the impact data out
  409. // Input : &data -
  410. // *vecOrigin -
  411. // *vecAngles -
  412. // *iMaterial -
  413. // *iDamageType -
  414. // *iHitbox -
  415. // *iEntIndex -
  416. //-----------------------------------------------------------------------------
  417. C_BaseEntity *ParseImpactData( const CEffectData &data, Vector *vecOrigin, Vector *vecStart,
  418. Vector *vecShotDir, short &nSurfaceProp, int &iMaterial, int &iDamageType, int &iHitbox )
  419. {
  420. C_BaseEntity *pEntity = data.GetEntity( );
  421. if ( data.m_bPositionsAreRelativeToEntity && pEntity )
  422. {
  423. *vecOrigin = data.m_vOrigin + pEntity->GetAbsOrigin();
  424. *vecStart = data.m_vStart + pEntity->GetAbsOrigin();
  425. }
  426. else
  427. {
  428. *vecOrigin = data.m_vOrigin;
  429. *vecStart = data.m_vStart;
  430. }
  431. nSurfaceProp = data.m_nSurfaceProp;
  432. iDamageType = data.m_nDamageType;
  433. iHitbox = data.m_nHitBox;
  434. *vecShotDir = (*vecOrigin - *vecStart);
  435. VectorNormalize( *vecShotDir );
  436. // Get the material from the surfaceprop
  437. surfacedata_t *psurfaceData = physprops->GetSurfaceData( data.m_nSurfaceProp );
  438. iMaterial = psurfaceData->game.material;
  439. return pEntity;
  440. }