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

//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============//
//
//=============================================================================//
#include "cbase.h"
#include <numeric>
#include "paint_stream_manager.h"
#include "paint_blobs_shared.h"
#include "paint_sprayer_shared.h"
#include "debugoverlay_shared.h"
#include "fmtstr.h"
#include "vprof.h"
#include "paint_stream_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define VPROF_BUDGETGROUP_PAINTBLOB _T("Paintblob")
const char* const CPaintStreamManager::m_pPaintMaterialNames[PAINT_POWER_TYPE_COUNT_PLUS_NO_POWER] =
{
"paintblobs/blob_surface_bounce",
"paintblobs/blob_surface_stick", // FIXME: Bring this back for DLC2 "paintblobs/blob_surface_reflect",
"paintblobs/blob_surface_speed",
"paintblobs/blob_surface_portal",
"paintblobs/blob_surface_erase"
};
const char* const CPaintStreamManager::s_SoundEffectNames[PAINT_IMPACT_EFFECT_COUNT] =
{
"Paintblob.Impact",
"Paintblob.ImpactDrip"
};
//Paint particle impact effect convars
ConVar paint_impact_particles_distance_threshold( "paint_impact_particles_distance_threshold", "20.0f", FCVAR_REPLICATED );
ConVar paint_impact_particles_duration( "paint_impact_particles_duration", "0.2f", FCVAR_REPLICATED );
ConVar paint_min_impact_particles( "paint_min_impact_particles", "20", FCVAR_REPLICATED );
ConVar paint_max_impact_particles( "paint_max_impact_particles", "50", FCVAR_REPLICATED );
ConVar paint_impact_accumulate_sound_distance_threshold( "paint_impact_accumulate_sound_distance_threshold", "128.0f", FCVAR_REPLICATED );
ConVar paint_impact_count_to_max_adjusted_volume( "paint_impact_count_to_max_adjusted_volume", "5", FCVAR_REPLICATED );
ConVar paint_impact_count_to_min_adjusted_pitch_after_full_volume( "paint_impact_count_to_min_adjusted_pitch_after_full_volume", "5", FCVAR_REPLICATED );
ConVar min_adjusted_pitch_percentage( "min_adjusted_pitch_percentage", "0.85", FCVAR_REPLICATED );
ConVar draw_paint_splat_particles( "draw_paint_splat_particles", "1", FCVAR_REPLICATED );
ConVar group_paint_impact_effects( "cl_group_paint_impact_effects", "1", FCVAR_REPLICATED );
ConVar debug_paint_impact_effects( "debug_paint_impact_effects", "0", FCVAR_REPLICATED );
ConVar blobs_paused("blobs_paused", "0", FCVAR_CHEAT | FCVAR_REPLICATED );
CPaintStreamManager PaintStreamManager( "PaintStreamManager" );
CPaintStreamManager::CPaintStreamManager( char const *name )
: CAutoGameSystemPerFrame( name )
{
}
CPaintStreamManager::~CPaintStreamManager( void )
{
}
void CPaintStreamManager::LevelInitPreEntity()
{
blobs_paused.SetValue( true );
}
void CPaintStreamManager::LevelInitPostEntity()
{
blobs_paused.SetValue( false );
}
void CPaintStreamManager::LevelShutdownPostEntity()
{
m_PaintImpactParticles.RemoveAll();
if ( m_pBlobPool )
{
m_pBlobPool->Clear();
delete m_pBlobPool;
m_pBlobPool = NULL;
}
}
void CPaintStreamManager::AllocatePaintBlobPool( int nMaxBlobs )
{
int nMaxCount = ( nMaxBlobs ) ? nMaxBlobs : 250;
// pre-allocate pool of blobs
if ( !m_pBlobPool )
{
#ifdef GAME_DLL
m_pBlobPool = new CClassMemoryPool< CPaintBlob >( nMaxCount, CUtlMemoryPool::GROW_NONE );
#else
if ( !engine->IsClientLocalToActiveServer() )
{
m_pBlobPool = new CClassMemoryPool< CPaintBlob >( nMaxCount, CUtlMemoryPool::GROW_NONE );
}
#endif
}
else if ( ( m_pBlobPool->Size() / m_pBlobPool->BlockSize() ) != nMaxBlobs )
{
Assert( 0 );
Warning( "CPaintStreamManager::AllocatePaintBlobPool is being called multiple times (for some reasons) with different pool sizes." );
}
}
void CPaintStreamManager::RemoveAllPaintBlobs( void )
{
for( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
{
CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
if( pStream )
{
pStream->RemoveAllPaintBlobs();
}
}
}
CPaintBlob* CPaintStreamManager::AllocatePaintBlob( bool bSilent /*= false*/ )
{
// don't create when blob is paused
if ( blobs_paused.GetBool() )
{
return NULL;
}
#ifdef CLIENT_DLL
if ( bSilent )
{
return NULL;
}
#endif
return m_pBlobPool->Alloc();
}
void CPaintStreamManager::FreeBlob( CPaintBlob* pBlob )
{
m_pBlobPool->Free( pBlob );
}
const char *CPaintStreamManager::GetPaintMaterialName( int type )
{
return m_pPaintMaterialNames[ type ];
}
#ifdef CLIENT_DLL
void CPaintStreamManager::Update( float frametime )
{
PaintStreamUpdate();
//Update the particle and sound impact effects
UpdatePaintImpactEffects( frametime, m_PaintImpactParticles );
//Display a list of all the paint impact effects currently playing
if( debug_paint_impact_effects.GetBool() )
{
int line = 6;
float lineHeight = 0.015;
float startX = 0.01f;
float startY = 0.0f;
CFmtStr msg;
msg.sprintf( "Paint blob impact particles: %d", m_PaintImpactParticles.Count() );
NDebugOverlay::ScreenText( startX, startY + (line * lineHeight), msg, 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
line++;
}
}
#else
void CPaintStreamManager::PreClientUpdate( void )
{
PaintStreamUpdate();
//engine->Con_NPrintf( 0, "num blobs = %d", GetBlobCount() );
//Update the particle and sound impact effects
UpdatePaintImpactEffects( gpGlobals->frametime, m_PaintImpactParticles );
//Display a list of all the paint impact effects currently playing
if( debug_paint_impact_effects.GetBool() )
{
int line = 6;
float lineHeight = 0.015;
float startX = 0.01f;
float startY = 0.0f;
CFmtStr msg;
msg.sprintf( "Paint blob impact particles: %d", m_PaintImpactParticles.Count() );
NDebugOverlay::ScreenText( startX, startY + (line * lineHeight), msg, 0, 255, 255, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
line++;
}
}
#endif
void CPaintStreamManager::PaintStreamUpdate()
{
VPROF_BUDGET( "CPaintStreamManager::PaintStreamUpdate", VPROF_BUDGETGROUP_PAINTBLOB );
#ifdef CLIENT_DLL
// if the client is local to server, only update render bounds
// let the server do all the work.
if ( engine->IsClientLocalToActiveServer() )
{
for ( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
{
CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
if ( pStream )
{
pStream->UpdateRenderBoundsAndOriginWorldspace();
}
}
return;
}
#endif
// remove dead blobs from beam list before we set dead blobs to NULL
CTrigger_TractorBeam_Shared::RemoveDeadBlobsFromBeams();
// we want to update blob collision for all blobs at once
PaintBlobVector_t allBlobs;
// preupdate (delete blobs from streams)
for ( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
{
CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
if ( pStream )
{
pStream->PreUpdateBlobs();
int numCurrentBlobs = allBlobs.Count();
int numNewBlobs = pStream->GetBlobList().Count();
allBlobs.EnsureCount( allBlobs.Count() + numNewBlobs );
V_memcpy( allBlobs.Base() + numCurrentBlobs, pStream->GetBlobList().Base(), numNewBlobs * sizeof( CBasePaintBlob* ) );
}
}
// copy blobs from all stream and update all of them
if ( blobs_paused.GetBool() )
{
for ( int i=0; i<allBlobs.Count(); ++i )
{
allBlobs[i]->SetLastUpdateTime( gpGlobals->curtime );
}
}
else
{
// update all blobs
PaintBlobUpdate( allBlobs );
}
// post update
for ( int i = 0; i < IPaintStreamAutoList::AutoList().Count(); ++i )
{
CPaintStream *pStream = static_cast< CPaintStream* >( IPaintStreamAutoList::AutoList()[i] );
if ( pStream )
{
pStream->PostUpdateBlobs();
}
}
// remove blobs that change beams to correct blob list in beam
CTrigger_TractorBeam_Shared::RemoveBlobsFromPreviousBeams();
}
void CPaintStreamManager::UpdatePaintImpactEffects( float flDeltaTime, PaintBlobImpactEffectVector_t &paintImpactEffects )
{
//Update the paint blob impact effects
for( int i = 0; i < paintImpactEffects.Count(); ++i )
{
PaintBlobImpactEffect_t *pEffect = &paintImpactEffects[i];
if ( pEffect )
{
//Decrement the timer of the effect
pEffect->flTime -= flDeltaTime;
//Remove the effect if it has finished playing
if( pEffect->flTime <= 0.0f )
{
paintImpactEffects.FastRemove( i-- );
}
}
}
}
void CPaintStreamManager::CreatePaintImpactParticles( const Vector &vecPosition, const Vector &vecNormal, int paintType )
{
//Check if the impact particle effect should be played
if( ShouldPlayImpactEffect( vecPosition,
m_PaintImpactParticles,
paint_min_impact_particles.GetInt(),
paint_max_impact_particles.GetInt(),
paint_impact_particles_distance_threshold.GetFloat() * paint_impact_particles_distance_threshold.GetFloat() ) )
{
PlayPaintImpactParticles( vecPosition, vecNormal, paintType );
}
}
bool CPaintStreamManager::ShouldPlayImpactEffect( const Vector& vecPosition, PaintBlobImpactEffectVector_t &paintImpactEffects, int minEffects, int maxEffects, float flDistanceThresholdSqr )
{
if( !group_paint_impact_effects.GetBool() )
{
return true;
}
int iImpactEffectCount = paintImpactEffects.Count();
//If we are below the min threshold then play the paint impact effect
if( iImpactEffectCount < minEffects )
{
return true;
}
//Don't play any paint impact effect if we are above the max
else if( iImpactEffectCount >= maxEffects )
{
return false;
}
int iEffectIndex = 0;
//Don't play the effect if it's too close to another paint impact effect
for ( iEffectIndex = 0; iEffectIndex < iImpactEffectCount; ++iEffectIndex )
{
PaintBlobImpactEffect_t *pEffect = &paintImpactEffects[iEffectIndex];
if ( pEffect )
{
//Check if this effect is too close to a effect already playing
if ( vecPosition.DistToSqr( pEffect->vecPosition ) < ( flDistanceThresholdSqr ) )
{
return false;
}
}
}
//OK to play the effect
return true;
}
struct SplatParticlesForPaint_t
{
int nPaintType;
const char *lpszParticleSystemName;
};
SplatParticlesForPaint_t paintSplatCallbacks[] =
{
{ BOUNCE_POWER, "paint_splat_bounce_01" },
{ REFLECT_POWER,"paint_splat_stick_01" }, // FIXME: Bring this back for DLC2 { REFLECT_POWER,"paint_splat_reflect_01" },
{ SPEED_POWER, "paint_splat_speed_01" },
{ PORTAL_POWER, "paint_splat_erase_01" },
{ NO_POWER, "paint_splat_erase_01" },
};
void PaintSplatEffect( const Vector& vecPosition, const Vector& vecNormal, int paintType )
{
Assert( paintType >= 0 && paintType < ARRAYSIZE( paintSplatCallbacks ) );
Assert( paintSplatCallbacks[paintType].nPaintType == paintType );
QAngle angle;
VectorAngles( -vecNormal, angle );
CBasePlayer *pPlayer = NULL;
#ifdef GAME_DLL
if ( !engine->IsDedicatedServer() )
{
pPlayer = UTIL_GetLocalPlayerOrListenServerHost();
}
#else
pPlayer = GetSplitScreenViewPlayer();
#endif
if ( pPlayer )
{
CSingleUserRecipientFilter filter( pPlayer );
DispatchParticleEffect( paintSplatCallbacks[paintType].lpszParticleSystemName, vecPosition, angle, NULL, -1, &filter );
}
}
void CPaintStreamManager::PlayPaintImpactParticles( const Vector &vecPosition, const Vector &vecNormal, int paintType )
{
//Play the particle effect for the impact
if( draw_paint_splat_particles.GetBool() )
{
PaintSplatEffect( vecPosition, vecNormal, paintType );
}
//Add the effect to the list
int iEffectIndex = m_PaintImpactParticles.AddToTail();
m_PaintImpactParticles[iEffectIndex].vecPosition = vecPosition;
m_PaintImpactParticles[iEffectIndex].flTime = paint_impact_particles_duration.GetFloat();
}
float CPaintStreamManager::PlayPaintImpactSound( const Vector &vecPosition, PaintImpactEffect impactEffect )
{
//Emit the sound for the impact
#ifdef GAME_DLL
CBasePlayer *pRecipient = UTIL_GetLocalPlayerOrListenServerHost();
if ( pRecipient == NULL )
{
return 0.0f;
}
CSingleUserRecipientFilter filter( pRecipient );
#else
CLocalPlayerFilter filter;
#endif
const char* soundName = GetPaintSoundEffectName( impactEffect );
CBaseEntity::EmitSound( filter, 0, soundName, &vecPosition );
return CBaseEntity::GetSoundDuration( soundName, NULL );
}
typedef CUtlVectorFixedGrowable< Vector, 16 > AccumulatedSoundPositionVector;
struct AccumulatedImpactSound
{
CSoundParameters soundParams;
AccumulatedSoundPositionVector positions;
float volumeIncreasePerImpact;
int pitchDecreasePerFullVolumeImpact;
int minAdjustedPitch;
void Initialize( const Vector& center, const char* soundName );
};
void AccumulatedImpactSound::Initialize( const Vector& center, const char* soundName )
{
positions.AddToTail( center );
if( CBaseEntity::GetParametersForSound( soundName, soundParams, NULL ) )
{
const int impactsToMinPitch = paint_impact_count_to_min_adjusted_pitch_after_full_volume.GetInt();
minAdjustedPitch = min_adjusted_pitch_percentage.GetFloat() * soundParams.pitch + 0.5f;
const int deltaToMinPitch = soundParams.pitch - minAdjustedPitch;
pitchDecreasePerFullVolumeImpact = static_cast<float>(deltaToMinPitch) / impactsToMinPitch + 0.5f;
const int impactsToFullVolume = paint_impact_count_to_max_adjusted_volume.GetInt();
const float deltaToFullVolume = VOL_NORM - soundParams.volume;
volumeIncreasePerImpact = deltaToFullVolume / impactsToFullVolume;
}
else
{
Assert(!"GetParametersForSound() failed.");
}
}
typedef CUtlVectorFixedGrowable< AccumulatedImpactSound, 32 > AccumulatedImpactSoundVector;
void CPaintStreamManager::PlayMultiplePaintImpactSounds( TimeStampVector& channelTimeStamps, int maxChannels, const PaintImpactPositionVector& positions, PaintImpactEffect impactEffect )
{
const char* soundName = GetPaintSoundEffectName( impactEffect );
Assert( positions.Count() > 0 && soundName != NULL );
const int maxChannelsToAdd = imax( maxChannels - channelTimeStamps.Count(), 0 );
if( positions.Count() > 0 && soundName != NULL && maxChannelsToAdd > 0 )
{
AccumulatedImpactSoundVector accumulatedSounds;
accumulatedSounds.AddToTail();
accumulatedSounds.Tail().Initialize( positions.Head(), soundName );
const float maxRadiusSq = Sqr( paint_impact_accumulate_sound_distance_threshold.GetFloat() );
// For each position
for( int positionIndex = 1; positionIndex < positions.Count(); ++positionIndex )
{
// Check if the position is close enough to the center of an accumulated impact sound
// Note: "Center" is just the first position in the list.
const Vector& soundPosition = positions[positionIndex];
bool positionAccumulated = false;
for( int accumSoundIndex = 0; accumSoundIndex < accumulatedSounds.Count(); ++accumSoundIndex )
{
AccumulatedImpactSound& sound = accumulatedSounds[accumSoundIndex];
const Vector& center = sound.positions.Head();
if( (center - soundPosition).LengthSqr() < maxRadiusSq )
{
sound.positions.AddToTail( soundPosition );
int& pitch = sound.soundParams.pitch;
float& volume = sound.soundParams.volume;
const int adjustedPitch = pitch - isel( VOL_NORM - volume, sound.pitchDecreasePerFullVolumeImpact, 0 );
pitch = imax( adjustedPitch, sound.minAdjustedPitch );
volume = fpmin( volume + sound.volumeIncreasePerImpact, VOL_NORM );
positionAccumulated = true;
break;
}
}
if( !positionAccumulated && accumulatedSounds.Count() < maxChannelsToAdd )
{
accumulatedSounds.AddToTail();
accumulatedSounds.Tail().Initialize( soundPosition, soundName );
}
}
// Play each accumulated sound
for( int accumSoundIndex = 0; accumSoundIndex < accumulatedSounds.Count(); ++accumSoundIndex )
{
AccumulatedImpactSound& sound = accumulatedSounds[accumSoundIndex];
// Find the average position
const AccumulatedSoundPositionVector& soundPositions = sound.positions;
Vector averagedCenter = std::accumulate( soundPositions.Base(), soundPositions.Base() + soundPositions.Count(), vec3_origin );
averagedCenter /= soundPositions.Count();
// Emit the sound
EmitSound_t emitParams( sound.soundParams );
emitParams.m_pOrigin = &averagedCenter;
const float duration = PlayPaintImpactSound( emitParams );
// Update the number of used channels
channelTimeStamps.AddToTail( gpGlobals->curtime + duration );
}
}
}
const char* CPaintStreamManager::GetPaintSoundEffectName( unsigned int impactEffect )
{
return impactEffect < PAINT_IMPACT_EFFECT_COUNT ? s_SoundEffectNames[impactEffect] : NULL;
}
float CPaintStreamManager::PlayPaintImpactSound( const EmitSound_t& emitParams )
{
//Emit the sound for the impact
#ifdef GAME_DLL
CBasePlayer *pRecipient = UTIL_GetLocalPlayerOrListenServerHost();
if ( pRecipient == NULL )
{
return 0.0f;
}
CSingleUserRecipientFilter filter( pRecipient );
#else
CLocalPlayerFilter filter;
#endif
CBaseEntity::EmitSound( filter, 0, emitParams );
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.
}