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
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.
|
|
}
|