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.
1971 lines
52 KiB
1971 lines
52 KiB
//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
|
|
#include <keyvalues.h>
|
|
#include "filesystem.h"
|
|
#include "utldict.h"
|
|
#include "tier2/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 "tier2/tier2.h"
|
|
#include "datacache/iresourceaccesscontrol.h"
|
|
#include "checksum_crc.h"
|
|
#include "tier1/generichash.h"
|
|
|
|
#if IsPlatformX360()
|
|
#include "filesystem/IXboxInstaller.h"
|
|
#endif
|
|
|
|
// 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"
|
|
|
|
BEGIN_DEFINE_LOGGING_CHANNEL( LOG_SOUNDEMITTER_SYSTEM, "SoundEmitterSystem", LCF_CONSOLE_ONLY, LS_MESSAGE );
|
|
END_DEFINE_LOGGING_CHANNEL();
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#define MAX_MEASURED_SOUNDENTRIES 6124
|
|
|
|
// Allocate sound entries in 64k blocks
|
|
DEFINE_FIXEDSIZE_ALLOCATOR( CSoundEntry, 64*1024 / sizeof( CSoundEntry ), CUtlMemoryPool::GROW_SLOW );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CSoundEmitterSystemBase::CSoundEmitterSystemBase() :
|
|
m_ActorGenders( true, 0, 0 ), // Case insensitive
|
|
m_nInitCount( 0 ),
|
|
m_uManifestPlusScriptChecksum( 0 ),
|
|
m_HashToSoundEntry( 0, 0, DefLessFunc( unsigned int ) )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CSoundEmitterSystemBase::First() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : i -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int CSoundEmitterSystemBase::Next( int i ) const
|
|
{
|
|
if ( ++i >= m_Sounds.Count() )
|
|
{
|
|
return m_Sounds.InvalidIndex();
|
|
}
|
|
return i;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CSoundEmitterSystemBase::InvalidIndex() const
|
|
{
|
|
return m_Sounds.InvalidIndex();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// 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 ( !BaseClass::Connect( factory ) )
|
|
return false;
|
|
|
|
if ( !g_pFullFileSystem )
|
|
{
|
|
Error( "The soundemittersystem system requires the filesystem to run!\n" );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CSoundEmitterSystemBase::Disconnect()
|
|
{
|
|
BaseClass::Disconnect();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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.
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
long ft = g_pFullFileSystem->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.
|
|
//-----------------------------------------------------------------------------
|
|
InitReturnVal_t CSoundEmitterSystemBase::Init()
|
|
{
|
|
++m_nInitCount;
|
|
if ( m_nInitCount > 1 )
|
|
return INIT_OK;
|
|
|
|
InitReturnVal_t nRetVal = BaseClass::Init();
|
|
if ( nRetVal != INIT_OK )
|
|
return nRetVal;
|
|
|
|
bool bLoaded = LoadGameSoundManifest();
|
|
return bLoaded ? INIT_OK : INIT_FAILED;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSoundEmitterSystemBase::Shutdown()
|
|
{
|
|
if ( --m_nInitCount > 0 )
|
|
return;
|
|
|
|
ShutdownSounds();
|
|
BaseClass::Shutdown();
|
|
}
|
|
|
|
|
|
bool CSoundEmitterSystemBase::LoadGameSoundManifest()
|
|
{
|
|
/*
|
|
if ( m_SoundKeyValues.Count() > 0 )
|
|
{
|
|
Shutdown();
|
|
}
|
|
*/
|
|
|
|
LoadGlobalActors();
|
|
|
|
m_uManifestPlusScriptChecksum = 0u;
|
|
|
|
CRC32_t crc;
|
|
CRC32_Init( &crc );
|
|
|
|
#if 0
|
|
AccumulateFileNameAndTimestampIntoChecksum( &crc, "scripts/game_sounds_music/game_sounds_music_deathcams.txt" );
|
|
AddSoundsFromFile( "scripts/game_sounds_music/game_sounds_music_deathcams.txt", true, true );
|
|
#endif
|
|
|
|
|
|
KeyValues *manifest = new KeyValues( MANIFEST_FILE );
|
|
if ( g_pFullFileSystem->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, false );
|
|
continue;
|
|
}
|
|
if ( !Q_stricmp( sub->GetName(), "autocache_file" ) )
|
|
{
|
|
AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
|
|
|
|
// Add and always precache and autocache
|
|
AddSoundsFromFile( sub->GetString(), false, true );
|
|
continue;
|
|
}
|
|
else if ( !Q_stricmp( sub->GetName(), "preload_file" ) )
|
|
{
|
|
AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
|
|
|
|
// Add and always precache
|
|
AddSoundsFromFile( sub->GetString(), true, false );
|
|
continue;
|
|
}
|
|
|
|
Warning( "CSoundEmitterSystemBase::BaseInit: Manifest '%s' with bogus file type '%s', expecting 'declare_file' or 'precache_file'\n",
|
|
MANIFEST_FILE, sub->GetName() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( IsPS3() )
|
|
{
|
|
return false;
|
|
}
|
|
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
|
|
|
|
// Helpful code to dump out sound entry lists if we suspect this of being out-of-sync with RTM
|
|
//if ( 0 )
|
|
//{
|
|
// FileHandle_t hSndDumpFile = NULL;
|
|
|
|
// hSndDumpFile = g_pFullFileSystem->Open( "sound_dump.csv", "w" );
|
|
// for ( int i = 0; i < m_Sounds.Count(); ++ i )
|
|
// {
|
|
// int nHash = HashSoundName( m_Sounds[ i ]->m_Name.String() );
|
|
// int nSlot = m_HashToSoundIndex.Find( nHash );
|
|
// nSlot;
|
|
// Assert( nSlot != m_HashToSoundIndex.InvalidIndex() );
|
|
// Assert( m_HashToSoundIndex[ nSlot ] == i );
|
|
// g_pFullFileSystem->FPrintf( hSndDumpFile, "%s,%X\n", m_Sounds[ i ]->m_Name.String(), nHash );
|
|
// }
|
|
|
|
// g_pFullFileSystem->Close( hSndDumpFile );
|
|
//}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CSoundEmitterSystemBase::ShutdownSounds()
|
|
{
|
|
int i;
|
|
m_SoundKeyValues.RemoveAll();
|
|
for ( i = 0; i < m_Sounds.Count(); ++i )
|
|
{
|
|
delete m_Sounds[ i ];
|
|
}
|
|
m_Sounds.Purge();
|
|
|
|
for ( i = 0; i < m_SavedOverrides.Count() ; ++i )
|
|
{
|
|
delete m_SavedOverrides[ i ];
|
|
}
|
|
m_SavedOverrides.Purge();
|
|
m_Waves.RemoveAll();
|
|
m_ActorGenders.Purge();
|
|
m_HashToSoundEntry.Purge();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pName -
|
|
//-----------------------------------------------------------------------------
|
|
int CSoundEmitterSystemBase::GetSoundIndex( const char *pName ) const
|
|
{
|
|
// Use the hash, its faster
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( pName );
|
|
int idx = GetSoundIndexForHash( hash );
|
|
return idx;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CSoundEmitterSystemBase::IsValidIndex( int index )
|
|
{
|
|
return m_Sounds.IsValidIndex( index );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : index -
|
|
// Output : char const
|
|
//-----------------------------------------------------------------------------
|
|
const char *CSoundEmitterSystemBase::GetSoundName( int index )
|
|
{
|
|
if ( !IsValidIndex( index ) )
|
|
return "";
|
|
|
|
return m_Sounds[ index ]->m_Name.String();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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, int &nRandomSeed )
|
|
{
|
|
// Check for recycling of random sounds...
|
|
EnsureAvailableSlotsForGender( pSoundnames, c, gender );
|
|
#if 0
|
|
Msg( "nRandomSeed(1) %i : ", nRandomSeed );
|
|
#endif
|
|
|
|
// because this random int / index came across the network as a 6 bit uint
|
|
// we utilize the 0 slot as "undefined", however 0 is a valid index
|
|
// therefore the shift
|
|
int nAdjRandomSeed = nRandomSeed - 1;
|
|
|
|
if ( c <= 0 )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// have we been passed a valid index
|
|
int idx;
|
|
if ( nAdjRandomSeed >= 0 )
|
|
{
|
|
// extract LSB index
|
|
idx = nAdjRandomSeed % c;
|
|
}
|
|
else
|
|
{
|
|
// make a list of possible indices
|
|
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 )
|
|
{
|
|
|
|
// TODO: morasky, this should get tested at load time?
|
|
Assert( slots.Count() < MAX_SOUND_SEED_VALUE );
|
|
|
|
int nRandomIndex = randomStream->RandomInt( 0, slots.Count() - 1 );
|
|
|
|
idx = slots[ nRandomIndex ];
|
|
|
|
// create random MSB for full res seed
|
|
int nMaxBitDiv = MAX_SOUND_SEED_VALUE / c;
|
|
int nRandomMSB = randomStream->RandomInt( 0, nMaxBitDiv );
|
|
int nRandomSum = ( nRandomMSB * c ) + idx;
|
|
|
|
// we are using 0 = undefined
|
|
nRandomSum += 1;
|
|
if( nRandomSum > MAX_SOUND_SEED_VALUE )
|
|
{
|
|
nRandomSum -= c;
|
|
}
|
|
nRandomSeed = nRandomSum;
|
|
#if 0
|
|
Msg( "nRandomIndex %i : nRandomMSB %i : nRandomSum %i : ", nRandomIndex, nRandomMSB, nRandomSum );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
idx = -1;
|
|
nRandomSeed = 0;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
Msg( "nRandomSeed %i : nAdjRandomSeed %i : nRandomLSB %i : idx %i : %i\n", nRandomSeed, nAdjRandomSeed, nRandomLSB, idx );
|
|
#endif
|
|
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*/ )
|
|
{
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( soundname );
|
|
int index = GetSoundIndexForHash( hash );
|
|
|
|
if ( index == m_Sounds.InvalidIndex() )
|
|
{
|
|
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 );
|
|
|
|
Warning( "CSoundEmitterSystemBase::GetParametersForSound: No such sound %s\n", soundname );
|
|
}
|
|
return GetParametersForSoundEx( "Error", hash, params, gender, isbeingemitted );
|
|
}
|
|
|
|
return GetParametersForSoundEx( soundname, hash, params, gender, isbeingemitted );
|
|
}
|
|
|
|
CSoundParametersInternal *CSoundEmitterSystemBase::InternalGetParametersForSound( int index )
|
|
{
|
|
if ( !m_Sounds.IsValidIndex( 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:
|
|
//-----------------------------------------------------------------------------
|
|
static const char *FindGenderMacro( const char *wavename, int *duration )
|
|
{
|
|
char const *p = Q_stristr( wavename, SOUNDGENDER_MACRO );
|
|
if ( p )
|
|
{
|
|
*duration = SOUNDGENDER_MACRO_LENGTH;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : params -
|
|
// *wavename -
|
|
//-----------------------------------------------------------------------------
|
|
void CSoundEmitterSystemBase::ExpandSoundNameMacros( CSoundParametersInternal& params, char const *wavename )
|
|
{
|
|
int duration = SOUNDGENDER_MACRO_LENGTH;
|
|
const char *p = FindGenderMacro( wavename, &duration );
|
|
|
|
if ( !p )
|
|
{
|
|
AddSoundName( params, wavename, GENDER_NONE );
|
|
return;
|
|
}
|
|
|
|
int offset = p - wavename;
|
|
Assert( offset >= 0 );
|
|
|
|
// 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 );
|
|
|
|
int duration = SOUNDGENDER_MACRO_LENGTH;
|
|
const char *p = FindGenderMacro( in, &duration );
|
|
if ( !p )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Look up actor gender
|
|
if ( gender == GENDER_NONE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int offset = p - in;
|
|
Assert( offset >= 0 );
|
|
|
|
// 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( g_pFullFileSystem, "scripts/global_actors.txt", NULL ) )
|
|
{
|
|
KeyValues *pvkActor;
|
|
for ( pvkActor = allActors->GetFirstSubKey(); pvkActor != NULL; pvkActor = pvkActor->GetNextKey() )
|
|
{
|
|
int idx = m_ActorGenders.Find( pvkActor->GetName() );
|
|
if ( idx == m_ActorGenders.InvalidIndex() )
|
|
{
|
|
if ( m_ActorGenders.Count() + 1 == m_ActorGenders.InvalidIndex() )
|
|
{
|
|
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 ) );
|
|
}
|
|
|
|
int idx = m_ActorGenders.Find( actor );
|
|
if ( idx == m_ActorGenders.InvalidIndex() )
|
|
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 )
|
|
{
|
|
// for special case soundentry version error handling
|
|
const char *pSoundEntryVersionValueStr = kv->GetString( "soundentry_version", "1" );
|
|
int nSoundEntryVersion = 1;
|
|
if( pSoundEntryVersionValueStr && pSoundEntryVersionValueStr[0] )
|
|
{
|
|
nSoundEntryVersion = V_atoi( pSoundEntryVersionValueStr );
|
|
}
|
|
bool bEntryNumHasErrored = false;
|
|
|
|
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 )
|
|
{
|
|
if( params.NumSoundNames() >= MAX_SOUND_RNDWAVE_NUM &&
|
|
nSoundEntryVersion > 1 )
|
|
{
|
|
if( !bEntryNumHasErrored )
|
|
{
|
|
Assert( params.NumSoundNames() >= MAX_SOUND_RNDWAVE_NUM );
|
|
Log_Warning( LOG_SOUNDEMITTER_SYSTEM, "Error: SoundEmitterSystemBase: %s attempting to load too many rndwave soundfiles!\n", soundname );
|
|
}
|
|
bEntryNumHasErrored = true;
|
|
}
|
|
else
|
|
{
|
|
ExpandSoundNameMacros( params, pWaves->GetString() );
|
|
}
|
|
|
|
pWaves = pWaves->GetNextKey();
|
|
}
|
|
}
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) || !Q_strcasecmp( pKey->GetName(), "CompatibilityAttenuation" ) )
|
|
{
|
|
if ( params.GetSoundLevel().start != SNDLVL_NORM || params.GetSoundLevel().range != 0 )
|
|
{
|
|
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has multiple attenuation, CompatabilityAttenuation, and/or soundlevel entries.\n", soundname );
|
|
}
|
|
|
|
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" ) || !Q_strcasecmp( pKey->GetName(), "CompatibilitySoundlevel" ) )
|
|
{
|
|
if ( params.GetSoundLevel().start != SNDLVL_NORM || params.GetSoundLevel().range != 0 )
|
|
{
|
|
DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has multiple attenuation, CompatabilityAttenuation, and/or soundlevel entries.\n", soundname );
|
|
}
|
|
|
|
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() );
|
|
|
|
// 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(), "CompatibilitySoundlevel" );
|
|
if ( bCompatibilityAttenuation )
|
|
{
|
|
if ( params.GetSoundLevel().range != 0 )
|
|
{
|
|
Warning( "CompatibilitySoundlevel 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(), "play_to_owner_only" ) )
|
|
{
|
|
params.SetOnlyPlayToOwner( pKey->GetBool() );
|
|
}
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "delay_msec" ) )
|
|
{
|
|
// Don't allow negative delay
|
|
params.SetDelayMsec( MAX( 0, pKey->GetInt() ) );
|
|
|
|
}
|
|
else if ( !Q_strcasecmp( pKey->GetName(), "soundentry_version" ) )
|
|
{
|
|
params.SetSoundEntryVersion( pKey->GetInt() );
|
|
}
|
|
else if ( !V_strcasecmp( pKey->GetName(), "operator_stacks" ) )
|
|
{
|
|
params.SetOperatorsKV( pKey );
|
|
}
|
|
else if (!V_strcasecmp(pKey->GetName(), "hrtf_follow"))
|
|
{
|
|
params.SetHRTFFollowEntity(pKey->GetBool());
|
|
}
|
|
else if (!V_strcasecmp(pKey->GetName(), "hrtf_bilinear"))
|
|
{
|
|
params.SetHRTFBilinear(pKey->GetBool());
|
|
}
|
|
|
|
|
|
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 bAutoCache, bool bIsOverride /*=false*/ )
|
|
{
|
|
CSoundScriptFile sf;
|
|
sf.hFilename = g_pFullFileSystem->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 ( g_pFullFileSystem->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() + 1 == m_Sounds.InvalidIndex() )
|
|
{
|
|
Warning( "Exceeded maximum number of sound emitter entries\n" );
|
|
break;
|
|
}
|
|
|
|
CSoundEntry *pEntry;
|
|
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
pEntry = new CSoundEntry;
|
|
}
|
|
|
|
const char *pName = pKeys->GetName();
|
|
if ( !V_strlen( pName ) )
|
|
{
|
|
Error( "Syntax Error! Empty named KV block in %s\n", filename );
|
|
}
|
|
|
|
pEntry->m_Name = pName;
|
|
pEntry->m_bRemoved = false;
|
|
pEntry->m_nScriptFileIndex = scriptindex;
|
|
pEntry->m_bIsOverride = bIsOverride;
|
|
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( pEntry->m_Name.String() );
|
|
|
|
if ( bIsOverride )
|
|
{
|
|
++newOverrideCount;
|
|
}
|
|
|
|
bool add = true;
|
|
|
|
int lookup = GetSoundIndexForHash( hash );
|
|
if ( lookup != m_Sounds.InvalidIndex() )
|
|
{
|
|
add = false;
|
|
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 ]->m_bIsOverride )
|
|
{
|
|
m_SavedOverrides.AddToTail( m_Sounds[ lookup ] );
|
|
}
|
|
else
|
|
{
|
|
delete m_Sounds[ lookup ];
|
|
++duplicatedReplacements;
|
|
}
|
|
|
|
InitSoundInternalParameters( pKeys->GetName(), pKeys, pEntry->m_SoundParams );
|
|
pEntry->m_SoundParams.SetShouldPreload( bPreload ); // this gets handled by game code after initting.
|
|
pEntry->m_SoundParams.SetShouldAutoCache( bAutoCache ); // this gets handled by game code after initting.
|
|
|
|
m_Sounds[ lookup ] = pEntry;
|
|
|
|
++replaceCount;
|
|
}
|
|
else
|
|
{
|
|
delete pEntry;
|
|
// DevMsg( "CSoundEmitterSystem::AddSoundsFromFile(%s): Entry %s duplicated, skipping\n", filename, pKeys->GetName() );
|
|
}
|
|
}
|
|
|
|
if ( add )
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
InitSoundInternalParameters( pKeys->GetName(), pKeys, pEntry->m_SoundParams );
|
|
pEntry->m_SoundParams.SetShouldPreload( bPreload ); // this gets handled by game code after initting.
|
|
pEntry->m_SoundParams.SetShouldAutoCache( bAutoCache ); // this gets handled by game code after initting.
|
|
|
|
int idx = m_Sounds.AddToTail( pEntry );
|
|
AddHash( pEntry->m_Name.String(), idx );
|
|
}
|
|
}
|
|
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 )
|
|
{
|
|
Warning( "SoundEmitter: adding map sound overrides from %s [%i total, %i replacements, %i duplicated replacements]\n",
|
|
filename,
|
|
newOverrideCount,
|
|
replaceCount,
|
|
duplicatedReplacements );
|
|
}
|
|
|
|
Assert( scriptindex >= 0 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 ( g_pFullFileSystem->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 ( g_pFullFileSystem->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 = g_pFullFileSystem->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 );
|
|
|
|
idx = m_Sounds.AddToTail( pEntry );
|
|
AddHash( pEntry->m_Name.String(), idx );
|
|
|
|
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 ( g_pFullFileSystem->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 ( g_pFullFileSystem->FileExists( outfile ) &&
|
|
!g_pFullFileSystem->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 ( g_pFullFileSystem->FileExists( GAME_SOUNDS_HEADER_BLOCK ) )
|
|
{
|
|
FileHandle_t header = g_pFullFileSystem->Open( GAME_SOUNDS_HEADER_BLOCK, "rb", NULL );
|
|
if ( header != FILESYSTEM_INVALID_HANDLE )
|
|
{
|
|
int len = g_pFullFileSystem->Size( header );
|
|
|
|
unsigned char *data = new unsigned char[ len + 1 ];
|
|
Q_memset( data, 0, len + 1 );
|
|
|
|
g_pFullFileSystem->Read( data, len, header );
|
|
g_pFullFileSystem->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 = g_pFullFileSystem->Open( outfile, "wt" );
|
|
if (fh)
|
|
{
|
|
g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
|
|
g_pFullFileSystem->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;
|
|
}
|
|
|
|
HSOUNDSCRIPTHASH oldHash = HashSoundName( soundname );
|
|
HSOUNDSCRIPTHASH newHash = HashSoundName( newname );
|
|
|
|
int index = GetSoundIndexForHash( oldHash );
|
|
if ( !IsValidIndex( index ) )
|
|
{
|
|
Msg( "Can't rename %s, no such sound\n", soundname );
|
|
return;
|
|
}
|
|
|
|
int check = GetSoundIndexForHash( newHash );
|
|
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[ index ];
|
|
pEntry->m_Name = newname;
|
|
RemoveHash( soundname );
|
|
AddHash( pEntry->m_Name.String(), index );
|
|
|
|
// 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 )
|
|
{
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( soundname );
|
|
int soundindex = GetSoundIndexForHash( hash );
|
|
|
|
// 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, HSOUNDSCRIPTHASH& handle, CSoundParameters& params, gender_t gender, bool isbeingemitted /*= false*/ )
|
|
{
|
|
if ( g_pResourceAccessControl )
|
|
{
|
|
if ( !g_pResourceAccessControl->IsAccessAllowed( RESOURCE_GAMESOUND, soundname ) )
|
|
return false;
|
|
}
|
|
|
|
if ( handle == SOUNDEMITTER_INVALID_HASH )
|
|
{
|
|
handle = HashSoundName( soundname );
|
|
}
|
|
|
|
int index = GetSoundIndexForHash( handle );
|
|
|
|
CSoundParametersInternal *internal = InternalGetParametersForSound( index );
|
|
|
|
if ( !internal )
|
|
{
|
|
Assert( 0 );
|
|
Warning( "CSoundEmitterSystemBase::GetParametersForSound: No such sound %s\n", soundname );
|
|
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( "Error" );
|
|
int index = GetSoundIndexForHash( hash );
|
|
internal = InternalGetParametersForSound( index );
|
|
if ( !internal )
|
|
return false;
|
|
}
|
|
|
|
int nNumberOfSoundNames = internal->NumSoundNames();
|
|
#if IsPlatformPS3()
|
|
if ( g_pFullFileSystem->IsPrefetchingDone() == false )
|
|
{
|
|
nNumberOfSoundNames = imin( nNumberOfSoundNames, 5 ); // The HDD is not filled yet, we are going to play up to 5 variations max
|
|
}
|
|
#elif IsPlatformX360()
|
|
if ( g_pXboxInstaller->IsFullyInstalled() == false )
|
|
{
|
|
nNumberOfSoundNames = imin( nNumberOfSoundNames, 5 ); // Either there is no HDD, or it has not been fully installed
|
|
}
|
|
#endif
|
|
|
|
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 = nNumberOfSoundNames;
|
|
params.soundname[ 0 ] = 0;
|
|
|
|
params.m_nSoundEntryVersion = (int)internal->GetSoundEntryVersion();
|
|
params.m_hSoundScriptHash = handle;
|
|
params.m_pOperatorsKV = internal->GetOperatorsKV();
|
|
|
|
int bestIndex = FindBestSoundForGender( internal->GetSoundNames(), nNumberOfSoundNames, gender, params.m_nRandomSeed );
|
|
|
|
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();
|
|
params.m_bHRTFBilinear = internal->HasHRTFBilinear();
|
|
params.m_bHRTFFollowEntity = internal->HasHRTFFollowEntity();
|
|
|
|
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 ( !g_pFullFileSystem->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, HSOUNDSCRIPTHASH& handle )
|
|
{
|
|
if ( handle == SOUNDEMITTER_INVALID_HASH )
|
|
{
|
|
handle = HashSoundName( soundname );
|
|
}
|
|
int index = GetSoundIndexForHash( handle );
|
|
|
|
CSoundParametersInternal *internal = InternalGetParametersForSound( index );
|
|
if ( !internal )
|
|
{
|
|
return SNDLVL_NORM;
|
|
}
|
|
|
|
return (soundlevel_t)(int)internal->GetSoundLevel().Random();
|
|
}
|
|
|
|
KeyValues * CSoundEmitterSystemBase::GetOperatorKVByHandle( HSOUNDSCRIPTHASH& handle )
|
|
{
|
|
if ( handle == SOUNDEMITTER_INVALID_HASH )
|
|
{
|
|
return NULL;
|
|
}
|
|
int index = GetSoundIndexForHash( handle );
|
|
|
|
CSoundParametersInternal *internal = InternalGetParametersForSound( index );
|
|
if ( !internal )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return internal->GetOperatorsKV();
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
FileNameHandle_t handle = g_pFullFileSystem->FindOrAddFileName( scriptfile );
|
|
if ( m_OverrideFiles.Find( handle ) != m_OverrideFiles.InvalidIndex() )
|
|
return;
|
|
|
|
m_OverrideFiles.AddToTail( handle );
|
|
// These are overrides and assume bShoudPreload and bShouldAutocache are false
|
|
AddSoundsFromFile( scriptfile, false, false, true );
|
|
}
|
|
|
|
// Called by either client or server in LevelShutdown to clear out custom overrides
|
|
void CSoundEmitterSystemBase::ClearSoundOverrides()
|
|
{
|
|
for ( int i = 0; i < m_SavedOverrides.Count(); ++i )
|
|
{
|
|
CSoundEntry *entry = m_SavedOverrides[ i ];
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( entry->m_Name.String() );
|
|
int idx = GetSoundIndexForHash( hash );
|
|
if ( IsValidIndex( idx ) )
|
|
{
|
|
delete m_Sounds[ idx ];
|
|
m_Sounds[ idx ] = m_SavedOverrides[i];
|
|
}
|
|
}
|
|
|
|
m_SavedOverrides.Purge();
|
|
m_OverrideFiles.Purge();
|
|
}
|
|
|
|
void CSoundEmitterSystemBase::AddHash( char const *pchSoundName, int nIndex )
|
|
{
|
|
Assert( nIndex >= 0 && nIndex < m_Sounds.Count() );
|
|
CSoundEntry *entry = m_Sounds[ nIndex ];
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( pchSoundName );
|
|
|
|
// Check for collisions
|
|
int slot = m_HashToSoundEntry.Find( hash );
|
|
if ( slot != m_HashToSoundEntry.InvalidIndex() )
|
|
{
|
|
Error( "Sound name hash collision! '%s' collides with '%s' %i!", pchSoundName, m_HashToSoundEntry[ slot ].pEntry->m_Name.String(), hash );
|
|
return;
|
|
}
|
|
|
|
soundEntryHash_t soundEntryHash =
|
|
{
|
|
nIndex,
|
|
entry
|
|
};
|
|
m_HashToSoundEntry.Insert( hash, soundEntryHash );
|
|
}
|
|
|
|
void CSoundEmitterSystemBase::RemoveHash( char const *pchSoundName )
|
|
{
|
|
m_HashToSoundEntry.Remove( HashSoundName( pchSoundName ) );
|
|
}
|
|
|
|
char const *CSoundEmitterSystemBase::GetSoundNameForHash( HSOUNDSCRIPTHASH hash ) const
|
|
{
|
|
int slot = m_HashToSoundEntry.Find( hash );
|
|
if ( slot == m_HashToSoundEntry.InvalidIndex() )
|
|
return NULL;
|
|
CSoundEntry *entry = m_HashToSoundEntry[ slot ].pEntry;
|
|
return entry->m_Name.String();
|
|
}
|
|
|
|
int CSoundEmitterSystemBase::GetSoundIndexForHash( HSOUNDSCRIPTHASH hash ) const
|
|
{
|
|
int slot = m_HashToSoundEntry.Find( hash );
|
|
if ( slot == m_HashToSoundEntry.InvalidIndex() )
|
|
return m_Sounds.InvalidIndex();
|
|
return m_HashToSoundEntry[ slot ].soundIndex;
|
|
}
|
|
|
|
#define SOUNDEMITTER_MURMURHASH_SEED ( ( 'D' << 24 ) | ( 'O' << 16 ) | ( 'T' << 8 ) | 'A' )
|
|
|
|
HSOUNDSCRIPTHASH CSoundEmitterSystemBase::HashSoundName( char const *pchSndName ) const
|
|
{
|
|
HSOUNDSCRIPTHASH hash = MurmurHash2LowerCase( pchSndName, SOUNDEMITTER_MURMURHASH_SEED );
|
|
return hash;
|
|
}
|
|
|
|
bool CSoundEmitterSystemBase::IsValidHash( HSOUNDSCRIPTHASH hash ) const
|
|
{
|
|
int idx = m_HashToSoundEntry.Find( hash );
|
|
return idx != m_HashToSoundEntry.InvalidIndex();
|
|
}
|
|
|
|
void CSoundEmitterSystemBase::DescribeSound( char const *soundname )
|
|
{
|
|
HSOUNDSCRIPTHASH hash = HashSoundName( soundname );
|
|
int index = GetSoundIndexForHash( hash );
|
|
if ( index == m_Sounds.InvalidIndex() )
|
|
{
|
|
Msg( "SoundEmitterSystemBase::DescribeSound: No such sound %s\n", soundname );
|
|
return;
|
|
}
|
|
|
|
CSoundParametersInternal *p = InternalGetParametersForSound( index );
|
|
if ( !p )
|
|
{
|
|
Msg( "SoundEmitterSystemBase::DescribeSound: No such sound %s\n", soundname );
|
|
return;
|
|
}
|
|
|
|
Msg( "\"%s\"\n{\n", GetSoundName( index ) );
|
|
|
|
Msg( "\t\"channel\"\t\t\"%s\"\n", p->ChannelToString() );
|
|
Msg( "\t\"volume\"\t\t\"%s\"\n", p->VolumeToString() );
|
|
Msg( "\t\"pitch\"\t\t\t\"%s\"\n", p->PitchToString() );
|
|
Msg( "\n" );
|
|
Msg( "\t\"soundlevel\"\t\"%s\"\n", p->SoundLevelToString() );
|
|
|
|
if ( p->OnlyPlayToOwner() )
|
|
{
|
|
Msg( "\t\"play_to_owner_only\"\t\"1\"\n" );
|
|
}
|
|
|
|
if ( p->GetDelayMsec() != 0 )
|
|
{
|
|
Msg( "\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 )
|
|
{
|
|
Msg( "\n" );
|
|
|
|
if ( waveCount == 1 )
|
|
{
|
|
Assert( p->GetSoundNames()[ 0 ].gender == GENDER_NONE );
|
|
Msg( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ 0 ].symbol ) );
|
|
}
|
|
else if ( convertedCount == 1 )
|
|
{
|
|
Assert( p->GetConvertedNames()[ 0 ].gender == GENDER_NONE );
|
|
Msg( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ 0 ].symbol ) );
|
|
}
|
|
else
|
|
{
|
|
Msg( "\t\"rndwave\"\n" );
|
|
Msg( "\t{\n" );
|
|
|
|
int wave;
|
|
for ( wave = 0; wave < waveCount; wave++ )
|
|
{
|
|
// Skip macro-expanded names
|
|
if ( p->GetSoundNames()[ wave ].gender != GENDER_NONE )
|
|
continue;
|
|
|
|
Msg( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ wave ].symbol ) );
|
|
}
|
|
for ( wave = 0; wave < convertedCount; wave++ )
|
|
{
|
|
Msg( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ wave ].symbol ) );
|
|
}
|
|
|
|
Msg( "\t}\n" );
|
|
}
|
|
}
|
|
|
|
Msg( "}\n" );
|
|
}
|
|
|
|
void CSoundEmitterSystemBase::Flush()
|
|
{
|
|
ShutdownSounds();
|
|
LoadGameSoundManifest();
|
|
}
|
|
|
|
CSoundEmitterSystemBase g_SoundEmitterSystemBase;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSoundEmitterSystemBase, ISoundEmitterSystemBase,
|
|
SOUNDEMITTERSYSTEM_INTERFACE_VERSION, g_SoundEmitterSystemBase );
|