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.

563 lines
17 KiB

  1. //========= Copyright � 1996-2009, Valve Corporation, All rights reserved. ============//
  2. //
  3. //=============================================================================//
  4. #include "cbase.h"
  5. #include <numeric>
  6. #include "paint_stream_manager.h"
  7. #include "paint_blobs_shared.h"
  8. #include "paint_sprayer_shared.h"
  9. #include "debugoverlay_shared.h"
  10. #include "fmtstr.h"
  11. #include "vprof.h"
  12. #include "paint_stream_shared.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. #define VPROF_BUDGETGROUP_PAINTBLOB _T("Paintblob")
  16. const char* const CPaintStreamManager::m_pPaintMaterialNames[PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER] =
  17. {
  18. "paintblobs/blob_surface_bounce",
  19. "paintblobs/blob_surface_stick", // FIXME: Bring this back for DLC2 "paintblobs/blob_surface_reflect",
  20. "paintblobs/blob_surface_speed",
  21. "paintblobs/blob_surface_portal",
  22. "paintblobs/blob_surface_erase"
  23. };
  24. const char* const CPaintStreamManager::s_SoundEffectNames[PAINT_IMPACT_EFFECT_COUNT] =
  25. {
  26. "Paintblob.Impact",
  27. "Paintblob.ImpactDrip"
  28. };
  29. //Paint particle impact effect convars
  30. ConVar paint_impact_particles_distance_threshold( "paint_impact_particles_distance_threshold", "20.0f", FCVAR_REPLICATED );
  31. ConVar paint_impact_particles_duration( "paint_impact_particles_duration", "0.2f", FCVAR_REPLICATED );
  32. ConVar paint_min_impact_particles( "paint_min_impact_particles", "20", FCVAR_REPLICATED );
  33. ConVar paint_max_impact_particles( "paint_max_impact_particles", "50", FCVAR_REPLICATED );
  34. ConVar paint_impact_accumulate_sound_distance_threshold( "paint_impact_accumulate_sound_distance_threshold", "128.0f", FCVAR_REPLICATED );
  35. ConVar paint_impact_count_to_max_adjusted_volume( "paint_impact_count_to_max_adjusted_volume", "5", FCVAR_REPLICATED );
  36. ConVar paint_impact_count_to_min_adjusted_pitch_after_full_volume( "paint_impact_count_to_min_adjusted_pitch_after_full_volume", "5", FCVAR_REPLICATED );
  37. ConVar min_adjusted_pitch_percentage( "min_adjusted_pitch_percentage", "0.85", FCVAR_REPLICATED );
  38. ConVar draw_paint_splat_particles( "draw_paint_splat_particles", "1", FCVAR_REPLICATED );
  39. ConVar group_paint_impact_effects( "cl_group_paint_impact_effects", "1", FCVAR_REPLICATED );
  40. ConVar debug_paint_impact_effects( "debug_paint_impact_effects", "0", FCVAR_REPLICATED );
  41. ConVar blobs_paused("blobs_paused", "0", FCVAR_CHEAT | FCVAR_REPLICATED );
  42. CPaintStreamManager PaintStreamManager( "PaintStreamManager" );
  43. CPaintStreamManager::CPaintStreamManager( char const *name )
  44. : CAutoGameSystemPerFrame( name )
  45. {
  46. }
  47. CPaintStreamManager::~CPaintStreamManager( void )
  48. {
  49. }
  50. void CPaintStreamManager::LevelInitPreEntity()
  51. {
  52. blobs_paused.SetValue( true );
  53. }
  54. void CPaintStreamManager::LevelInitPostEntity()
  55. {
  56. blobs_paused.SetValue( false );
  57. }
  58. void CPaintStreamManager::LevelShutdownPostEntity()
  59. {
  60. m_PaintImpactParticles.RemoveAll();
  61. if ( m_pBlobPool )
  62. {
  63. m_pBlobPool->Clear();
  64. delete m_pBlobPool;
  65. m_pBlobPool = NULL;
  66. }
  67. }
  68. void CPaintStreamManager::AllocatePaintBlobPool( int nMaxBlobs )
  69. {
  70. int nMaxCount = ( nMaxBlobs ) ? nMaxBlobs : 250;
  71. // pre-allocate pool of blobs
  72. if ( !m_pBlobPool )
  73. {
  74. #ifdef GAME_DLL
  75. m_pBlobPool = new CClassMemoryPool< CPaintBlob >( nMaxCount, CUtlMemoryPool::GROW_NONE );
  76. #else
  77. if ( !engine->IsClientLocalToActiveServer() )
  78. {
  79. m_pBlobPool = new CClassMemoryPool< CPaintBlob >( nMaxCount, CUtlMemoryPool::GROW_NONE );
  80. }
  81. #endif
  82. }
  83. else if ( ( m_pBlobPool->Size() / m_pBlobPool->BlockSize() ) != nMaxBlobs )
  84. {
  85. Assert( 0 );
  86. Warning( "CPaintStreamManager::AllocatePaintBlobPool is being called multiple times (for some reasons) with different pool sizes." );
  87. }
  88. }
  89. void CPaintStreamManager::RemoveAllPaintBlobs( void )
  90. {
  91. for( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
  92. {
  93. CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
  94. if( pStream )
  95. {
  96. pStream->RemoveAllPaintBlobs();
  97. }
  98. }
  99. }
  100. CPaintBlob* CPaintStreamManager::AllocatePaintBlob( bool bSilent /*= false*/ )
  101. {
  102. // don't create when blob is paused
  103. if ( blobs_paused.GetBool() )
  104. {
  105. return NULL;
  106. }
  107. #ifdef CLIENT_DLL
  108. if ( bSilent )
  109. {
  110. return NULL;
  111. }
  112. #endif
  113. return m_pBlobPool->Alloc();
  114. }
  115. void CPaintStreamManager::FreeBlob( CPaintBlob* pBlob )
  116. {
  117. m_pBlobPool->Free( pBlob );
  118. }
  119. const char *CPaintStreamManager::GetPaintMaterialName( int type )
  120. {
  121. return m_pPaintMaterialNames[ type ];
  122. }
  123. #ifdef CLIENT_DLL
  124. void CPaintStreamManager::Update( float frametime )
  125. {
  126. PaintStreamUpdate();
  127. //Update the particle and sound impact effects
  128. UpdatePaintImpactEffects( frametime, m_PaintImpactParticles );
  129. //Display a list of all the paint impact effects currently playing
  130. if( debug_paint_impact_effects.GetBool() )
  131. {
  132. int line = 6;
  133. float lineHeight = 0.015;
  134. float startX = 0.01f;
  135. float startY = 0.0f;
  136. CFmtStr msg;
  137. msg.sprintf( "Paint blob impact particles: %d", m_PaintImpactParticles.Count() );
  138. NDebugOverlay::ScreenText( startX, startY + (line * lineHeight), msg, 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  139. line++;
  140. }
  141. }
  142. #else
  143. void CPaintStreamManager::PreClientUpdate( void )
  144. {
  145. PaintStreamUpdate();
  146. //engine->Con_NPrintf( 0, "num blobs = %d", GetBlobCount() );
  147. //Update the particle and sound impact effects
  148. UpdatePaintImpactEffects( gpGlobals->frametime, m_PaintImpactParticles );
  149. //Display a list of all the paint impact effects currently playing
  150. if( debug_paint_impact_effects.GetBool() )
  151. {
  152. int line = 6;
  153. float lineHeight = 0.015;
  154. float startX = 0.01f;
  155. float startY = 0.0f;
  156. CFmtStr msg;
  157. msg.sprintf( "Paint blob impact particles: %d", m_PaintImpactParticles.Count() );
  158. NDebugOverlay::ScreenText( startX, startY + (line * lineHeight), msg, 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  159. line++;
  160. }
  161. }
  162. #endif
  163. void CPaintStreamManager::PaintStreamUpdate()
  164. {
  165. VPROF_BUDGET( "CPaintStreamManager::PaintStreamUpdate", VPROF_BUDGETGROUP_PAINTBLOB );
  166. #ifdef CLIENT_DLL
  167. // if the client is local to server, only update render bounds
  168. // let the server do all the work.
  169. if ( engine->IsClientLocalToActiveServer() )
  170. {
  171. for ( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
  172. {
  173. CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
  174. if ( pStream )
  175. {
  176. pStream->UpdateRenderBoundsAndOriginWorldspace();
  177. }
  178. }
  179. return;
  180. }
  181. #endif
  182. // remove dead blobs from beam list before we set dead blobs to NULL
  183. CTrigger_TractorBeam_Shared::RemoveDeadBlobsFromBeams();
  184. // we want to update blob collision for all blobs at once
  185. PaintBlobVector_t allBlobs;
  186. // preupdate (delete blobs from streams)
  187. for ( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
  188. {
  189. CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
  190. if ( pStream )
  191. {
  192. pStream->PreUpdateBlobs();
  193. int numCurrentBlobs = allBlobs.Count();
  194. int numNewBlobs = pStream->GetBlobList().Count();
  195. allBlobs.EnsureCount( allBlobs.Count() + numNewBlobs );
  196. V_memcpy( allBlobs.Base() + numCurrentBlobs, pStream->GetBlobList().Base(), numNewBlobs * sizeof( CBasePaintBlob* ) );
  197. }
  198. }
  199. // copy blobs from all stream and update all of them
  200. if ( blobs_paused.GetBool() )
  201. {
  202. for ( int i=0; i<allBlobs.Count(); ++i )
  203. {
  204. allBlobs[i]->SetLastUpdateTime( gpGlobals->curtime );
  205. }
  206. }
  207. else
  208. {
  209. // update all blobs
  210. PaintBlobUpdate( allBlobs );
  211. }
  212. // post update
  213. for ( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
  214. {
  215. CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
  216. if ( pStream )
  217. {
  218. pStream->PostUpdateBlobs();
  219. }
  220. }
  221. // remove blobs that change beams to correct blob list in beam
  222. CTrigger_TractorBeam_Shared::RemoveBlobsFromPreviousBeams();
  223. }
  224. void CPaintStreamManager::UpdatePaintImpactEffects( float flDeltaTime, PaintBlobImpactEffectVector_t &paintImpactEffects )
  225. {
  226. //Update the paint blob impact effects
  227. for( int i = 0; i < paintImpactEffects.Count(); ++i )
  228. {
  229. PaintBlobImpactEffect_t *pEffect = &paintImpactEffects[i];
  230. if ( pEffect )
  231. {
  232. //Decrement the timer of the effect
  233. pEffect->flTime -= flDeltaTime;
  234. //Remove the effect if it has finished playing
  235. if( pEffect->flTime <= 0.0f )
  236. {
  237. paintImpactEffects.FastRemove( i-- );
  238. }
  239. }
  240. }
  241. }
  242. void CPaintStreamManager::CreatePaintImpactParticles( const Vector &vecPosition, const Vector &vecNormal, int paintType )
  243. {
  244. //Check if the impact particle effect should be played
  245. if( ShouldPlayImpactEffect( vecPosition,
  246. m_PaintImpactParticles,
  247. paint_min_impact_particles.GetInt(),
  248. paint_max_impact_particles.GetInt(),
  249. paint_impact_particles_distance_threshold.GetFloat() * paint_impact_particles_distance_threshold.GetFloat() ) )
  250. {
  251. PlayPaintImpactParticles( vecPosition, vecNormal, paintType );
  252. }
  253. }
  254. bool CPaintStreamManager::ShouldPlayImpactEffect( const Vector& vecPosition, PaintBlobImpactEffectVector_t &paintImpactEffects, int minEffects, int maxEffects, float flDistanceThresholdSqr )
  255. {
  256. if( !group_paint_impact_effects.GetBool() )
  257. {
  258. return true;
  259. }
  260. int iImpactEffectCount = paintImpactEffects.Count();
  261. //If we are below the min threshold then play the paint impact effect
  262. if( iImpactEffectCount < minEffects )
  263. {
  264. return true;
  265. }
  266. //Don't play any paint impact effect if we are above the max
  267. else if( iImpactEffectCount >= maxEffects )
  268. {
  269. return false;
  270. }
  271. int iEffectIndex = 0;
  272. //Don't play the effect if it's too close to another paint impact effect
  273. for ( iEffectIndex = 0; iEffectIndex < iImpactEffectCount; ++iEffectIndex )
  274. {
  275. PaintBlobImpactEffect_t *pEffect = &paintImpactEffects[iEffectIndex];
  276. if ( pEffect )
  277. {
  278. //Check if this effect is too close to a effect already playing
  279. if ( vecPosition.DistToSqr( pEffect->vecPosition ) < ( flDistanceThresholdSqr ) )
  280. {
  281. return false;
  282. }
  283. }
  284. }
  285. //OK to play the effect
  286. return true;
  287. }
  288. struct SplatParticlesForPaint_t
  289. {
  290. int nPaintType;
  291. const char *lpszParticleSystemName;
  292. };
  293. SplatParticlesForPaint_t paintSplatCallbacks[] =
  294. {
  295. { BOUNCE_POWER, "paint_splat_bounce_01" },
  296. { REFLECT_POWER,"paint_splat_stick_01" }, // FIXME: Bring this back for DLC2 { REFLECT_POWER,"paint_splat_reflect_01" },
  297. { SPEED_POWER, "paint_splat_speed_01" },
  298. { PORTAL_POWER, "paint_splat_erase_01" },
  299. { NO_POWER, "paint_splat_erase_01" },
  300. };
  301. void PaintSplatEffect( const Vector& vecPosition, const Vector& vecNormal, int paintType )
  302. {
  303. Assert( paintType >= 0 && paintType < ARRAYSIZE( paintSplatCallbacks ) );
  304. Assert( paintSplatCallbacks[paintType].nPaintType == paintType );
  305. QAngle angle;
  306. VectorAngles( -vecNormal, angle );
  307. CBasePlayer *pPlayer = NULL;
  308. #ifdef GAME_DLL
  309. if ( !engine->IsDedicatedServer() )
  310. {
  311. pPlayer = UTIL_GetLocalPlayerOrListenServerHost();
  312. }
  313. #else
  314. pPlayer = GetSplitScreenViewPlayer();
  315. #endif
  316. if ( pPlayer )
  317. {
  318. CSingleUserRecipientFilter filter( pPlayer );
  319. DispatchParticleEffect( paintSplatCallbacks[paintType].lpszParticleSystemName, vecPosition, angle, NULL, -1, &filter );
  320. }
  321. }
  322. void CPaintStreamManager::PlayPaintImpactParticles( const Vector &vecPosition, const Vector &vecNormal, int paintType )
  323. {
  324. //Play the particle effect for the impact
  325. if( draw_paint_splat_particles.GetBool() )
  326. {
  327. PaintSplatEffect( vecPosition, vecNormal, paintType );
  328. }
  329. //Add the effect to the list
  330. int iEffectIndex = m_PaintImpactParticles.AddToTail();
  331. m_PaintImpactParticles[iEffectIndex].vecPosition = vecPosition;
  332. m_PaintImpactParticles[iEffectIndex].flTime = paint_impact_particles_duration.GetFloat();
  333. }
  334. float CPaintStreamManager::PlayPaintImpactSound( const Vector &vecPosition, PaintImpactEffect impactEffect )
  335. {
  336. //Emit the sound for the impact
  337. #ifdef GAME_DLL
  338. CBasePlayer *pRecipient = UTIL_GetLocalPlayerOrListenServerHost();
  339. if ( pRecipient == NULL )
  340. {
  341. return 0.0f;
  342. }
  343. CSingleUserRecipientFilter filter( pRecipient );
  344. #else
  345. CLocalPlayerFilter filter;
  346. #endif
  347. const char* soundName = GetPaintSoundEffectName( impactEffect );
  348. CBaseEntity::EmitSound( filter, 0, soundName, &vecPosition );
  349. return CBaseEntity::GetSoundDuration( soundName, NULL );
  350. }
  351. typedef CUtlVectorFixedGrowable< Vector, 16 > AccumulatedSoundPositionVector;
  352. struct AccumulatedImpactSound
  353. {
  354. CSoundParameters soundParams;
  355. AccumulatedSoundPositionVector positions;
  356. float volumeIncreasePerImpact;
  357. int pitchDecreasePerFullVolumeImpact;
  358. int minAdjustedPitch;
  359. void Initialize( const Vector& center, const char* soundName );
  360. };
  361. void AccumulatedImpactSound::Initialize( const Vector& center, const char* soundName )
  362. {
  363. positions.AddToTail( center );
  364. if( CBaseEntity::GetParametersForSound( soundName, soundParams, NULL ) )
  365. {
  366. const int impactsToMinPitch = paint_impact_count_to_min_adjusted_pitch_after_full_volume.GetInt();
  367. minAdjustedPitch = min_adjusted_pitch_percentage.GetFloat() * soundParams.pitch + 0.5f;
  368. const int deltaToMinPitch = soundParams.pitch - minAdjustedPitch;
  369. pitchDecreasePerFullVolumeImpact = static_cast<float>(deltaToMinPitch) / impactsToMinPitch + 0.5f;
  370. const int impactsToFullVolume = paint_impact_count_to_max_adjusted_volume.GetInt();
  371. const float deltaToFullVolume = VOL_NORM - soundParams.volume;
  372. volumeIncreasePerImpact = deltaToFullVolume / impactsToFullVolume;
  373. }
  374. else
  375. {
  376. Assert(!"GetParametersForSound() failed.");
  377. }
  378. }
  379. typedef CUtlVectorFixedGrowable< AccumulatedImpactSound, 32 > AccumulatedImpactSoundVector;
  380. void CPaintStreamManager::PlayMultiplePaintImpactSounds( TimeStampVector& channelTimeStamps, int maxChannels, const PaintImpactPositionVector& positions, PaintImpactEffect impactEffect )
  381. {
  382. const char* soundName = GetPaintSoundEffectName( impactEffect );
  383. Assert( positions.Count() > 0 && soundName != NULL );
  384. const int maxChannelsToAdd = imax( maxChannels - channelTimeStamps.Count(), 0 );
  385. if( positions.Count() > 0 && soundName != NULL && maxChannelsToAdd > 0 )
  386. {
  387. AccumulatedImpactSoundVector accumulatedSounds;
  388. accumulatedSounds.AddToTail();
  389. accumulatedSounds.Tail().Initialize( positions.Head(), soundName );
  390. const float maxRadiusSq = Sqr( paint_impact_accumulate_sound_distance_threshold.GetFloat() );
  391. // For each position
  392. for( int positionIndex = 1; positionIndex < positions.Count(); ++positionIndex )
  393. {
  394. // Check if the position is close enough to the center of an accumulated impact sound
  395. // Note: "Center" is just the first position in the list.
  396. const Vector& soundPosition = positions[positionIndex];
  397. bool positionAccumulated = false;
  398. for( int accumSoundIndex = 0; accumSoundIndex < accumulatedSounds.Count(); ++accumSoundIndex )
  399. {
  400. AccumulatedImpactSound& sound = accumulatedSounds[accumSoundIndex];
  401. const Vector& center = sound.positions.Head();
  402. if( (center - soundPosition).LengthSqr() < maxRadiusSq )
  403. {
  404. sound.positions.AddToTail( soundPosition );
  405. int& pitch = sound.soundParams.pitch;
  406. float& volume = sound.soundParams.volume;
  407. const int adjustedPitch = pitch - isel( VOL_NORM - volume, sound.pitchDecreasePerFullVolumeImpact, 0 );
  408. pitch = imax( adjustedPitch, sound.minAdjustedPitch );
  409. volume = fpmin( volume + sound.volumeIncreasePerImpact, VOL_NORM );
  410. positionAccumulated = true;
  411. break;
  412. }
  413. }
  414. if( !positionAccumulated && accumulatedSounds.Count() < maxChannelsToAdd )
  415. {
  416. accumulatedSounds.AddToTail();
  417. accumulatedSounds.Tail().Initialize( soundPosition, soundName );
  418. }
  419. }
  420. // Play each accumulated sound
  421. for( int accumSoundIndex = 0; accumSoundIndex < accumulatedSounds.Count(); ++accumSoundIndex )
  422. {
  423. AccumulatedImpactSound& sound = accumulatedSounds[accumSoundIndex];
  424. // Find the average position
  425. const AccumulatedSoundPositionVector& soundPositions = sound.positions;
  426. Vector averagedCenter = std::accumulate( soundPositions.Base(), soundPositions.Base() + soundPositions.Count(), vec3_origin );
  427. averagedCenter /= soundPositions.Count();
  428. // Emit the sound
  429. EmitSound_t emitParams( sound.soundParams );
  430. emitParams.m_pOrigin = &averagedCenter;
  431. const float duration = PlayPaintImpactSound( emitParams );
  432. // Update the number of used channels
  433. channelTimeStamps.AddToTail( gpGlobals->curtime + duration );
  434. }
  435. }
  436. }
  437. const char* CPaintStreamManager::GetPaintSoundEffectName( unsigned int impactEffect )
  438. {
  439. return impactEffect < PAINT_IMPACT_EFFECT_COUNT ? s_SoundEffectNames[impactEffect] : NULL;
  440. }
  441. float CPaintStreamManager::PlayPaintImpactSound( const EmitSound_t& emitParams )
  442. {
  443. //Emit the sound for the impact
  444. #ifdef GAME_DLL
  445. CBasePlayer *pRecipient = UTIL_GetLocalPlayerOrListenServerHost();
  446. if ( pRecipient == NULL )
  447. {
  448. return 0.0f;
  449. }
  450. CSingleUserRecipientFilter filter( pRecipient );
  451. #else
  452. CLocalPlayerFilter filter;
  453. #endif
  454. CBaseEntity::EmitSound( filter, 0, emitParams );
  455. return CBaseEntity::GetSoundDuration( emitParams.m_pSoundName, NULL ); // This will generate a "should use game_sounds.txt" warning, but the sound name comes from game_sounds.txt. The warning is benign.
  456. }