source-engine/soundemittersystem/soundemittersystembase.cpp

1665 lines
43 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include <KeyValues.h>
#include "filesystem.h"
#include "utldict.h"
#include "interval.h"
#include "engine/IEngineSound.h"
#include "soundemittersystembase.h"
#include "utlbuffer.h"
#include "soundchars.h"
#include "vstdlib/random.h"
#include "checksum_crc.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "ifilelist.h"
#include <time.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define MANIFEST_FILE "scripts/game_sounds_manifest.txt"
#define GAME_SOUNDS_HEADER_BLOCK "scripts/game_sounds_header.txt"
static IFileSystem* filesystem = 0;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CSoundEmitterSystemBase::CSoundEmitterSystemBase() :
m_nInitCount( 0 ),
m_uManifestPlusScriptChecksum( 0 )
{
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::First() const
{
return m_Sounds.FirstHandle();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : i -
// Output : int
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::Next( int i ) const
{
return m_Sounds.NextHandle(i);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::InvalidIndex() const
{
return m_Sounds.InvalidHandle();
}
//-----------------------------------------------------------------------------
//
// implementation of IUniformRandomStream
//
//-----------------------------------------------------------------------------
class CSoundEmitterUniformRandomStream : public IUniformRandomStream
{
public:
// Sets the seed of the random number generator
void SetSeed( int iSeed )
{
// Never call this from the client or game!
Assert(0);
}
// Generates random numbers
float RandomFloat( float flMinVal = 0.0f, float flMaxVal = 1.0f )
{
return ::RandomFloat( flMinVal, flMaxVal );
}
int RandomInt( int iMinVal, int iMaxVal )
{
return ::RandomInt( iMinVal, iMaxVal );
}
float RandomFloatExp( float flMinVal = 0.0f, float flMaxVal = 1.0f, float flExponent = 1.0f )
{
return ::RandomFloatExp( flMinVal, flMaxVal, flExponent );
}
};
static CSoundEmitterUniformRandomStream g_RandomStream;
IUniformRandomStream *randomStream = &g_RandomStream;
//-----------------------------------------------------------------------------
// Connect, disconnect
//-----------------------------------------------------------------------------
bool CSoundEmitterSystemBase::Connect( CreateInterfaceFn factory )
{
// If someone already connected us up, don't redo the connection
if ( NULL != filesystem )
{
return true;
}
filesystem = (IFileSystem *)factory( FILESYSTEM_INTERFACE_VERSION, NULL );
if( !filesystem )
{
Error( "The soundemittersystem system requires the filesystem to run!\n" );
return false;
}
return true;
}
void CSoundEmitterSystemBase::Disconnect()
{
filesystem = NULL;
}
//-----------------------------------------------------------------------------
// Query interface
//-----------------------------------------------------------------------------
void *CSoundEmitterSystemBase::QueryInterface( const char *pInterfaceName )
{
// Loading the engine DLL mounts *all* soundemitter interfaces
CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary
return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing.
}
//-----------------------------------------------------------------------------
// Init, shutdown
//-----------------------------------------------------------------------------
InitReturnVal_t CSoundEmitterSystemBase::Init()
{
return INIT_OK;
}
void CSoundEmitterSystemBase::Shutdown()
{
}
//-----------------------------------------------------------------------------
// Purpose: Helper for checksuming script files and manifest to determine if soundname caches
// need to be blown away.
// Input : *crc -
// *filename -
// Output : static void
//-----------------------------------------------------------------------------
static void AccumulateFileNameAndTimestampIntoChecksum( CRC32_t *crc, char const *filename )
{
if ( IsX360() )
{
// this is an expensive i/o operation due to search path fall through
// 360 doesn't need or use the checksums
return;
}
time_t ft = filesystem->GetFileTime( filename, "GAME" );
CRC32_ProcessBuffer( crc, &ft, sizeof( ft ) );
CRC32_ProcessBuffer( crc, filename, Q_strlen( filename ) );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSoundEmitterSystemBase::InternalModInit()
{
/*
if ( m_SoundKeyValues.Count() > 0 )
{
Shutdown();
}
*/
LoadGlobalActors();
m_uManifestPlusScriptChecksum = 0u;
CRC32_t crc;
CRC32_Init( &crc );
KeyValues *manifest = new KeyValues( MANIFEST_FILE );
if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDEMITTER, MANIFEST_FILE, "GAME" ) )
{
AccumulateFileNameAndTimestampIntoChecksum( &crc, MANIFEST_FILE );
for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
{
if ( !Q_stricmp( sub->GetName(), "precache_file" ) )
{
AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
// Add and always precache
AddSoundsFromFile( sub->GetString(), false );
continue;
}
else if ( !Q_stricmp( sub->GetName(), "preload_file" ) )
{
AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
// Add and always precache
AddSoundsFromFile( sub->GetString(), true );
continue;
}
else if ( !Q_stricmp( sub->GetName(), "faceposer_file" ) )
{
// do nothing for these files; they're only used for faceposer
continue;
}
Warning( "CSoundEmitterSystemBase::BaseInit: Manifest '%s' with bogus file type '%s', expecting 'declare_file' or 'precache_file'\n",
MANIFEST_FILE, sub->GetName() );
}
}
else
{
Error( "Unable to load manifest file '%s'\n", MANIFEST_FILE );
}
manifest->deleteThis();
CRC32_Final( &crc );
m_uManifestPlusScriptChecksum =( unsigned int )crc;
// Only print total once, on server
#if !defined( CLIENT_DLL ) && !defined( FACEPOSER )
DevMsg( 1, "CSoundEmitterSystem: Registered %i sounds\n", m_Sounds.Count() );
#endif
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSoundEmitterSystemBase::ModInit()
{
++m_nInitCount;
if ( m_nInitCount > 1 )
{
return true;
}
return InternalModInit();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::InternalModShutdown()
{
int i;
m_SoundKeyValues.RemoveAll();
for ( UtlHashHandle_t nIndex = m_Sounds.FirstHandle(); nIndex != m_Sounds.InvalidHandle(); nIndex = m_Sounds.NextHandle( nIndex ) )
{
delete m_Sounds[ nIndex ];
}
m_Sounds.Purge();
for ( i = 0; i < m_SavedOverrides.Count() ; ++i )
{
delete m_SavedOverrides[ i ];
}
m_SavedOverrides.Purge();
m_Waves.RemoveAll();
m_ActorGenders.Purge();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::ModShutdown()
{
if ( --m_nInitCount > 0 )
return;
InternalModShutdown();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pName -
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::GetSoundIndex( const char *pName ) const
{
if ( !pName )
return -1;
CSoundEntry search;
search.m_Name = pName;
UtlHashHandle_t idx = m_Sounds.Find( pName );
if ( idx == m_Sounds.InvalidHandle() )
return -1;
return idx;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSoundEmitterSystemBase::IsValidIndex( int index )
{
return m_Sounds.IsValidHandle( index );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
// Output : char const
//-----------------------------------------------------------------------------
const char *CSoundEmitterSystemBase::GetSoundName( int index )
{
if ( !IsValidIndex( index ) )
return "";
return m_Sounds[ index ]->m_Name.Get();
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::GetSoundCount( void )
{
return m_Sounds.Count();
}
void CSoundEmitterSystemBase::EnsureAvailableSlotsForGender( SoundFile *pSoundnames, int c, gender_t gender )
{
int i;
if ( c <= 0 )
{
return;
}
CUtlVector< int > slots;
bool needsreset = false;
for ( i = 0; i < c; i++ )
{
if ( pSoundnames[ i ].gender != gender )
continue;
// There was at least one match for the gender
needsreset = true;
// This sound is unavailable
if ( !pSoundnames[ i ].available )
continue;
slots.AddToTail( i );
}
if ( slots.Count() == 0 && needsreset )
{
// Reset all slots for the specified gender!!!
for ( i = 0; i < c; i++ )
{
if ( pSoundnames[ i ].gender != gender )
continue;
pSoundnames[ i ].available = true;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : gender -
// soundnames -
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::FindBestSoundForGender( SoundFile *pSoundnames, int c, gender_t gender )
{
// Check for recycling of random sounds...
EnsureAvailableSlotsForGender( pSoundnames, c, gender );
if ( c <= 0 )
{
return -1;
}
CUtlVector< int > slots;
for ( int i = 0; i < c; i++ )
{
if ( pSoundnames[ i ].gender == gender &&
pSoundnames[ i ].available )
{
slots.AddToTail( i );
}
}
if ( slots.Count() >= 1 )
{
int idx = slots[ randomStream->RandomInt( 0, slots.Count() - 1 ) ];
return idx;
}
int idx = randomStream->RandomInt( 0, c - 1 );
return idx;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *soundname -
// params -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSoundEmitterSystemBase::GetParametersForSound( const char *soundname, CSoundParameters& params, gender_t gender, bool isbeingemitted /*= false*/ )
{
HSOUNDSCRIPTHANDLE index = (HSOUNDSCRIPTHANDLE)GetSoundIndex( soundname );
if ( index == SOUNDEMITTER_INVALID_HANDLE )
{
static CUtlSymbolTable soundWarnings;
char key[ 256 ];
Q_snprintf( key, sizeof( key ), "%s:%s", soundname, params.soundname );
if ( UTL_INVAL_SYMBOL == soundWarnings.Find( key ) )
{
soundWarnings.AddString( key );
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: No such sound %s\n", soundname );
}
return false;
}
return GetParametersForSoundEx( soundname, index, params, gender, isbeingemitted );
}
CSoundParametersInternal *CSoundEmitterSystemBase::InternalGetParametersForSound( int index )
{
if ( !m_Sounds.IsValidHandle( index ) )
{
Assert( !"CSoundEmitterSystemBase::InternalGetParametersForSound: Bogus index" );
return NULL;
}
return &m_Sounds[ index ]->m_SoundParams;
}
static void SplitName( char const *input, int splitchar, int splitlen, char *before, int beforelen, char *after, int afterlen )
{
char const *in = input;
char *out = before;
int c = 0;
int l = 0;
int maxl = beforelen;
while ( *in )
{
if ( c == splitchar )
{
while ( --splitlen >= 0 )
{
in++;
}
*out = 0;
out = after;
maxl = afterlen;
c++;
continue;
}
if ( l >= maxl )
{
in++;
c++;
continue;
}
*out++ = *in++;
l++;
c++;
}
*out = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : params -
// *wavename -
// gender -
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::AddSoundName( CSoundParametersInternal& params, char const *wavename, gender_t gender )
{
CUtlSymbol sym = m_Waves.AddString( wavename );
SoundFile e;
e.symbol = sym;
e.gender = gender;
if ( gender != GENDER_NONE )
{
params.SetUsesGenderToken( true );
}
params.AddSoundName( e );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : params -
// *wavename -
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::ExpandSoundNameMacros( CSoundParametersInternal& params, char const *wavename )
{
char const *p = Q_stristr( wavename, SOUNDGENDER_MACRO );
if ( !p )
{
AddSoundName( params, wavename, GENDER_NONE );
return;
}
int offset = p - wavename;
Assert( offset >= 0 );
int duration = SOUNDGENDER_MACRO_LENGTH;
// Create a "male" and "female" version of the sound
char before[ 256 ], after[ 256 ];
Q_memset( before, 0, sizeof( before ) );
Q_memset( after, 0, sizeof( after ) );
SplitName( wavename, offset, duration, before, sizeof( before ), after, sizeof( after ) );
char temp[ 256 ];
Q_snprintf( temp, sizeof( temp ), "%s%s%s", before, "male", after );
AddSoundName( params, temp, GENDER_MALE );
Q_snprintf( temp, sizeof( temp ), "%s%s%s", before, "female", after );
AddSoundName( params, temp, GENDER_FEMALE );
// Add the conversion entry with the gender tags still in it
CUtlSymbol sym = m_Waves.AddString( wavename );
SoundFile e;
e.symbol = sym;
e.gender = GENDER_NONE;
params.AddConvertedName( e );
}
void CSoundEmitterSystemBase::GenderExpandString( gender_t gender, char const *in, char *out, int maxlen )
{
// Assume the worst
Q_strncpy( out, in, maxlen );
char const *p = Q_stristr( in, SOUNDGENDER_MACRO );
if ( !p )
{
return;
}
// Look up actor gender
if ( gender == GENDER_NONE )
{
return;
}
int offset = p - in;
Assert( offset >= 0 );
int duration = SOUNDGENDER_MACRO_LENGTH;
// Create a "male" and "female" version of the sound
char before[ 256 ], after[ 256 ];
Q_memset( before, 0, sizeof( before ) );
Q_memset( after, 0, sizeof( after ) );
SplitName( in, offset, duration, before, sizeof( before ), after, sizeof( after ) );
switch ( gender )
{
default:
case GENDER_NONE:
{
Assert( !"CSoundEmitterSystemBase::GenderExpandString: expecting MALE or FEMALE!" );
}
break;
case GENDER_MALE:
{
Q_snprintf( out, maxlen, "%s%s%s", before, "male", after );
}
break;
case GENDER_FEMALE:
{
Q_snprintf( out, maxlen, "%s%s%s", before, "female", after );
}
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *actorname -
// *in -
// *out -
// maxlen -
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::GenderExpandString( char const *actormodel, char const *in, char *out, int maxlen )
{
gender_t gender = GetActorGender( actormodel );
GenderExpandString( gender, in, out, maxlen );
}
void CSoundEmitterSystemBase::LoadGlobalActors()
{
// Now load the global actor list from the scripts/globalactors.txt file
KeyValues *allActors = NULL;
allActors = new KeyValues( "allactors" );
if ( allActors->LoadFromFile( filesystem, "scripts/global_actors.txt", NULL ) )
{
KeyValues *pvkActor;
for ( pvkActor = allActors->GetFirstSubKey(); pvkActor != NULL; pvkActor = pvkActor->GetNextKey() )
{
UtlHashHandle_t idx = m_ActorGenders.Find( pvkActor->GetName() );
if ( idx == m_ActorGenders.InvalidHandle() )
{
if ( m_ActorGenders.Count() > 254 )
{
Warning( "Exceeded max number of actors in scripts/global_actors.txt\n" );
break;
}
gender_t gender = GENDER_NONE;
if ( !Q_stricmp( pvkActor->GetString(), "male" ) )
{
gender = GENDER_MALE;
}
else if (!Q_stricmp( pvkActor->GetString(), "female" ) )
{
gender = GENDER_FEMALE;
}
m_ActorGenders.Insert( pvkActor->GetName(), gender );
}
}
}
allActors->deleteThis();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *actorname -
// Output : gender_t
//-----------------------------------------------------------------------------
gender_t CSoundEmitterSystemBase::GetActorGender( char const *actormodel )
{
char actor[ 256 ];
actor[0] = 0;
if ( actormodel )
{
Q_FileBase( actormodel, actor, sizeof( actor ) );
}
UtlHashHandle_t idx = m_ActorGenders.Find( actor );
if ( idx == m_ActorGenders.InvalidHandle() )
return GENDER_NONE;
return m_ActorGenders[ idx ];
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *soundname -
// params -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSoundEmitterSystemBase::InitSoundInternalParameters( const char *soundname, KeyValues *kv, CSoundParametersInternal& params )
{
KeyValues *pKey = kv->GetFirstSubKey();
while ( pKey )
{
if ( !Q_strcasecmp( pKey->GetName(), "channel" ) )
{
params.ChannelFromString( pKey->GetString() );
}
else if ( !Q_strcasecmp( pKey->GetName(), "volume" ) )
{
params.VolumeFromString( pKey->GetString() );
}
else if ( !Q_strcasecmp( pKey->GetName(), "pitch" ) )
{
params.PitchFromString( pKey->GetString() );
}
else if ( !Q_strcasecmp( pKey->GetName(), "wave" ) )
{
ExpandSoundNameMacros( params, pKey->GetString() );
}
else if ( !Q_strcasecmp( pKey->GetName(), "rndwave" ) )
{
KeyValues *pWaves = pKey->GetFirstSubKey();
while ( pWaves )
{
ExpandSoundNameMacros( params, pWaves->GetString() );
pWaves = pWaves->GetNextKey();
}
}
else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) || !Q_strcasecmp( pKey->GetName(), "CompatibilityAttenuation" ) )
{
if ( !Q_strncasecmp( pKey->GetString(), "SNDLVL_", strlen( "SNDLVL_" ) ) )
{
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has \"attenuation\" with %s value!\n",
soundname, pKey->GetString() );
}
if ( !Q_strncasecmp( pKey->GetString(), "ATTN_", strlen( "ATTN_" ) ) )
{
params.SetSoundLevel( ATTN_TO_SNDLVL( TranslateAttenuation( pKey->GetString() ) ) );
}
else
{
interval_t interval;
interval = ReadInterval( pKey->GetString() );
// Translate from attenuation to soundlevel
float start = interval.start;
float end = interval.start + interval.range;
params.SetSoundLevel( ATTN_TO_SNDLVL( start ), ATTN_TO_SNDLVL( end ) - ATTN_TO_SNDLVL( start ) );
}
// Goldsrc compatibility mode.. feed the sndlevel value through the sound engine interface in such a way
// that it can reconstruct the original sndlevel value and flag the sound as using Goldsrc attenuation.
bool bCompatibilityAttenuation = !Q_strcasecmp( pKey->GetName(), "CompatibilityAttenuation" );
if ( bCompatibilityAttenuation )
{
if ( params.GetSoundLevel().range != 0 )
{
Warning( "CompatibilityAttenuation for sound %s must have same start and end values.\n", soundname );
}
params.SetSoundLevel( SNDLEVEL_TO_COMPATIBILITY_MODE( params.GetSoundLevel().start ) );
}
}
else if ( !Q_strcasecmp( pKey->GetName(), "soundlevel" ) )
{
if ( !Q_strncasecmp( pKey->GetString(), "ATTN_", strlen( "ATTN_" ) ) )
{
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has \"soundlevel\" with %s value!\n",
soundname, pKey->GetString() );
}
params.SoundLevelFromString( pKey->GetString() );
}
else if ( !Q_strcasecmp( pKey->GetName(), "play_to_owner_only" ) )
{
params.SetOnlyPlayToOwner( pKey->GetInt() ? true : false );
}
else if ( !Q_strcasecmp( pKey->GetName(), "delay_msec" ) )
{
// Don't allow negative delay
params.SetDelayMsec( max( 0, pKey->GetInt() ) );
}
pKey = pKey->GetNextKey();
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *soundname -
// Output : char const
//-----------------------------------------------------------------------------
const char *CSoundEmitterSystemBase::GetWavFileForSound( const char *soundname, char const *actormodel )
{
gender_t gender = GetActorGender( actormodel );
return GetWavFileForSound( soundname, gender );
}
const char *CSoundEmitterSystemBase::GetWavFileForSound( const char *soundname, gender_t gender )
{
CSoundParameters params;
if ( !GetParametersForSound( soundname, params, gender ) )
{
return soundname;
}
if ( !params.soundname[ 0 ] )
{
return soundname;
}
static char outsound[ 512 ];
Q_strncpy( outsound, params.soundname, sizeof( outsound ) );
return outsound;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *soundname -
// Output : soundlevel_t
//-----------------------------------------------------------------------------
soundlevel_t CSoundEmitterSystemBase::LookupSoundLevel( const char *soundname )
{
CSoundParameters params;
if ( !GetParametersForSound( soundname, params, GENDER_NONE ) )
{
return SNDLVL_NORM;
}
return params.soundlevel;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *filename -
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::AddSoundsFromFile( const char *filename, bool bPreload, bool bIsOverride /*=false*/, bool bRefresh /*=false*/ )
{
CSoundScriptFile sf;
sf.hFilename = filesystem->FindOrAddFileName( filename );
sf.dirty = false;
int scriptindex = m_SoundKeyValues.AddToTail( sf );
int replaceCount = 0;
int newOverrideCount = 0;
int duplicatedReplacements = 0;
// Open the soundscape data file, and abort if we can't
KeyValues *kv = new KeyValues( "" );
if ( filesystem->LoadKeyValues( *kv, IFileSystem::TYPE_SOUNDEMITTER, filename, "GAME" ) )
{
// parse out all of the top level sections and save their names
KeyValues *pKeys = kv;
while ( pKeys )
{
if ( pKeys->GetFirstSubKey() )
{
if ( m_Sounds.Count() >= 65534 )
{
Warning( "Exceeded maximum number of sound emitter entries\n" );
break;
}
CSoundEntry *pEntry;
{
MEM_ALLOC_CREDIT();
pEntry = new CSoundEntry;
}
pEntry->m_Name = pKeys->GetName();
pEntry->m_bRemoved = false;
pEntry->m_nScriptFileIndex = scriptindex;
pEntry->m_bIsOverride = bIsOverride;
if ( bIsOverride )
{
++newOverrideCount;
}
UtlHashHandle_t lookup = m_Sounds.Insert( pEntry ); // insert returns existing item if found
if ( m_Sounds[ lookup ] != pEntry )
{
if ( bIsOverride )
{
MEM_ALLOC_CREDIT();
// Store off the old sound if it's not already an "override" from another file!!!
// Otherwise, just whack it again!!!
if ( !m_Sounds[ lookup ]->IsOverride() )
{
m_SavedOverrides.AddToTail( m_Sounds[ lookup ] );
}
else
{
++duplicatedReplacements;
}
InitSoundInternalParameters( pKeys->GetName(), pKeys, pEntry->m_SoundParams );
pEntry->m_SoundParams.SetShouldPreload( bPreload ); // this gets handled by game code after initting.
m_Sounds.ReplaceKey( lookup, pEntry );
++replaceCount;
}
else if ( bRefresh )
{
InitSoundInternalParameters( pKeys->GetName(), pKeys, m_Sounds[ lookup ]->m_SoundParams );
}
#if 0
else
{
DevMsg( "CSoundEmitterSystem::AddSoundsFromFile(%s): Entry %s duplicated, skipping\n", filename, pKeys->GetName() );
}
#endif
}
else
{
MEM_ALLOC_CREDIT();
InitSoundInternalParameters( pKeys->GetName(), pKeys, pEntry->m_SoundParams );
pEntry->m_SoundParams.SetShouldPreload( bPreload ); // this gets handled by game code after initting.
}
}
pKeys = pKeys->GetNextKey();
}
kv->deleteThis();
}
else
{
if ( !bIsOverride )
{
Warning( "CSoundEmitterSystem::AddSoundsFromFile: No such file %s\n", filename );
}
// Discard
m_SoundKeyValues.Remove( scriptindex );
kv->deleteThis();
return;
}
if ( bIsOverride )
{
DevMsg( "SoundEmitter: adding map sound overrides from %s [%i total, %i replacements, %i duplicated replacements]\n",
filename,
newOverrideCount,
replaceCount,
duplicatedReplacements );
}
Assert( scriptindex >= 0 );
}
//-----------------------------------------------------------------------------
// Purpose: Reload a sound emitter file (used to refresh files after sv_pure is turned on)
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::ReloadSoundEntriesInList( IFileList *pFilesToReload )
{
int i, c;
c = m_SoundKeyValues.Count();
CUtlVector< const char * > processed;
for ( i = 0; i < c ; i++ )
{
const char *pszFileName = GetSoundScriptName( i );
if ( pszFileName && pszFileName[0] )
{
if ( processed.Find( pszFileName) == processed.InvalidIndex() && pFilesToReload->IsFileInList( pszFileName ) )
{
Msg( "Reloading sound file '%s' due to pure settings.\n", pszFileName );
AddSoundsFromFile( pszFileName, false, false, true );
// Now mark this file name as being reloaded
processed.AddToTail( pszFileName );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Force ModShutdown and ModInit, skips checks for how many systems have
// requested inits (for con commands).
//-----------------------------------------------------------------------------
void CSoundEmitterSystemBase::Flush()
{
InternalModShutdown();
InternalModInit();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::CheckForMissingWavFiles( bool verbose )
{
int missing = 0;
int c = GetSoundCount();
int i;
char testfile[ 512 ];
for ( i = 0; i < c; i++ )
{
CSoundParametersInternal *internal = InternalGetParametersForSound( i );
if ( !internal )
{
Assert( 0 );
continue;
}
int waveCount = internal->NumSoundNames();
for ( int wave = 0; wave < waveCount; wave++ )
{
CUtlSymbol sym = internal->GetSoundNames()[ wave ].symbol;
const char *name = m_Waves.String( sym );
if ( !name || !name[ 0 ] )
{
Assert( 0 );
continue;
}
// Skip ! sentence stuff
if ( name[0] == CHAR_SENTENCE )
continue;
Q_snprintf( testfile, sizeof( testfile ), "sound/%s", PSkipSoundChars( name ) );
if ( filesystem->FileExists( testfile ) )
continue;
internal->SetHadMissingWaveFiles( true );
++missing;
if ( verbose )
{
DevMsg( "Sound %s references missing file %s\n", GetSoundName( i ), name );
}
}
}
return missing;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *key -
// Output : float
//-----------------------------------------------------------------------------
float CSoundEmitterSystemBase::TranslateAttenuation( const char *key )
{
if ( !key )
{
Assert( 0 );
return ATTN_NORM;
}
if ( !Q_strcasecmp( key, "ATTN_NONE" ) )
return ATTN_NONE;
if ( !Q_strcasecmp( key, "ATTN_NORM" ) )
return ATTN_NORM;
if ( !Q_strcasecmp( key, "ATTN_IDLE" ) )
return ATTN_IDLE;
if ( !Q_strcasecmp( key, "ATTN_STATIC" ) )
return ATTN_STATIC;
if ( !Q_strcasecmp( key, "ATTN_RICOCHET" ) )
return ATTN_RICOCHET;
if ( !Q_strcasecmp( key, "ATTN_GUNFIRE" ) )
return ATTN_GUNFIRE;
DevMsg( "CSoundEmitterSystem: Unknown attenuation key %s\n", key );
return ATTN_NORM;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *key -
// Output : soundlevel_t
//-----------------------------------------------------------------------------
soundlevel_t CSoundEmitterSystemBase::TranslateSoundLevel( const char *key )
{
return TextToSoundLevel( key );
}
//-----------------------------------------------------------------------------
// Purpose: Convert "chan_xxx" into integer value for channel
// Input : *name -
// Output : static int
//-----------------------------------------------------------------------------
int CSoundEmitterSystemBase::TranslateChannel( const char *name )
{
return TextToChannel( name );
}
const char *CSoundEmitterSystemBase::GetSourceFileForSound( int index ) const
{
if ( index < 0 || index >= (int)m_Sounds.Count() )
{
Assert( 0 );
return "";
}
CSoundEntry const *entry = m_Sounds[ index ];
int scriptindex = entry->m_nScriptFileIndex;
if ( scriptindex < 0 || scriptindex >= m_SoundKeyValues.Count() )
{
Assert( 0 );
return "";
}
static char fn[ 512 ];
if ( filesystem->String( m_SoundKeyValues[ scriptindex ].hFilename, fn, sizeof( fn ) ))
{
return fn;
}
Assert( 0 );
return "";
}
const char *CSoundEmitterSystemBase::GetWaveName( CUtlSymbol& sym )
{
return m_Waves.String( sym );
}
int CSoundEmitterSystemBase::FindSoundScript( const char *name ) const
{
int i, c;
FileNameHandle_t hFilename = filesystem->FindFileName( name );
if ( hFilename )
{
// First, make sure it's known
c = m_SoundKeyValues.Count();
for ( i = 0; i < c ; i++ )
{
if ( m_SoundKeyValues[ i ].hFilename == hFilename )
{
return i;
}
}
}
return m_SoundKeyValues.InvalidIndex();
}
bool CSoundEmitterSystemBase::AddSound( const char *soundname, const char *scriptfile, const CSoundParametersInternal& params )
{
int idx = GetSoundIndex( soundname );
int i = FindSoundScript( scriptfile );
if ( i == m_SoundKeyValues.InvalidIndex() )
{
Warning( "CSoundEmitterSystemBase::AddSound( '%s', '%s', ... ), script file not list in manifest '%s'\n",
soundname, scriptfile, MANIFEST_FILE );
return false;
}
MEM_ALLOC_CREDIT();
// More like an update...
if ( IsValidIndex( idx ) )
{
CSoundEntry *entry = m_Sounds[ idx ];
entry->m_bRemoved = false;
entry->m_nScriptFileIndex = i;
entry->m_SoundParams.CopyFrom( params );
m_SoundKeyValues[ i ].dirty = true;
return true;
}
CSoundEntry *pEntry = new CSoundEntry;
pEntry->m_Name = soundname;
pEntry->m_bRemoved = false;
pEntry->m_nScriptFileIndex = i;
pEntry->m_SoundParams.CopyFrom( params );
m_Sounds.Insert( pEntry );
m_SoundKeyValues[ i ].dirty = true;
return true;
}
void CSoundEmitterSystemBase::RemoveSound( const char *soundname )
{
int idx = GetSoundIndex( soundname );
if ( !IsValidIndex( idx ) )
{
Warning( "Can't remove %s, no such sound!\n", soundname );
return;
}
m_Sounds[ idx ]->m_bRemoved = true;
// Mark script as dirty
int scriptindex = m_Sounds[ idx ]->m_nScriptFileIndex;
if ( scriptindex < 0 || scriptindex >= m_SoundKeyValues.Count() )
{
Assert( 0 );
return;
}
m_SoundKeyValues[ scriptindex ].dirty = true;
}
void CSoundEmitterSystemBase::MoveSound( const char *soundname, const char *newscript )
{
int idx = GetSoundIndex( soundname );
if ( !IsValidIndex( idx ) )
{
Warning( "Can't move '%s', no such sound!\n", soundname );
return;
}
int oldscriptindex = m_Sounds[ idx ]->m_nScriptFileIndex;
if ( oldscriptindex < 0 || oldscriptindex >= m_SoundKeyValues.Count() )
{
Assert( 0 );
return;
}
int newscriptindex = FindSoundScript( newscript );
if ( newscriptindex == m_SoundKeyValues.InvalidIndex() )
{
Warning( "CSoundEmitterSystemBase::MoveSound( '%s', '%s' ), script file not list in manifest '%s'\n",
soundname, newscript, MANIFEST_FILE );
return;
}
// No actual change
if ( oldscriptindex == newscriptindex )
{
return;
}
// Move it
m_Sounds[ idx ]->m_nScriptFileIndex = newscriptindex;
// Mark both scripts as dirty
m_SoundKeyValues[ oldscriptindex ].dirty = true;
m_SoundKeyValues[ newscriptindex ].dirty = true;
}
int CSoundEmitterSystemBase::GetNumSoundScripts() const
{
return m_SoundKeyValues.Count();
}
const char *CSoundEmitterSystemBase::GetSoundScriptName( int index ) const
{
if ( index < 0 || index >= m_SoundKeyValues.Count() )
return NULL;
static char fn[ 512 ];
if ( filesystem->String( m_SoundKeyValues[ index ].hFilename, fn, sizeof( fn ) ) )
{
return fn;
}
return "";
}
bool CSoundEmitterSystemBase::IsSoundScriptDirty( int index ) const
{
if ( index < 0 || index >= m_SoundKeyValues.Count() )
return false;
return m_SoundKeyValues[ index ].dirty;
}
void CSoundEmitterSystemBase::SaveChangesToSoundScript( int scriptindex )
{
const char *outfile = GetSoundScriptName( scriptindex );
if ( !outfile )
{
Msg( "CSoundEmitterSystemBase::SaveChangesToSoundScript: No script file for index %i\n", scriptindex );
return;
}
if ( filesystem->FileExists( outfile ) &&
!filesystem->IsFileWritable( outfile ) )
{
Warning( "%s is not writable, can't save data to file\n", outfile );
return;
}
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
// FIXME: Write sound script header
if ( filesystem->FileExists( GAME_SOUNDS_HEADER_BLOCK ) )
{
FileHandle_t header = filesystem->Open( GAME_SOUNDS_HEADER_BLOCK, "rb", NULL );
if ( header != FILESYSTEM_INVALID_HANDLE )
{
int len = filesystem->Size( header );
unsigned char *data = new unsigned char[ len + 1 ];
Q_memset( data, 0, len + 1 );
filesystem->Read( data, len, header );
filesystem->Close( header );
data[ len ] = 0;
char *p = (char *)data;
while ( *p )
{
if ( *p != '\r' )
{
buf.PutChar( *p );
}
++p;
}
delete[] data;
}
buf.Printf( "\n" );
}
int c = GetSoundCount();
for ( int i = 0; i < c; i++ )
{
if ( Q_stricmp( outfile, GetSourceFileForSound( i ) ) )
continue;
// It's marked for deletion, just skip it
if ( m_Sounds[ i ]->m_bRemoved )
continue;
CSoundParametersInternal *p = InternalGetParametersForSound( i );
if ( !p )
continue;
buf.Printf( "\"%s\"\n{\n", GetSoundName( i ) );
buf.Printf( "\t\"channel\"\t\t\"%s\"\n", p->ChannelToString() );
buf.Printf( "\t\"volume\"\t\t\"%s\"\n", p->VolumeToString() );
buf.Printf( "\t\"pitch\"\t\t\t\"%s\"\n", p->PitchToString() );
buf.Printf( "\n" );
buf.Printf( "\t\"soundlevel\"\t\"%s\"\n", p->SoundLevelToString() );
if ( p->OnlyPlayToOwner() )
{
buf.Printf( "\t\"play_to_owner_only\"\t\"1\"\n" );
}
if ( p->GetDelayMsec() != 0 )
{
buf.Printf( "\t\"delay_msec\"\t\"%i\"\n", p->GetDelayMsec() );
}
int totalCount = 0;
int waveCount = p->NumSoundNames();
int convertedCount = p->NumConvertedNames();
totalCount = ( waveCount - 2 * convertedCount ) + convertedCount;
if ( totalCount > 0 )
{
buf.Printf( "\n" );
if ( waveCount == 1 )
{
Assert( p->GetSoundNames()[ 0 ].gender == GENDER_NONE );
buf.Printf( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ 0 ].symbol ) );
}
else if ( convertedCount == 1 )
{
Assert( p->GetConvertedNames()[ 0 ].gender == GENDER_NONE );
buf.Printf( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ 0 ].symbol ) );
}
else
{
buf.Printf( "\t\"rndwave\"\n" );
buf.Printf( "\t{\n" );
int wave;
for ( wave = 0; wave < waveCount; wave++ )
{
// Skip macro-expanded names
if ( p->GetSoundNames()[ wave ].gender != GENDER_NONE )
continue;
buf.Printf( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ wave ].symbol ) );
}
for ( wave = 0; wave < convertedCount; wave++ )
{
buf.Printf( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ wave ].symbol ) );
}
buf.Printf( "\t}\n" );
}
}
buf.Printf( "}\n" );
if ( i != c - 1 )
{
buf.Printf( "\n" );
}
}
// Write it out baby
FileHandle_t fh = filesystem->Open( outfile, "wt" );
if (fh)
{
filesystem->Write( buf.Base(), buf.TellPut(), fh );
filesystem->Close(fh);
// Changed saved successfully
m_SoundKeyValues[ scriptindex ].dirty = false;
}
else
{
Warning( "SceneManager_SaveSoundsToScriptFile: Unable to write file %s!!!\n", outfile );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
// Output : CUtlSymbol
//-----------------------------------------------------------------------------
CUtlSymbol CSoundEmitterSystemBase::AddWaveName( const char *name )
{
return m_Waves.AddString( name );
}
void CSoundEmitterSystemBase::RenameSound( const char *soundname, const char *newname )
{
// Same name?
if ( !Q_stricmp( soundname, newname ) )
{
return;
}
int oldindex = GetSoundIndex( soundname );
if ( !IsValidIndex( oldindex ) )
{
Msg( "Can't rename %s, no such sound\n", soundname );
return;
}
int check = GetSoundIndex( newname );
if ( IsValidIndex( check ) )
{
Msg( "Can't rename %s to %s, new name already in list\n", soundname, newname );
return;
}
MEM_ALLOC_CREDIT();
// Copy out old entry
CSoundEntry *pEntry = m_Sounds[ oldindex ];
// Remove it
m_Sounds.Remove( pEntry );
pEntry->m_Name = newname;
// Re-insert in new spot
m_Sounds.Insert( pEntry );
// Mark associated script as dirty
m_SoundKeyValues[ pEntry->m_nScriptFileIndex ].dirty = true;
}
void CSoundEmitterSystemBase::UpdateSoundParameters( const char *soundname, const CSoundParametersInternal& params )
{
int idx = GetSoundIndex( soundname );
if ( !IsValidIndex( idx ) )
{
Msg( "Can't UpdateSoundParameters %s, no such sound\n", soundname );
return;
}
CSoundEntry *entry = m_Sounds[ idx ];
if ( entry->m_SoundParams == params )
{
// No changes
return;
}
// Update parameters
entry->m_SoundParams.CopyFrom( params );
// Set dirty flag
m_SoundKeyValues[ entry->m_nScriptFileIndex ].dirty = true;
}
bool CSoundEmitterSystemBase::IsUsingGenderToken( char const *soundname )
{
int soundindex = GetSoundIndex( soundname );
if ( soundindex < 0 )
return false;
// Look up the sound level from the soundemitter system
CSoundParametersInternal *params = InternalGetParametersForSound( soundindex );
if ( !params )
return false;
return params->UsesGenderToken();
}
unsigned int CSoundEmitterSystemBase::GetManifestFileTimeChecksum()
{
return m_uManifestPlusScriptChecksum;
}
bool CSoundEmitterSystemBase::GetParametersForSoundEx( const char *soundname, HSOUNDSCRIPTHANDLE& handle, CSoundParameters& params, gender_t gender, bool isbeingemitted /*= false*/ )
{
if ( handle == SOUNDEMITTER_INVALID_HANDLE )
{
handle = GetSoundIndex( soundname );
if ( handle == SOUNDEMITTER_INVALID_HANDLE )
return false;
}
CSoundParametersInternal *internal = InternalGetParametersForSound( (int)handle );
if ( !internal )
{
Assert( 0 );
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: No such sound %s\n", soundname );
return false;
}
params.channel = internal->GetChannel();
params.volume = internal->GetVolume().Random();
params.pitch = internal->GetPitch().Random();
params.pitchlow = internal->GetPitch().start;
params.pitchhigh = params.pitchlow + internal->GetPitch().range;
params.delay_msec = internal->GetDelayMsec();
params.count = internal->NumSoundNames();
params.soundname[ 0 ] = 0;
int bestIndex = FindBestSoundForGender( internal->GetSoundNames(), internal->NumSoundNames(), gender );
if ( bestIndex >= 0 )
{
Q_strncpy( params.soundname, GetWaveName( internal->GetSoundNames()[ bestIndex ].symbol), sizeof( params.soundname ) );
// If we are actually emitting the sound, mark it as not available...
if ( isbeingemitted )
{
internal->GetSoundNames()[ bestIndex ].available = 0;
}
}
params.soundlevel = (soundlevel_t)(int)internal->GetSoundLevel().Random();
params.play_to_owner_only = internal->OnlyPlayToOwner();
if ( !params.soundname[ 0 ] )
{
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has no wave or rndwave key!\n", soundname );
return false;
}
if ( internal->HadMissingWaveFiles() &&
params.soundname[ 0 ] != CHAR_SENTENCE )
{
char testfile[ 256 ];
Q_snprintf( testfile, sizeof( testfile ), "sound/%s", PSkipSoundChars( params.soundname ) );
if ( !filesystem->FileExists( testfile ) )
{
// Prevent repetitive spew...
static CUtlSymbolTable soundWarnings;
char key[ 256 ];
Q_snprintf( key, sizeof( key ), "%s:%s", soundname, params.soundname );
if ( UTL_INVAL_SYMBOL == soundWarnings.Find( key ) )
{
soundWarnings.AddString( key );
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound '%s' references wave '%s' which doesn't exist on disk!\n",
soundname,
params.soundname );
}
return false;
}
}
return true;
}
soundlevel_t CSoundEmitterSystemBase::LookupSoundLevelByHandle( char const *soundname, HSOUNDSCRIPTHANDLE& handle )
{
if ( handle == SOUNDEMITTER_INVALID_HANDLE )
{
handle = (HSOUNDSCRIPTHANDLE)GetSoundIndex( soundname );
if ( handle == SOUNDEMITTER_INVALID_HANDLE )
return SNDLVL_NORM;
}
CSoundParametersInternal *internal = InternalGetParametersForSound( (int)handle );
if ( !internal )
{
return SNDLVL_NORM;
}
return (soundlevel_t)(int)internal->GetSoundLevel().Random();
}
// Called from both client and server (single player) or just one (server only in dedicated server and client only if connected to a remote server)
// Called by LevelInitPreEntity to override sound scripts for the mod with level specific overrides based on custom mapnames, etc.
void CSoundEmitterSystemBase::AddSoundOverrides( char const *scriptfile, bool bPreload /*= false*/ )
{
FileNameHandle_t handle = filesystem->FindOrAddFileName( scriptfile );
if ( m_OverrideFiles.Find( handle ) != m_OverrideFiles.InvalidIndex() )
return;
m_OverrideFiles.AddToTail( handle );
// These are overrides
AddSoundsFromFile( scriptfile, bPreload, true );
}
// Called by either client or server in LevelShutdown to clear out custom overrides
void CSoundEmitterSystemBase::ClearSoundOverrides()
{
int i;
int removed = 0;
for ( UtlHashHandle_t i = m_Sounds.FirstHandle(); i != m_Sounds.InvalidHandle(); )
{
CSoundEntry *entry = m_Sounds[ i ];
if ( entry->IsOverride() )
{
i = m_Sounds.RemoveAndAdvance( i );
++removed;
}
else
{
i = m_Sounds.NextHandle( i );
}
}
if (removed > 0 || m_SavedOverrides.Count() > 0 )
{
Warning( "SoundEmitter: removing map sound overrides [%i to remove, %i to restore]\n",
removed,
m_SavedOverrides.Count() );
}
// Now restore the original entries into the main dictionary.
for ( i = 0; i < m_SavedOverrides.Count(); ++i )
{
CSoundEntry *entry = m_SavedOverrides[ i ];
m_Sounds.Insert( entry );
}
m_SavedOverrides.Purge();
m_OverrideFiles.Purge();
}
CSoundEmitterSystemBase g_SoundEmitterSystemBase;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSoundEmitterSystemBase, ISoundEmitterSystemBase,
SOUNDEMITTERSYSTEM_INTERFACE_VERSION, g_SoundEmitterSystemBase );