Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1971 lines
52 KiB

  1. //===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include <keyvalues.h>
  7. #include "filesystem.h"
  8. #include "utldict.h"
  9. #include "tier2/interval.h"
  10. #include "engine/IEngineSound.h"
  11. #include "soundemittersystembase.h"
  12. #include "utlbuffer.h"
  13. #include "soundchars.h"
  14. #include "vstdlib/random.h"
  15. #include "checksum_crc.h"
  16. #include "SoundEmitterSystem/isoundemittersystembase.h"
  17. #include "tier2/tier2.h"
  18. #include "datacache/iresourceaccesscontrol.h"
  19. #include "checksum_crc.h"
  20. #include "tier1/generichash.h"
  21. #if IsPlatformX360()
  22. #include "filesystem/IXboxInstaller.h"
  23. #endif
  24. // memdbgon must be the last include file in a .cpp file!!!
  25. #include "tier0/memdbgon.h"
  26. #define MANIFEST_FILE "scripts/game_sounds_manifest.txt"
  27. #define GAME_SOUNDS_HEADER_BLOCK "scripts/game_sounds_header.txt"
  28. BEGIN_DEFINE_LOGGING_CHANNEL( LOG_SOUNDEMITTER_SYSTEM, "SoundEmitterSystem", LCF_CONSOLE_ONLY, LS_MESSAGE );
  29. END_DEFINE_LOGGING_CHANNEL();
  30. //-----------------------------------------------------------------------------
  31. #define MAX_MEASURED_SOUNDENTRIES 6124
  32. // Allocate sound entries in 64k blocks
  33. DEFINE_FIXEDSIZE_ALLOCATOR( CSoundEntry, 64*1024 / sizeof( CSoundEntry ), CUtlMemoryPool::GROW_SLOW );
  34. //-----------------------------------------------------------------------------
  35. // Purpose:
  36. //-----------------------------------------------------------------------------
  37. CSoundEmitterSystemBase::CSoundEmitterSystemBase() :
  38. m_ActorGenders( true, 0, 0 ), // Case insensitive
  39. m_nInitCount( 0 ),
  40. m_uManifestPlusScriptChecksum( 0 ),
  41. m_HashToSoundEntry( 0, 0, DefLessFunc( unsigned int ) )
  42. {
  43. }
  44. //-----------------------------------------------------------------------------
  45. // Purpose:
  46. // Output : int
  47. //-----------------------------------------------------------------------------
  48. int CSoundEmitterSystemBase::First() const
  49. {
  50. return 0;
  51. }
  52. //-----------------------------------------------------------------------------
  53. // Purpose:
  54. // Input : i -
  55. // Output : int
  56. //-----------------------------------------------------------------------------
  57. int CSoundEmitterSystemBase::Next( int i ) const
  58. {
  59. if ( ++i >= m_Sounds.Count() )
  60. {
  61. return m_Sounds.InvalidIndex();
  62. }
  63. return i;
  64. }
  65. //-----------------------------------------------------------------------------
  66. // Purpose:
  67. //-----------------------------------------------------------------------------
  68. int CSoundEmitterSystemBase::InvalidIndex() const
  69. {
  70. return m_Sounds.InvalidIndex();
  71. }
  72. //-----------------------------------------------------------------------------
  73. //
  74. // implementation of IUniformRandomStream
  75. //
  76. //-----------------------------------------------------------------------------
  77. class CSoundEmitterUniformRandomStream : public IUniformRandomStream
  78. {
  79. public:
  80. // Sets the seed of the random number generator
  81. void SetSeed( int iSeed )
  82. {
  83. // Never call this from the client or game!
  84. Assert(0);
  85. }
  86. // Generates random numbers
  87. float RandomFloat( float flMinVal = 0.0f, float flMaxVal = 1.0f )
  88. {
  89. return ::RandomFloat( flMinVal, flMaxVal );
  90. }
  91. int RandomInt( int iMinVal, int iMaxVal )
  92. {
  93. return ::RandomInt( iMinVal, iMaxVal );
  94. }
  95. float RandomFloatExp( float flMinVal = 0.0f, float flMaxVal = 1.0f, float flExponent = 1.0f )
  96. {
  97. return ::RandomFloatExp( flMinVal, flMaxVal, flExponent );
  98. }
  99. };
  100. static CSoundEmitterUniformRandomStream g_RandomStream;
  101. IUniformRandomStream *randomStream = &g_RandomStream;
  102. //-----------------------------------------------------------------------------
  103. // Connect, disconnect
  104. //-----------------------------------------------------------------------------
  105. bool CSoundEmitterSystemBase::Connect( CreateInterfaceFn factory )
  106. {
  107. if ( !BaseClass::Connect( factory ) )
  108. return false;
  109. if ( !g_pFullFileSystem )
  110. {
  111. Error( "The soundemittersystem system requires the filesystem to run!\n" );
  112. return false;
  113. }
  114. return true;
  115. }
  116. void CSoundEmitterSystemBase::Disconnect()
  117. {
  118. BaseClass::Disconnect();
  119. }
  120. //-----------------------------------------------------------------------------
  121. // Query interface
  122. //-----------------------------------------------------------------------------
  123. void *CSoundEmitterSystemBase::QueryInterface( const char *pInterfaceName )
  124. {
  125. // Loading the engine DLL mounts *all* soundemitter interfaces
  126. CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary
  127. return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing.
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Purpose: Helper for checksuming script files and manifest to determine if soundname caches
  131. // need to be blown away.
  132. // Input : *crc -
  133. // *filename -
  134. // Output : static void
  135. //-----------------------------------------------------------------------------
  136. static void AccumulateFileNameAndTimestampIntoChecksum( CRC32_t *crc, char const *filename )
  137. {
  138. if ( IsX360() )
  139. {
  140. // this is an expensive i/o operation due to search path fall through
  141. // 360 doesn't need or use the checksums
  142. return;
  143. }
  144. long ft = g_pFullFileSystem->GetFileTime( filename, "GAME" );
  145. CRC32_ProcessBuffer( crc, &ft, sizeof( ft ) );
  146. CRC32_ProcessBuffer( crc, filename, Q_strlen( filename ) );
  147. }
  148. //-----------------------------------------------------------------------------
  149. // Purpose:
  150. // Output : Returns true on success, false on failure.
  151. //-----------------------------------------------------------------------------
  152. InitReturnVal_t CSoundEmitterSystemBase::Init()
  153. {
  154. ++m_nInitCount;
  155. if ( m_nInitCount > 1 )
  156. return INIT_OK;
  157. InitReturnVal_t nRetVal = BaseClass::Init();
  158. if ( nRetVal != INIT_OK )
  159. return nRetVal;
  160. bool bLoaded = LoadGameSoundManifest();
  161. return bLoaded ? INIT_OK : INIT_FAILED;
  162. }
  163. //-----------------------------------------------------------------------------
  164. // Purpose:
  165. //-----------------------------------------------------------------------------
  166. void CSoundEmitterSystemBase::Shutdown()
  167. {
  168. if ( --m_nInitCount > 0 )
  169. return;
  170. ShutdownSounds();
  171. BaseClass::Shutdown();
  172. }
  173. bool CSoundEmitterSystemBase::LoadGameSoundManifest()
  174. {
  175. /*
  176. if ( m_SoundKeyValues.Count() > 0 )
  177. {
  178. Shutdown();
  179. }
  180. */
  181. LoadGlobalActors();
  182. m_uManifestPlusScriptChecksum = 0u;
  183. CRC32_t crc;
  184. CRC32_Init( &crc );
  185. #if 0
  186. AccumulateFileNameAndTimestampIntoChecksum( &crc, "scripts/game_sounds_music/game_sounds_music_deathcams.txt" );
  187. AddSoundsFromFile( "scripts/game_sounds_music/game_sounds_music_deathcams.txt", true, true );
  188. #endif
  189. KeyValues *manifest = new KeyValues( MANIFEST_FILE );
  190. if ( g_pFullFileSystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDEMITTER, MANIFEST_FILE, "GAME" ) )
  191. {
  192. AccumulateFileNameAndTimestampIntoChecksum( &crc, MANIFEST_FILE );
  193. for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
  194. {
  195. if ( !Q_stricmp( sub->GetName(), "precache_file" ) )
  196. {
  197. AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
  198. // Add and always precache
  199. AddSoundsFromFile( sub->GetString(), false, false );
  200. continue;
  201. }
  202. if ( !Q_stricmp( sub->GetName(), "autocache_file" ) )
  203. {
  204. AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
  205. // Add and always precache and autocache
  206. AddSoundsFromFile( sub->GetString(), false, true );
  207. continue;
  208. }
  209. else if ( !Q_stricmp( sub->GetName(), "preload_file" ) )
  210. {
  211. AccumulateFileNameAndTimestampIntoChecksum( &crc, sub->GetString() );
  212. // Add and always precache
  213. AddSoundsFromFile( sub->GetString(), true, false );
  214. continue;
  215. }
  216. Warning( "CSoundEmitterSystemBase::BaseInit: Manifest '%s' with bogus file type '%s', expecting 'declare_file' or 'precache_file'\n",
  217. MANIFEST_FILE, sub->GetName() );
  218. }
  219. }
  220. else
  221. {
  222. if( IsPS3() )
  223. {
  224. return false;
  225. }
  226. else
  227. {
  228. Error( "Unable to load manifest file '%s'\n", MANIFEST_FILE );
  229. }
  230. }
  231. manifest->deleteThis();
  232. CRC32_Final( &crc );
  233. m_uManifestPlusScriptChecksum = ( unsigned int )crc;
  234. // Only print total once, on server
  235. #if !defined( CLIENT_DLL ) && !defined( FACEPOSER )
  236. DevMsg( 1, "CSoundEmitterSystem: Registered %i sounds\n", m_Sounds.Count() );
  237. #endif
  238. // Helpful code to dump out sound entry lists if we suspect this of being out-of-sync with RTM
  239. //if ( 0 )
  240. //{
  241. // FileHandle_t hSndDumpFile = NULL;
  242. // hSndDumpFile = g_pFullFileSystem->Open( "sound_dump.csv", "w" );
  243. // for ( int i = 0; i < m_Sounds.Count(); ++ i )
  244. // {
  245. // int nHash = HashSoundName( m_Sounds[ i ]->m_Name.String() );
  246. // int nSlot = m_HashToSoundIndex.Find( nHash );
  247. // nSlot;
  248. // Assert( nSlot != m_HashToSoundIndex.InvalidIndex() );
  249. // Assert( m_HashToSoundIndex[ nSlot ] == i );
  250. // g_pFullFileSystem->FPrintf( hSndDumpFile, "%s,%X\n", m_Sounds[ i ]->m_Name.String(), nHash );
  251. // }
  252. // g_pFullFileSystem->Close( hSndDumpFile );
  253. //}
  254. return true;
  255. }
  256. void CSoundEmitterSystemBase::ShutdownSounds()
  257. {
  258. int i;
  259. m_SoundKeyValues.RemoveAll();
  260. for ( i = 0; i < m_Sounds.Count(); ++i )
  261. {
  262. delete m_Sounds[ i ];
  263. }
  264. m_Sounds.Purge();
  265. for ( i = 0; i < m_SavedOverrides.Count() ; ++i )
  266. {
  267. delete m_SavedOverrides[ i ];
  268. }
  269. m_SavedOverrides.Purge();
  270. m_Waves.RemoveAll();
  271. m_ActorGenders.Purge();
  272. m_HashToSoundEntry.Purge();
  273. }
  274. //-----------------------------------------------------------------------------
  275. // Purpose:
  276. // Input : *pName -
  277. //-----------------------------------------------------------------------------
  278. int CSoundEmitterSystemBase::GetSoundIndex( const char *pName ) const
  279. {
  280. // Use the hash, its faster
  281. HSOUNDSCRIPTHASH hash = HashSoundName( pName );
  282. int idx = GetSoundIndexForHash( hash );
  283. return idx;
  284. }
  285. //-----------------------------------------------------------------------------
  286. // Purpose:
  287. // Input : index -
  288. // Output : Returns true on success, false on failure.
  289. //-----------------------------------------------------------------------------
  290. bool CSoundEmitterSystemBase::IsValidIndex( int index )
  291. {
  292. return m_Sounds.IsValidIndex( index );
  293. }
  294. //-----------------------------------------------------------------------------
  295. // Purpose:
  296. // Input : index -
  297. // Output : char const
  298. //-----------------------------------------------------------------------------
  299. const char *CSoundEmitterSystemBase::GetSoundName( int index )
  300. {
  301. if ( !IsValidIndex( index ) )
  302. return "";
  303. return m_Sounds[ index ]->m_Name.String();
  304. }
  305. //-----------------------------------------------------------------------------
  306. // Purpose:
  307. // Output : int
  308. //-----------------------------------------------------------------------------
  309. int CSoundEmitterSystemBase::GetSoundCount( void )
  310. {
  311. return m_Sounds.Count();
  312. }
  313. void CSoundEmitterSystemBase::EnsureAvailableSlotsForGender( SoundFile *pSoundnames, int c, gender_t gender )
  314. {
  315. int i;
  316. if ( c <= 0 )
  317. {
  318. return;
  319. }
  320. CUtlVector< int > slots;
  321. bool needsreset = false;
  322. for ( i = 0; i < c; i++ )
  323. {
  324. if ( pSoundnames[ i ].gender != gender )
  325. continue;
  326. // There was at least one match for the gender
  327. needsreset = true;
  328. // This sound is unavailable
  329. if ( !pSoundnames[ i ].available )
  330. continue;
  331. slots.AddToTail( i );
  332. }
  333. if ( slots.Count() == 0 && needsreset )
  334. {
  335. // Reset all slots for the specified gender!!!
  336. for ( i = 0; i < c; i++ )
  337. {
  338. if ( pSoundnames[ i ].gender != gender )
  339. continue;
  340. pSoundnames[ i ].available = true;
  341. }
  342. }
  343. }
  344. //-----------------------------------------------------------------------------
  345. // Purpose:
  346. // Input : gender -
  347. // soundnames -
  348. //-----------------------------------------------------------------------------
  349. int CSoundEmitterSystemBase::FindBestSoundForGender( SoundFile *pSoundnames, int c, gender_t gender, int &nRandomSeed )
  350. {
  351. // Check for recycling of random sounds...
  352. EnsureAvailableSlotsForGender( pSoundnames, c, gender );
  353. #if 0
  354. Msg( "nRandomSeed(1) %i : ", nRandomSeed );
  355. #endif
  356. // because this random int / index came across the network as a 6 bit uint
  357. // we utilize the 0 slot as "undefined", however 0 is a valid index
  358. // therefore the shift
  359. int nAdjRandomSeed = nRandomSeed - 1;
  360. if ( c <= 0 )
  361. {
  362. return -1;
  363. }
  364. // have we been passed a valid index
  365. int idx;
  366. if ( nAdjRandomSeed >= 0 )
  367. {
  368. // extract LSB index
  369. idx = nAdjRandomSeed % c;
  370. }
  371. else
  372. {
  373. // make a list of possible indices
  374. CUtlVector< int > slots;
  375. for ( int i = 0; i < c; i++ )
  376. {
  377. if ( pSoundnames[ i ].gender == gender &&
  378. ( pSoundnames[ i ].available ) )
  379. {
  380. slots.AddToTail( i );
  381. }
  382. }
  383. if ( slots.Count() >= 1 )
  384. {
  385. // TODO: morasky, this should get tested at load time?
  386. Assert( slots.Count() < MAX_SOUND_SEED_VALUE );
  387. int nRandomIndex = randomStream->RandomInt( 0, slots.Count() - 1 );
  388. idx = slots[ nRandomIndex ];
  389. // create random MSB for full res seed
  390. int nMaxBitDiv = MAX_SOUND_SEED_VALUE / c;
  391. int nRandomMSB = randomStream->RandomInt( 0, nMaxBitDiv );
  392. int nRandomSum = ( nRandomMSB * c ) + idx;
  393. // we are using 0 = undefined
  394. nRandomSum += 1;
  395. if( nRandomSum > MAX_SOUND_SEED_VALUE )
  396. {
  397. nRandomSum -= c;
  398. }
  399. nRandomSeed = nRandomSum;
  400. #if 0
  401. Msg( "nRandomIndex %i : nRandomMSB %i : nRandomSum %i : ", nRandomIndex, nRandomMSB, nRandomSum );
  402. #endif
  403. }
  404. else
  405. {
  406. idx = -1;
  407. nRandomSeed = 0;
  408. }
  409. }
  410. #if 0
  411. Msg( "nRandomSeed %i : nAdjRandomSeed %i : nRandomLSB %i : idx %i : %i\n", nRandomSeed, nAdjRandomSeed, nRandomLSB, idx );
  412. #endif
  413. return idx;
  414. // int idx = randomStream->RandomInt( 0, c - 1 );
  415. // return idx;
  416. }
  417. //-----------------------------------------------------------------------------
  418. // Purpose:
  419. // Input : *soundname -
  420. // params -
  421. // Output : Returns true on success, false on failure.
  422. //-----------------------------------------------------------------------------
  423. bool CSoundEmitterSystemBase::GetParametersForSound( const char *soundname, CSoundParameters& params, gender_t gender, bool isbeingemitted /*= false*/ )
  424. {
  425. HSOUNDSCRIPTHASH hash = HashSoundName( soundname );
  426. int index = GetSoundIndexForHash( hash );
  427. if ( index == m_Sounds.InvalidIndex() )
  428. {
  429. static CUtlSymbolTable soundWarnings;
  430. char key[ 256 ];
  431. Q_snprintf( key, sizeof( key ), "%s:%s", soundname, params.soundname );
  432. if ( UTL_INVAL_SYMBOL == soundWarnings.Find( key ) )
  433. {
  434. soundWarnings.AddString( key );
  435. Warning( "CSoundEmitterSystemBase::GetParametersForSound: No such sound %s\n", soundname );
  436. }
  437. return GetParametersForSoundEx( "Error", hash, params, gender, isbeingemitted );
  438. }
  439. return GetParametersForSoundEx( soundname, hash, params, gender, isbeingemitted );
  440. }
  441. CSoundParametersInternal *CSoundEmitterSystemBase::InternalGetParametersForSound( int index )
  442. {
  443. if ( !m_Sounds.IsValidIndex( index ) )
  444. {
  445. Assert( !"CSoundEmitterSystemBase::InternalGetParametersForSound: Bogus index" );
  446. return NULL;
  447. }
  448. return &m_Sounds[ index ]->m_SoundParams;
  449. }
  450. static void SplitName( char const *input, int splitchar, int splitlen, char *before, int beforelen, char *after, int afterlen )
  451. {
  452. char const *in = input;
  453. char *out = before;
  454. int c = 0;
  455. int l = 0;
  456. int maxl = beforelen;
  457. while ( *in )
  458. {
  459. if ( c == splitchar )
  460. {
  461. while ( --splitlen >= 0 )
  462. {
  463. in++;
  464. }
  465. *out = 0;
  466. out = after;
  467. maxl = afterlen;
  468. c++;
  469. continue;
  470. }
  471. if ( l >= maxl )
  472. {
  473. in++;
  474. c++;
  475. continue;
  476. }
  477. *out++ = *in++;
  478. l++;
  479. c++;
  480. }
  481. *out = 0;
  482. }
  483. //-----------------------------------------------------------------------------
  484. // Purpose:
  485. // Input : params -
  486. // *wavename -
  487. // gender -
  488. //-----------------------------------------------------------------------------
  489. void CSoundEmitterSystemBase::AddSoundName( CSoundParametersInternal& params, char const *wavename, gender_t gender )
  490. {
  491. CUtlSymbol sym = m_Waves.AddString( wavename );
  492. SoundFile e;
  493. e.symbol = sym;
  494. e.gender = gender;
  495. if ( gender != GENDER_NONE )
  496. {
  497. params.SetUsesGenderToken( true );
  498. }
  499. params.AddSoundName( e );
  500. }
  501. //-----------------------------------------------------------------------------
  502. // Purpose:
  503. //-----------------------------------------------------------------------------
  504. static const char *FindGenderMacro( const char *wavename, int *duration )
  505. {
  506. char const *p = Q_stristr( wavename, SOUNDGENDER_MACRO );
  507. if ( p )
  508. {
  509. *duration = SOUNDGENDER_MACRO_LENGTH;
  510. }
  511. return p;
  512. }
  513. //-----------------------------------------------------------------------------
  514. // Purpose:
  515. // Input : params -
  516. // *wavename -
  517. //-----------------------------------------------------------------------------
  518. void CSoundEmitterSystemBase::ExpandSoundNameMacros( CSoundParametersInternal& params, char const *wavename )
  519. {
  520. int duration = SOUNDGENDER_MACRO_LENGTH;
  521. const char *p = FindGenderMacro( wavename, &duration );
  522. if ( !p )
  523. {
  524. AddSoundName( params, wavename, GENDER_NONE );
  525. return;
  526. }
  527. int offset = p - wavename;
  528. Assert( offset >= 0 );
  529. // Create a "male" and "female" version of the sound
  530. char before[ 256 ], after[ 256 ];
  531. Q_memset( before, 0, sizeof( before ) );
  532. Q_memset( after, 0, sizeof( after ) );
  533. SplitName( wavename, offset, duration, before, sizeof( before ), after, sizeof( after ) );
  534. char temp[ 256 ];
  535. Q_snprintf( temp, sizeof( temp ), "%s%s%s", before, "male", after );
  536. AddSoundName( params, temp, GENDER_MALE );
  537. Q_snprintf( temp, sizeof( temp ), "%s%s%s", before, "female", after );
  538. AddSoundName( params, temp, GENDER_FEMALE );
  539. // Add the conversion entry with the gender tags still in it
  540. CUtlSymbol sym = m_Waves.AddString( wavename );
  541. SoundFile e;
  542. e.symbol = sym;
  543. e.gender = GENDER_NONE;
  544. params.AddConvertedName( e );
  545. }
  546. void CSoundEmitterSystemBase::GenderExpandString( gender_t gender, char const *in, char *out, int maxlen )
  547. {
  548. // Assume the worst
  549. Q_strncpy( out, in, maxlen );
  550. int duration = SOUNDGENDER_MACRO_LENGTH;
  551. const char *p = FindGenderMacro( in, &duration );
  552. if ( !p )
  553. {
  554. return;
  555. }
  556. // Look up actor gender
  557. if ( gender == GENDER_NONE )
  558. {
  559. return;
  560. }
  561. int offset = p - in;
  562. Assert( offset >= 0 );
  563. // Create a "male" and "female" version of the sound
  564. char before[ 256 ], after[ 256 ];
  565. Q_memset( before, 0, sizeof( before ) );
  566. Q_memset( after, 0, sizeof( after ) );
  567. SplitName( in, offset, duration, before, sizeof( before ), after, sizeof( after ) );
  568. switch ( gender )
  569. {
  570. default:
  571. case GENDER_NONE:
  572. {
  573. Assert( !"CSoundEmitterSystemBase::GenderExpandString: expecting MALE or FEMALE!" );
  574. }
  575. break;
  576. case GENDER_MALE:
  577. {
  578. Q_snprintf( out, maxlen, "%s%s%s", before, "male", after );
  579. }
  580. break;
  581. case GENDER_FEMALE:
  582. {
  583. Q_snprintf( out, maxlen, "%s%s%s", before, "female", after );
  584. }
  585. break;
  586. }
  587. }
  588. //-----------------------------------------------------------------------------
  589. // Purpose:
  590. // Input : *actorname -
  591. // *in -
  592. // *out -
  593. // maxlen -
  594. //-----------------------------------------------------------------------------
  595. void CSoundEmitterSystemBase::GenderExpandString( char const *actormodel, char const *in, char *out, int maxlen )
  596. {
  597. gender_t gender = GetActorGender( actormodel );
  598. GenderExpandString( gender, in, out, maxlen );
  599. }
  600. void CSoundEmitterSystemBase::LoadGlobalActors()
  601. {
  602. // Now load the global actor list from the scripts/globalactors.txt file
  603. KeyValues *allActors = NULL;
  604. allActors = new KeyValues( "allactors" );
  605. if ( allActors->LoadFromFile( g_pFullFileSystem, "scripts/global_actors.txt", NULL ) )
  606. {
  607. KeyValues *pvkActor;
  608. for ( pvkActor = allActors->GetFirstSubKey(); pvkActor != NULL; pvkActor = pvkActor->GetNextKey() )
  609. {
  610. int idx = m_ActorGenders.Find( pvkActor->GetName() );
  611. if ( idx == m_ActorGenders.InvalidIndex() )
  612. {
  613. if ( m_ActorGenders.Count() + 1 == m_ActorGenders.InvalidIndex() )
  614. {
  615. Warning( "Exceeded max number of actors in scripts/global_actors.txt\n" );
  616. break;
  617. }
  618. gender_t gender = GENDER_NONE;
  619. if ( !Q_stricmp( pvkActor->GetString(), "male" ) )
  620. {
  621. gender = GENDER_MALE;
  622. }
  623. else if (!Q_stricmp( pvkActor->GetString(), "female" ) )
  624. {
  625. gender = GENDER_FEMALE;
  626. }
  627. m_ActorGenders.Insert( pvkActor->GetName(), gender );
  628. }
  629. }
  630. }
  631. allActors->deleteThis();
  632. }
  633. //-----------------------------------------------------------------------------
  634. // Purpose:
  635. // Input : *actorname -
  636. // Output : gender_t
  637. //-----------------------------------------------------------------------------
  638. gender_t CSoundEmitterSystemBase::GetActorGender( char const *actormodel )
  639. {
  640. char actor[ 256 ];
  641. actor[0] = 0;
  642. if ( actormodel )
  643. {
  644. Q_FileBase( actormodel, actor, sizeof( actor ) );
  645. }
  646. int idx = m_ActorGenders.Find( actor );
  647. if ( idx == m_ActorGenders.InvalidIndex() )
  648. return GENDER_NONE;
  649. return m_ActorGenders[ idx ];
  650. }
  651. //-----------------------------------------------------------------------------
  652. // Purpose:
  653. // Input : *soundname -
  654. // params -
  655. // Output : Returns true on success, false on failure.
  656. //-----------------------------------------------------------------------------
  657. bool CSoundEmitterSystemBase::InitSoundInternalParameters( const char *soundname, KeyValues *kv, CSoundParametersInternal& params )
  658. {
  659. // for special case soundentry version error handling
  660. const char *pSoundEntryVersionValueStr = kv->GetString( "soundentry_version", "1" );
  661. int nSoundEntryVersion = 1;
  662. if( pSoundEntryVersionValueStr && pSoundEntryVersionValueStr[0] )
  663. {
  664. nSoundEntryVersion = V_atoi( pSoundEntryVersionValueStr );
  665. }
  666. bool bEntryNumHasErrored = false;
  667. KeyValues *pKey = kv->GetFirstSubKey();
  668. while ( pKey )
  669. {
  670. if ( !Q_strcasecmp( pKey->GetName(), "channel" ) )
  671. {
  672. params.ChannelFromString( pKey->GetString() );
  673. }
  674. else if ( !Q_strcasecmp( pKey->GetName(), "volume" ) )
  675. {
  676. params.VolumeFromString( pKey->GetString() );
  677. }
  678. else if ( !Q_strcasecmp( pKey->GetName(), "pitch" ) )
  679. {
  680. params.PitchFromString( pKey->GetString() );
  681. }
  682. else if ( !Q_strcasecmp( pKey->GetName(), "wave" ) )
  683. {
  684. ExpandSoundNameMacros( params, pKey->GetString() );
  685. }
  686. else if ( !Q_strcasecmp( pKey->GetName(), "rndwave" ) )
  687. {
  688. KeyValues *pWaves = pKey->GetFirstSubKey();
  689. while ( pWaves )
  690. {
  691. if( params.NumSoundNames() >= MAX_SOUND_RNDWAVE_NUM &&
  692. nSoundEntryVersion > 1 )
  693. {
  694. if( !bEntryNumHasErrored )
  695. {
  696. Assert( params.NumSoundNames() >= MAX_SOUND_RNDWAVE_NUM );
  697. Log_Warning( LOG_SOUNDEMITTER_SYSTEM, "Error: SoundEmitterSystemBase: %s attempting to load too many rndwave soundfiles!\n", soundname );
  698. }
  699. bEntryNumHasErrored = true;
  700. }
  701. else
  702. {
  703. ExpandSoundNameMacros( params, pWaves->GetString() );
  704. }
  705. pWaves = pWaves->GetNextKey();
  706. }
  707. }
  708. else if ( !Q_strcasecmp( pKey->GetName(), "attenuation" ) || !Q_strcasecmp( pKey->GetName(), "CompatibilityAttenuation" ) )
  709. {
  710. if ( params.GetSoundLevel().start != SNDLVL_NORM || params.GetSoundLevel().range != 0 )
  711. {
  712. DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has multiple attenuation, CompatabilityAttenuation, and/or soundlevel entries.\n", soundname );
  713. }
  714. if ( !Q_strncasecmp( pKey->GetString(), "SNDLVL_", strlen( "SNDLVL_" ) ) )
  715. {
  716. DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has \"attenuation\" with %s value!\n",
  717. soundname, pKey->GetString() );
  718. }
  719. if ( !Q_strncasecmp( pKey->GetString(), "ATTN_", strlen( "ATTN_" ) ) )
  720. {
  721. params.SetSoundLevel( ATTN_TO_SNDLVL( TranslateAttenuation( pKey->GetString() ) ) );
  722. }
  723. else
  724. {
  725. interval_t interval;
  726. interval = ReadInterval( pKey->GetString() );
  727. // Translate from attenuation to soundlevel
  728. float start = interval.start;
  729. float end = interval.start + interval.range;
  730. params.SetSoundLevel( ATTN_TO_SNDLVL( start ), ATTN_TO_SNDLVL( end ) - ATTN_TO_SNDLVL( start ) );
  731. }
  732. // Goldsrc compatibility mode.. feed the sndlevel value through the sound engine interface in such a way
  733. // that it can reconstruct the original sndlevel value and flag the sound as using Goldsrc attenuation.
  734. bool bCompatibilityAttenuation = !Q_strcasecmp( pKey->GetName(), "CompatibilityAttenuation" );
  735. if ( bCompatibilityAttenuation )
  736. {
  737. if ( params.GetSoundLevel().range != 0 )
  738. {
  739. Warning( "CompatibilityAttenuation for sound %s must have same start and end values.\n", soundname );
  740. }
  741. params.SetSoundLevel( SNDLEVEL_TO_COMPATIBILITY_MODE( params.GetSoundLevel().start ) );
  742. }
  743. }
  744. else if ( !Q_strcasecmp( pKey->GetName(), "soundlevel" ) || !Q_strcasecmp( pKey->GetName(), "CompatibilitySoundlevel" ) )
  745. {
  746. if ( params.GetSoundLevel().start != SNDLVL_NORM || params.GetSoundLevel().range != 0 )
  747. {
  748. DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has multiple attenuation, CompatabilityAttenuation, and/or soundlevel entries.\n", soundname );
  749. }
  750. if ( !Q_strncasecmp( pKey->GetString(), "ATTN_", strlen( "ATTN_" ) ) )
  751. {
  752. DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has \"soundlevel\" with %s value!\n",
  753. soundname, pKey->GetString() );
  754. }
  755. params.SoundLevelFromString( pKey->GetString() );
  756. // Goldsrc compatibility mode.. feed the sndlevel value through the sound engine interface in such a way
  757. // that it can reconstruct the original sndlevel value and flag the sound as using Goldsrc attenuation.
  758. bool bCompatibilityAttenuation = !Q_strcasecmp( pKey->GetName(), "CompatibilitySoundlevel" );
  759. if ( bCompatibilityAttenuation )
  760. {
  761. if ( params.GetSoundLevel().range != 0 )
  762. {
  763. Warning( "CompatibilitySoundlevel for sound %s must have same start and end values.\n", soundname );
  764. }
  765. params.SetSoundLevel( SNDLEVEL_TO_COMPATIBILITY_MODE( params.GetSoundLevel().start ) );
  766. }
  767. }
  768. else if ( !Q_strcasecmp( pKey->GetName(), "play_to_owner_only" ) )
  769. {
  770. params.SetOnlyPlayToOwner( pKey->GetBool() );
  771. }
  772. else if ( !Q_strcasecmp( pKey->GetName(), "delay_msec" ) )
  773. {
  774. // Don't allow negative delay
  775. params.SetDelayMsec( MAX( 0, pKey->GetInt() ) );
  776. }
  777. else if ( !Q_strcasecmp( pKey->GetName(), "soundentry_version" ) )
  778. {
  779. params.SetSoundEntryVersion( pKey->GetInt() );
  780. }
  781. else if ( !V_strcasecmp( pKey->GetName(), "operator_stacks" ) )
  782. {
  783. params.SetOperatorsKV( pKey );
  784. }
  785. else if (!V_strcasecmp(pKey->GetName(), "hrtf_follow"))
  786. {
  787. params.SetHRTFFollowEntity(pKey->GetBool());
  788. }
  789. else if (!V_strcasecmp(pKey->GetName(), "hrtf_bilinear"))
  790. {
  791. params.SetHRTFBilinear(pKey->GetBool());
  792. }
  793. pKey = pKey->GetNextKey();
  794. }
  795. return true;
  796. }
  797. //-----------------------------------------------------------------------------
  798. // Purpose:
  799. // Input : *soundname -
  800. // Output : char const
  801. //-----------------------------------------------------------------------------
  802. const char *CSoundEmitterSystemBase::GetWavFileForSound( const char *soundname, char const *actormodel )
  803. {
  804. gender_t gender = GetActorGender( actormodel );
  805. return GetWavFileForSound( soundname, gender );
  806. }
  807. const char *CSoundEmitterSystemBase::GetWavFileForSound( const char *soundname, gender_t gender )
  808. {
  809. CSoundParameters params;
  810. if ( !GetParametersForSound( soundname, params, gender ) )
  811. {
  812. return soundname;
  813. }
  814. if ( !params.soundname[ 0 ] )
  815. {
  816. return soundname;
  817. }
  818. static char outsound[ 512 ];
  819. Q_strncpy( outsound, params.soundname, sizeof( outsound ) );
  820. return outsound;
  821. }
  822. //-----------------------------------------------------------------------------
  823. // Purpose:
  824. // Input : *soundname -
  825. // Output : soundlevel_t
  826. //-----------------------------------------------------------------------------
  827. soundlevel_t CSoundEmitterSystemBase::LookupSoundLevel( const char *soundname )
  828. {
  829. CSoundParameters params;
  830. if ( !GetParametersForSound( soundname, params, GENDER_NONE ) )
  831. {
  832. return SNDLVL_NORM;
  833. }
  834. return params.soundlevel;
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Purpose:
  838. // Input : *filename -
  839. //-----------------------------------------------------------------------------
  840. void CSoundEmitterSystemBase::AddSoundsFromFile( const char *filename, bool bPreload, bool bAutoCache, bool bIsOverride /*=false*/ )
  841. {
  842. CSoundScriptFile sf;
  843. sf.hFilename = g_pFullFileSystem->FindOrAddFileName( filename );
  844. sf.dirty = false;
  845. int scriptindex = m_SoundKeyValues.AddToTail( sf );
  846. int replaceCount = 0;
  847. int newOverrideCount = 0;
  848. int duplicatedReplacements = 0;
  849. // Open the soundscape data file, and abort if we can't
  850. KeyValues *kv = new KeyValues( "" );
  851. if ( g_pFullFileSystem->LoadKeyValues( *kv, IFileSystem::TYPE_SOUNDEMITTER, filename, "GAME" ) )
  852. {
  853. // parse out all of the top level sections and save their names
  854. KeyValues *pKeys = kv;
  855. while ( pKeys )
  856. {
  857. if ( pKeys->GetFirstSubKey() )
  858. {
  859. if ( m_Sounds.Count() + 1 == m_Sounds.InvalidIndex() )
  860. {
  861. Warning( "Exceeded maximum number of sound emitter entries\n" );
  862. break;
  863. }
  864. CSoundEntry *pEntry;
  865. {
  866. MEM_ALLOC_CREDIT();
  867. pEntry = new CSoundEntry;
  868. }
  869. const char *pName = pKeys->GetName();
  870. if ( !V_strlen( pName ) )
  871. {
  872. Error( "Syntax Error! Empty named KV block in %s\n", filename );
  873. }
  874. pEntry->m_Name = pName;
  875. pEntry->m_bRemoved = false;
  876. pEntry->m_nScriptFileIndex = scriptindex;
  877. pEntry->m_bIsOverride = bIsOverride;
  878. HSOUNDSCRIPTHASH hash = HashSoundName( pEntry->m_Name.String() );
  879. if ( bIsOverride )
  880. {
  881. ++newOverrideCount;
  882. }
  883. bool add = true;
  884. int lookup = GetSoundIndexForHash( hash );
  885. if ( lookup != m_Sounds.InvalidIndex() )
  886. {
  887. add = false;
  888. if ( bIsOverride )
  889. {
  890. MEM_ALLOC_CREDIT();
  891. // Store off the old sound if it's not already an "override" from another file!!!
  892. // Otherwise, just whack it again!!!
  893. if ( !m_Sounds[ lookup ]->m_bIsOverride )
  894. {
  895. m_SavedOverrides.AddToTail( m_Sounds[ lookup ] );
  896. }
  897. else
  898. {
  899. delete m_Sounds[ lookup ];
  900. ++duplicatedReplacements;
  901. }
  902. InitSoundInternalParameters( pKeys->GetName(), pKeys, pEntry->m_SoundParams );
  903. pEntry->m_SoundParams.SetShouldPreload( bPreload ); // this gets handled by game code after initting.
  904. pEntry->m_SoundParams.SetShouldAutoCache( bAutoCache ); // this gets handled by game code after initting.
  905. m_Sounds[ lookup ] = pEntry;
  906. ++replaceCount;
  907. }
  908. else
  909. {
  910. delete pEntry;
  911. // DevMsg( "CSoundEmitterSystem::AddSoundsFromFile(%s): Entry %s duplicated, skipping\n", filename, pKeys->GetName() );
  912. }
  913. }
  914. if ( add )
  915. {
  916. MEM_ALLOC_CREDIT();
  917. InitSoundInternalParameters( pKeys->GetName(), pKeys, pEntry->m_SoundParams );
  918. pEntry->m_SoundParams.SetShouldPreload( bPreload ); // this gets handled by game code after initting.
  919. pEntry->m_SoundParams.SetShouldAutoCache( bAutoCache ); // this gets handled by game code after initting.
  920. int idx = m_Sounds.AddToTail( pEntry );
  921. AddHash( pEntry->m_Name.String(), idx );
  922. }
  923. }
  924. pKeys = pKeys->GetNextKey();
  925. }
  926. kv->deleteThis();
  927. }
  928. else
  929. {
  930. if ( !bIsOverride )
  931. {
  932. Warning( "CSoundEmitterSystem::AddSoundsFromFile: No such file %s\n", filename );
  933. }
  934. // Discard
  935. m_SoundKeyValues.Remove( scriptindex );
  936. kv->deleteThis();
  937. return;
  938. }
  939. if ( bIsOverride )
  940. {
  941. Warning( "SoundEmitter: adding map sound overrides from %s [%i total, %i replacements, %i duplicated replacements]\n",
  942. filename,
  943. newOverrideCount,
  944. replaceCount,
  945. duplicatedReplacements );
  946. }
  947. Assert( scriptindex >= 0 );
  948. }
  949. //-----------------------------------------------------------------------------
  950. // Purpose:
  951. //-----------------------------------------------------------------------------
  952. int CSoundEmitterSystemBase::CheckForMissingWavFiles( bool verbose )
  953. {
  954. int missing = 0;
  955. int c = GetSoundCount();
  956. int i;
  957. char testfile[ 512 ];
  958. for ( i = 0; i < c; i++ )
  959. {
  960. CSoundParametersInternal *internal = InternalGetParametersForSound( i );
  961. if ( !internal )
  962. {
  963. Assert( 0 );
  964. continue;
  965. }
  966. int waveCount = internal->NumSoundNames();
  967. for ( int wave = 0; wave < waveCount; wave++ )
  968. {
  969. CUtlSymbol sym = internal->GetSoundNames()[ wave ].symbol;
  970. const char *name = m_Waves.String( sym );
  971. if ( !name || !name[ 0 ] )
  972. {
  973. Assert( 0 );
  974. continue;
  975. }
  976. // Skip ! sentence stuff
  977. if ( name[0] == CHAR_SENTENCE )
  978. continue;
  979. Q_snprintf( testfile, sizeof( testfile ), "sound/%s", PSkipSoundChars( name ) );
  980. if ( g_pFullFileSystem->FileExists( testfile ) )
  981. continue;
  982. internal->SetHadMissingWaveFiles( true );
  983. ++missing;
  984. if ( verbose )
  985. {
  986. DevMsg( "Sound %s references missing file %s\n", GetSoundName( i ), name );
  987. }
  988. }
  989. }
  990. return missing;
  991. }
  992. //-----------------------------------------------------------------------------
  993. // Purpose:
  994. // Input : *key -
  995. // Output : float
  996. //-----------------------------------------------------------------------------
  997. float CSoundEmitterSystemBase::TranslateAttenuation( const char *key )
  998. {
  999. if ( !key )
  1000. {
  1001. Assert( 0 );
  1002. return ATTN_NORM;
  1003. }
  1004. if ( !Q_strcasecmp( key, "ATTN_NONE" ) )
  1005. return ATTN_NONE;
  1006. if ( !Q_strcasecmp( key, "ATTN_NORM" ) )
  1007. return ATTN_NORM;
  1008. if ( !Q_strcasecmp( key, "ATTN_IDLE" ) )
  1009. return ATTN_IDLE;
  1010. if ( !Q_strcasecmp( key, "ATTN_STATIC" ) )
  1011. return ATTN_STATIC;
  1012. if ( !Q_strcasecmp( key, "ATTN_RICOCHET" ) )
  1013. return ATTN_RICOCHET;
  1014. if ( !Q_strcasecmp( key, "ATTN_GUNFIRE" ) )
  1015. return ATTN_GUNFIRE;
  1016. DevMsg( "CSoundEmitterSystem: Unknown attenuation key %s\n", key );
  1017. return ATTN_NORM;
  1018. }
  1019. //-----------------------------------------------------------------------------
  1020. // Purpose:
  1021. // Input : *key -
  1022. // Output : soundlevel_t
  1023. //-----------------------------------------------------------------------------
  1024. soundlevel_t CSoundEmitterSystemBase::TranslateSoundLevel( const char *key )
  1025. {
  1026. return TextToSoundLevel( key );
  1027. }
  1028. //-----------------------------------------------------------------------------
  1029. // Purpose: Convert "chan_xxx" into integer value for channel
  1030. // Input : *name -
  1031. // Output : static int
  1032. //-----------------------------------------------------------------------------
  1033. int CSoundEmitterSystemBase::TranslateChannel( const char *name )
  1034. {
  1035. return TextToChannel( name );
  1036. }
  1037. const char *CSoundEmitterSystemBase::GetSourceFileForSound( int index ) const
  1038. {
  1039. if ( index < 0 || index >= (int)m_Sounds.Count() )
  1040. {
  1041. Assert( 0 );
  1042. return "";
  1043. }
  1044. CSoundEntry const *entry = m_Sounds[ index ];
  1045. int scriptindex = entry->m_nScriptFileIndex;
  1046. if ( scriptindex < 0 || scriptindex >= m_SoundKeyValues.Count() )
  1047. {
  1048. Assert( 0 );
  1049. return "";
  1050. }
  1051. static char fn[ 512 ];
  1052. if ( g_pFullFileSystem->String( m_SoundKeyValues[ scriptindex ].hFilename, fn, sizeof( fn ) ))
  1053. {
  1054. return fn;
  1055. }
  1056. Assert( 0 );
  1057. return "";
  1058. }
  1059. const char *CSoundEmitterSystemBase::GetWaveName( CUtlSymbol& sym )
  1060. {
  1061. return m_Waves.String( sym );
  1062. }
  1063. int CSoundEmitterSystemBase::FindSoundScript( const char *name ) const
  1064. {
  1065. int i, c;
  1066. FileNameHandle_t hFilename = g_pFullFileSystem->FindFileName( name );
  1067. if ( hFilename )
  1068. {
  1069. // First, make sure it's known
  1070. c = m_SoundKeyValues.Count();
  1071. for ( i = 0; i < c ; i++ )
  1072. {
  1073. if ( m_SoundKeyValues[ i ].hFilename == hFilename )
  1074. {
  1075. return i;
  1076. }
  1077. }
  1078. }
  1079. return m_SoundKeyValues.InvalidIndex();
  1080. }
  1081. bool CSoundEmitterSystemBase::AddSound( const char *soundname, const char *scriptfile, const CSoundParametersInternal& params )
  1082. {
  1083. int idx = GetSoundIndex( soundname );
  1084. int i = FindSoundScript( scriptfile );
  1085. if ( i == m_SoundKeyValues.InvalidIndex() )
  1086. {
  1087. Warning( "CSoundEmitterSystemBase::AddSound( '%s', '%s', ... ), script file not list in manifest '%s'\n",
  1088. soundname, scriptfile, MANIFEST_FILE );
  1089. return false;
  1090. }
  1091. MEM_ALLOC_CREDIT();
  1092. // More like an update...
  1093. if ( IsValidIndex( idx ) )
  1094. {
  1095. CSoundEntry *entry = m_Sounds[ idx ];
  1096. entry->m_bRemoved = false;
  1097. entry->m_nScriptFileIndex = i;
  1098. entry->m_SoundParams.CopyFrom( params );
  1099. m_SoundKeyValues[ i ].dirty = true;
  1100. return true;
  1101. }
  1102. CSoundEntry *pEntry = new CSoundEntry;
  1103. pEntry->m_Name = soundname;
  1104. pEntry->m_bRemoved = false;
  1105. pEntry->m_nScriptFileIndex = i;
  1106. pEntry->m_SoundParams.CopyFrom( params );
  1107. idx = m_Sounds.AddToTail( pEntry );
  1108. AddHash( pEntry->m_Name.String(), idx );
  1109. m_SoundKeyValues[ i ].dirty = true;
  1110. return true;
  1111. }
  1112. void CSoundEmitterSystemBase::RemoveSound( const char *soundname )
  1113. {
  1114. int idx = GetSoundIndex( soundname );
  1115. if ( !IsValidIndex( idx ) )
  1116. {
  1117. Warning( "Can't remove %s, no such sound!\n", soundname );
  1118. return;
  1119. }
  1120. m_Sounds[ idx ]->m_bRemoved = true;
  1121. // Mark script as dirty
  1122. int scriptindex = m_Sounds[ idx ]->m_nScriptFileIndex;
  1123. if ( scriptindex < 0 || scriptindex >= m_SoundKeyValues.Count() )
  1124. {
  1125. Assert( 0 );
  1126. return;
  1127. }
  1128. m_SoundKeyValues[ scriptindex ].dirty = true;
  1129. }
  1130. void CSoundEmitterSystemBase::MoveSound( const char *soundname, const char *newscript )
  1131. {
  1132. int idx = GetSoundIndex( soundname );
  1133. if ( !IsValidIndex( idx ) )
  1134. {
  1135. Warning( "Can't move '%s', no such sound!\n", soundname );
  1136. return;
  1137. }
  1138. int oldscriptindex = m_Sounds[ idx ]->m_nScriptFileIndex;
  1139. if ( oldscriptindex < 0 || oldscriptindex >= m_SoundKeyValues.Count() )
  1140. {
  1141. Assert( 0 );
  1142. return;
  1143. }
  1144. int newscriptindex = FindSoundScript( newscript );
  1145. if ( newscriptindex == m_SoundKeyValues.InvalidIndex() )
  1146. {
  1147. Warning( "CSoundEmitterSystemBase::MoveSound( '%s', '%s' ), script file not list in manifest '%s'\n",
  1148. soundname, newscript, MANIFEST_FILE );
  1149. return;
  1150. }
  1151. // No actual change
  1152. if ( oldscriptindex == newscriptindex )
  1153. {
  1154. return;
  1155. }
  1156. // Move it
  1157. m_Sounds[ idx ]->m_nScriptFileIndex = newscriptindex;
  1158. // Mark both scripts as dirty
  1159. m_SoundKeyValues[ oldscriptindex ].dirty = true;
  1160. m_SoundKeyValues[ newscriptindex ].dirty = true;
  1161. }
  1162. int CSoundEmitterSystemBase::GetNumSoundScripts() const
  1163. {
  1164. return m_SoundKeyValues.Count();
  1165. }
  1166. const char *CSoundEmitterSystemBase::GetSoundScriptName( int index ) const
  1167. {
  1168. if ( index < 0 || index >= m_SoundKeyValues.Count() )
  1169. return NULL;
  1170. static char fn[ 512 ];
  1171. if ( g_pFullFileSystem->String( m_SoundKeyValues[ index ].hFilename, fn, sizeof( fn ) ) )
  1172. {
  1173. return fn;
  1174. }
  1175. return "";
  1176. }
  1177. bool CSoundEmitterSystemBase::IsSoundScriptDirty( int index ) const
  1178. {
  1179. if ( index < 0 || index >= m_SoundKeyValues.Count() )
  1180. return false;
  1181. return m_SoundKeyValues[ index ].dirty;
  1182. }
  1183. void CSoundEmitterSystemBase::SaveChangesToSoundScript( int scriptindex )
  1184. {
  1185. const char *outfile = GetSoundScriptName( scriptindex );
  1186. if ( !outfile )
  1187. {
  1188. Msg( "CSoundEmitterSystemBase::SaveChangesToSoundScript: No script file for index %i\n", scriptindex );
  1189. return;
  1190. }
  1191. if ( g_pFullFileSystem->FileExists( outfile ) &&
  1192. !g_pFullFileSystem->IsFileWritable( outfile ) )
  1193. {
  1194. Warning( "%s is not writable, can't save data to file\n", outfile );
  1195. return;
  1196. }
  1197. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1198. // FIXME: Write sound script header
  1199. if ( g_pFullFileSystem->FileExists( GAME_SOUNDS_HEADER_BLOCK ) )
  1200. {
  1201. FileHandle_t header = g_pFullFileSystem->Open( GAME_SOUNDS_HEADER_BLOCK, "rb", NULL );
  1202. if ( header != FILESYSTEM_INVALID_HANDLE )
  1203. {
  1204. int len = g_pFullFileSystem->Size( header );
  1205. unsigned char *data = new unsigned char[ len + 1 ];
  1206. Q_memset( data, 0, len + 1 );
  1207. g_pFullFileSystem->Read( data, len, header );
  1208. g_pFullFileSystem->Close( header );
  1209. data[ len ] = 0;
  1210. char *p = (char *)data;
  1211. while ( *p )
  1212. {
  1213. if ( *p != '\r' )
  1214. {
  1215. buf.PutChar( *p );
  1216. }
  1217. ++p;
  1218. }
  1219. delete[] data;
  1220. }
  1221. buf.Printf( "\n" );
  1222. }
  1223. int c = GetSoundCount();
  1224. for ( int i = 0; i < c; i++ )
  1225. {
  1226. if ( Q_stricmp( outfile, GetSourceFileForSound( i ) ) )
  1227. continue;
  1228. // It's marked for deletion, just skip it
  1229. if ( m_Sounds[ i ]->m_bRemoved )
  1230. continue;
  1231. CSoundParametersInternal *p = InternalGetParametersForSound( i );
  1232. if ( !p )
  1233. continue;
  1234. buf.Printf( "\"%s\"\n{\n", GetSoundName( i ) );
  1235. buf.Printf( "\t\"channel\"\t\t\"%s\"\n", p->ChannelToString() );
  1236. buf.Printf( "\t\"volume\"\t\t\"%s\"\n", p->VolumeToString() );
  1237. buf.Printf( "\t\"pitch\"\t\t\t\"%s\"\n", p->PitchToString() );
  1238. buf.Printf( "\n" );
  1239. buf.Printf( "\t\"soundlevel\"\t\"%s\"\n", p->SoundLevelToString() );
  1240. if ( p->OnlyPlayToOwner() )
  1241. {
  1242. buf.Printf( "\t\"play_to_owner_only\"\t\"1\"\n" );
  1243. }
  1244. if ( p->GetDelayMsec() != 0 )
  1245. {
  1246. buf.Printf( "\t\"delay_msec\"\t\"%i\"\n", p->GetDelayMsec() );
  1247. }
  1248. int totalCount = 0;
  1249. int waveCount = p->NumSoundNames();
  1250. int convertedCount = p->NumConvertedNames();
  1251. totalCount = ( waveCount - 2 * convertedCount ) + convertedCount;
  1252. if ( totalCount > 0 )
  1253. {
  1254. buf.Printf( "\n" );
  1255. if ( waveCount == 1 )
  1256. {
  1257. Assert( p->GetSoundNames()[ 0 ].gender == GENDER_NONE );
  1258. buf.Printf( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ 0 ].symbol ) );
  1259. }
  1260. else if ( convertedCount == 1 )
  1261. {
  1262. Assert( p->GetConvertedNames()[ 0 ].gender == GENDER_NONE );
  1263. buf.Printf( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ 0 ].symbol ) );
  1264. }
  1265. else
  1266. {
  1267. buf.Printf( "\t\"rndwave\"\n" );
  1268. buf.Printf( "\t{\n" );
  1269. int wave;
  1270. for ( wave = 0; wave < waveCount; wave++ )
  1271. {
  1272. // Skip macro-expanded names
  1273. if ( p->GetSoundNames()[ wave ].gender != GENDER_NONE )
  1274. continue;
  1275. buf.Printf( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ wave ].symbol ) );
  1276. }
  1277. for ( wave = 0; wave < convertedCount; wave++ )
  1278. {
  1279. buf.Printf( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ wave ].symbol ) );
  1280. }
  1281. buf.Printf( "\t}\n" );
  1282. }
  1283. }
  1284. buf.Printf( "}\n" );
  1285. if ( i != c - 1 )
  1286. {
  1287. buf.Printf( "\n" );
  1288. }
  1289. }
  1290. // Write it out baby
  1291. FileHandle_t fh = g_pFullFileSystem->Open( outfile, "wt" );
  1292. if (fh)
  1293. {
  1294. g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
  1295. g_pFullFileSystem->Close(fh);
  1296. // Changed saved successfully
  1297. m_SoundKeyValues[ scriptindex ].dirty = false;
  1298. }
  1299. else
  1300. {
  1301. Warning( "SceneManager_SaveSoundsToScriptFile: Unable to write file %s!!!\n", outfile );
  1302. }
  1303. }
  1304. //-----------------------------------------------------------------------------
  1305. // Purpose:
  1306. // Input : *name -
  1307. // Output : CUtlSymbol
  1308. //-----------------------------------------------------------------------------
  1309. CUtlSymbol CSoundEmitterSystemBase::AddWaveName( const char *name )
  1310. {
  1311. return m_Waves.AddString( name );
  1312. }
  1313. void CSoundEmitterSystemBase::RenameSound( const char *soundname, const char *newname )
  1314. {
  1315. // Same name?
  1316. if ( !Q_stricmp( soundname, newname ) )
  1317. {
  1318. return;
  1319. }
  1320. HSOUNDSCRIPTHASH oldHash = HashSoundName( soundname );
  1321. HSOUNDSCRIPTHASH newHash = HashSoundName( newname );
  1322. int index = GetSoundIndexForHash( oldHash );
  1323. if ( !IsValidIndex( index ) )
  1324. {
  1325. Msg( "Can't rename %s, no such sound\n", soundname );
  1326. return;
  1327. }
  1328. int check = GetSoundIndexForHash( newHash );
  1329. if ( IsValidIndex( check ) )
  1330. {
  1331. Msg( "Can't rename %s to %s, new name already in list\n", soundname, newname );
  1332. return;
  1333. }
  1334. MEM_ALLOC_CREDIT();
  1335. // Copy out old entry
  1336. CSoundEntry *pEntry = m_Sounds[ index ];
  1337. pEntry->m_Name = newname;
  1338. RemoveHash( soundname );
  1339. AddHash( pEntry->m_Name.String(), index );
  1340. // Mark associated script as dirty
  1341. m_SoundKeyValues[ pEntry->m_nScriptFileIndex ].dirty = true;
  1342. }
  1343. void CSoundEmitterSystemBase::UpdateSoundParameters( const char *soundname, const CSoundParametersInternal& params )
  1344. {
  1345. int idx = GetSoundIndex( soundname );
  1346. if ( !IsValidIndex( idx ) )
  1347. {
  1348. Msg( "Can't UpdateSoundParameters %s, no such sound\n", soundname );
  1349. return;
  1350. }
  1351. CSoundEntry *entry = m_Sounds[ idx ];
  1352. if ( entry->m_SoundParams == params )
  1353. {
  1354. // No changes
  1355. return;
  1356. }
  1357. // Update parameters
  1358. entry->m_SoundParams.CopyFrom( params );
  1359. // Set dirty flag
  1360. m_SoundKeyValues[ entry->m_nScriptFileIndex ].dirty = true;
  1361. }
  1362. bool CSoundEmitterSystemBase::IsUsingGenderToken( char const *soundname )
  1363. {
  1364. HSOUNDSCRIPTHASH hash = HashSoundName( soundname );
  1365. int soundindex = GetSoundIndexForHash( hash );
  1366. // Look up the sound level from the soundemitter system
  1367. CSoundParametersInternal *params = InternalGetParametersForSound( soundindex );
  1368. if ( !params )
  1369. return false;
  1370. return params->UsesGenderToken();
  1371. }
  1372. unsigned int CSoundEmitterSystemBase::GetManifestFileTimeChecksum()
  1373. {
  1374. return m_uManifestPlusScriptChecksum;
  1375. }
  1376. bool CSoundEmitterSystemBase::GetParametersForSoundEx( const char *soundname, HSOUNDSCRIPTHASH& handle, CSoundParameters& params, gender_t gender, bool isbeingemitted /*= false*/ )
  1377. {
  1378. if ( g_pResourceAccessControl )
  1379. {
  1380. if ( !g_pResourceAccessControl->IsAccessAllowed( RESOURCE_GAMESOUND, soundname ) )
  1381. return false;
  1382. }
  1383. if ( handle == SOUNDEMITTER_INVALID_HASH )
  1384. {
  1385. handle = HashSoundName( soundname );
  1386. }
  1387. int index = GetSoundIndexForHash( handle );
  1388. CSoundParametersInternal *internal = InternalGetParametersForSound( index );
  1389. if ( !internal )
  1390. {
  1391. Assert( 0 );
  1392. Warning( "CSoundEmitterSystemBase::GetParametersForSound: No such sound %s\n", soundname );
  1393. HSOUNDSCRIPTHASH hash = HashSoundName( "Error" );
  1394. int index = GetSoundIndexForHash( hash );
  1395. internal = InternalGetParametersForSound( index );
  1396. if ( !internal )
  1397. return false;
  1398. }
  1399. int nNumberOfSoundNames = internal->NumSoundNames();
  1400. #if IsPlatformPS3()
  1401. if ( g_pFullFileSystem->IsPrefetchingDone() == false )
  1402. {
  1403. nNumberOfSoundNames = imin( nNumberOfSoundNames, 5 ); // The HDD is not filled yet, we are going to play up to 5 variations max
  1404. }
  1405. #elif IsPlatformX360()
  1406. if ( g_pXboxInstaller->IsFullyInstalled() == false )
  1407. {
  1408. nNumberOfSoundNames = imin( nNumberOfSoundNames, 5 ); // Either there is no HDD, or it has not been fully installed
  1409. }
  1410. #endif
  1411. params.channel = internal->GetChannel();
  1412. params.volume = internal->GetVolume().Random();
  1413. params.pitch = internal->GetPitch().Random();
  1414. params.pitchlow = internal->GetPitch().start;
  1415. params.pitchhigh = params.pitchlow + internal->GetPitch().range;
  1416. params.delay_msec = internal->GetDelayMsec();
  1417. params.count = nNumberOfSoundNames;
  1418. params.soundname[ 0 ] = 0;
  1419. params.m_nSoundEntryVersion = (int)internal->GetSoundEntryVersion();
  1420. params.m_hSoundScriptHash = handle;
  1421. params.m_pOperatorsKV = internal->GetOperatorsKV();
  1422. int bestIndex = FindBestSoundForGender( internal->GetSoundNames(), nNumberOfSoundNames, gender, params.m_nRandomSeed );
  1423. if ( bestIndex >= 0 )
  1424. {
  1425. Q_strncpy( params.soundname, GetWaveName( internal->GetSoundNames()[ bestIndex ].symbol), sizeof( params.soundname ) );
  1426. // If we are actually emitting the sound, mark it as not available...
  1427. if ( isbeingemitted )
  1428. {
  1429. internal->GetSoundNames()[ bestIndex ].available = 0;
  1430. }
  1431. }
  1432. params.soundlevel = (soundlevel_t)(int)internal->GetSoundLevel().Random();
  1433. params.play_to_owner_only = internal->OnlyPlayToOwner();
  1434. params.m_bHRTFBilinear = internal->HasHRTFBilinear();
  1435. params.m_bHRTFFollowEntity = internal->HasHRTFFollowEntity();
  1436. if ( !params.soundname[ 0 ] )
  1437. {
  1438. DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound %s has no wave or rndwave key!\n", soundname );
  1439. return false;
  1440. }
  1441. if ( internal->HadMissingWaveFiles() &&
  1442. params.soundname[ 0 ] != CHAR_SENTENCE )
  1443. {
  1444. char testfile[ 256 ];
  1445. Q_snprintf( testfile, sizeof( testfile ), "sound/%s", PSkipSoundChars( params.soundname ) );
  1446. if ( !g_pFullFileSystem->FileExists( testfile ) )
  1447. {
  1448. // Prevent repetitive spew...
  1449. static CUtlSymbolTable soundWarnings;
  1450. char key[ 256 ];
  1451. Q_snprintf( key, sizeof( key ), "%s:%s", soundname, params.soundname );
  1452. if ( UTL_INVAL_SYMBOL == soundWarnings.Find( key ) )
  1453. {
  1454. soundWarnings.AddString( key );
  1455. DevMsg( "CSoundEmitterSystemBase::GetParametersForSound: sound '%s' references wave '%s' which doesn't exist on disk!\n",
  1456. soundname,
  1457. params.soundname );
  1458. }
  1459. return false;
  1460. }
  1461. }
  1462. return true;
  1463. }
  1464. soundlevel_t CSoundEmitterSystemBase::LookupSoundLevelByHandle( char const *soundname, HSOUNDSCRIPTHASH& handle )
  1465. {
  1466. if ( handle == SOUNDEMITTER_INVALID_HASH )
  1467. {
  1468. handle = HashSoundName( soundname );
  1469. }
  1470. int index = GetSoundIndexForHash( handle );
  1471. CSoundParametersInternal *internal = InternalGetParametersForSound( index );
  1472. if ( !internal )
  1473. {
  1474. return SNDLVL_NORM;
  1475. }
  1476. return (soundlevel_t)(int)internal->GetSoundLevel().Random();
  1477. }
  1478. KeyValues * CSoundEmitterSystemBase::GetOperatorKVByHandle( HSOUNDSCRIPTHASH& handle )
  1479. {
  1480. if ( handle == SOUNDEMITTER_INVALID_HASH )
  1481. {
  1482. return NULL;
  1483. }
  1484. int index = GetSoundIndexForHash( handle );
  1485. CSoundParametersInternal *internal = InternalGetParametersForSound( index );
  1486. if ( !internal )
  1487. {
  1488. return NULL;
  1489. }
  1490. return internal->GetOperatorsKV();
  1491. }
  1492. // 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)
  1493. // Called by LevelInitPreEntity to override sound scripts for the mod with level specific overrides based on custom mapnames, etc.
  1494. void CSoundEmitterSystemBase::AddSoundOverrides( char const *scriptfile )
  1495. {
  1496. FileNameHandle_t handle = g_pFullFileSystem->FindOrAddFileName( scriptfile );
  1497. if ( m_OverrideFiles.Find( handle ) != m_OverrideFiles.InvalidIndex() )
  1498. return;
  1499. m_OverrideFiles.AddToTail( handle );
  1500. // These are overrides and assume bShoudPreload and bShouldAutocache are false
  1501. AddSoundsFromFile( scriptfile, false, false, true );
  1502. }
  1503. // Called by either client or server in LevelShutdown to clear out custom overrides
  1504. void CSoundEmitterSystemBase::ClearSoundOverrides()
  1505. {
  1506. for ( int i = 0; i < m_SavedOverrides.Count(); ++i )
  1507. {
  1508. CSoundEntry *entry = m_SavedOverrides[ i ];
  1509. HSOUNDSCRIPTHASH hash = HashSoundName( entry->m_Name.String() );
  1510. int idx = GetSoundIndexForHash( hash );
  1511. if ( IsValidIndex( idx ) )
  1512. {
  1513. delete m_Sounds[ idx ];
  1514. m_Sounds[ idx ] = m_SavedOverrides[i];
  1515. }
  1516. }
  1517. m_SavedOverrides.Purge();
  1518. m_OverrideFiles.Purge();
  1519. }
  1520. void CSoundEmitterSystemBase::AddHash( char const *pchSoundName, int nIndex )
  1521. {
  1522. Assert( nIndex >= 0 && nIndex < m_Sounds.Count() );
  1523. CSoundEntry *entry = m_Sounds[ nIndex ];
  1524. HSOUNDSCRIPTHASH hash = HashSoundName( pchSoundName );
  1525. // Check for collisions
  1526. int slot = m_HashToSoundEntry.Find( hash );
  1527. if ( slot != m_HashToSoundEntry.InvalidIndex() )
  1528. {
  1529. Error( "Sound name hash collision! '%s' collides with '%s' %i!", pchSoundName, m_HashToSoundEntry[ slot ].pEntry->m_Name.String(), hash );
  1530. return;
  1531. }
  1532. soundEntryHash_t soundEntryHash =
  1533. {
  1534. nIndex,
  1535. entry
  1536. };
  1537. m_HashToSoundEntry.Insert( hash, soundEntryHash );
  1538. }
  1539. void CSoundEmitterSystemBase::RemoveHash( char const *pchSoundName )
  1540. {
  1541. m_HashToSoundEntry.Remove( HashSoundName( pchSoundName ) );
  1542. }
  1543. char const *CSoundEmitterSystemBase::GetSoundNameForHash( HSOUNDSCRIPTHASH hash ) const
  1544. {
  1545. int slot = m_HashToSoundEntry.Find( hash );
  1546. if ( slot == m_HashToSoundEntry.InvalidIndex() )
  1547. return NULL;
  1548. CSoundEntry *entry = m_HashToSoundEntry[ slot ].pEntry;
  1549. return entry->m_Name.String();
  1550. }
  1551. int CSoundEmitterSystemBase::GetSoundIndexForHash( HSOUNDSCRIPTHASH hash ) const
  1552. {
  1553. int slot = m_HashToSoundEntry.Find( hash );
  1554. if ( slot == m_HashToSoundEntry.InvalidIndex() )
  1555. return m_Sounds.InvalidIndex();
  1556. return m_HashToSoundEntry[ slot ].soundIndex;
  1557. }
  1558. #define SOUNDEMITTER_MURMURHASH_SEED ( ( 'D' << 24 ) | ( 'O' << 16 ) | ( 'T' << 8 ) | 'A' )
  1559. HSOUNDSCRIPTHASH CSoundEmitterSystemBase::HashSoundName( char const *pchSndName ) const
  1560. {
  1561. HSOUNDSCRIPTHASH hash = MurmurHash2LowerCase( pchSndName, SOUNDEMITTER_MURMURHASH_SEED );
  1562. return hash;
  1563. }
  1564. bool CSoundEmitterSystemBase::IsValidHash( HSOUNDSCRIPTHASH hash ) const
  1565. {
  1566. int idx = m_HashToSoundEntry.Find( hash );
  1567. return idx != m_HashToSoundEntry.InvalidIndex();
  1568. }
  1569. void CSoundEmitterSystemBase::DescribeSound( char const *soundname )
  1570. {
  1571. HSOUNDSCRIPTHASH hash = HashSoundName( soundname );
  1572. int index = GetSoundIndexForHash( hash );
  1573. if ( index == m_Sounds.InvalidIndex() )
  1574. {
  1575. Msg( "SoundEmitterSystemBase::DescribeSound: No such sound %s\n", soundname );
  1576. return;
  1577. }
  1578. CSoundParametersInternal *p = InternalGetParametersForSound( index );
  1579. if ( !p )
  1580. {
  1581. Msg( "SoundEmitterSystemBase::DescribeSound: No such sound %s\n", soundname );
  1582. return;
  1583. }
  1584. Msg( "\"%s\"\n{\n", GetSoundName( index ) );
  1585. Msg( "\t\"channel\"\t\t\"%s\"\n", p->ChannelToString() );
  1586. Msg( "\t\"volume\"\t\t\"%s\"\n", p->VolumeToString() );
  1587. Msg( "\t\"pitch\"\t\t\t\"%s\"\n", p->PitchToString() );
  1588. Msg( "\n" );
  1589. Msg( "\t\"soundlevel\"\t\"%s\"\n", p->SoundLevelToString() );
  1590. if ( p->OnlyPlayToOwner() )
  1591. {
  1592. Msg( "\t\"play_to_owner_only\"\t\"1\"\n" );
  1593. }
  1594. if ( p->GetDelayMsec() != 0 )
  1595. {
  1596. Msg( "\t\"delay_msec\"\t\"%i\"\n", p->GetDelayMsec() );
  1597. }
  1598. int totalCount = 0;
  1599. int waveCount = p->NumSoundNames();
  1600. int convertedCount = p->NumConvertedNames();
  1601. totalCount = ( waveCount - 2 * convertedCount ) + convertedCount;
  1602. if ( totalCount > 0 )
  1603. {
  1604. Msg( "\n" );
  1605. if ( waveCount == 1 )
  1606. {
  1607. Assert( p->GetSoundNames()[ 0 ].gender == GENDER_NONE );
  1608. Msg( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ 0 ].symbol ) );
  1609. }
  1610. else if ( convertedCount == 1 )
  1611. {
  1612. Assert( p->GetConvertedNames()[ 0 ].gender == GENDER_NONE );
  1613. Msg( "\t\"wave\"\t\t\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ 0 ].symbol ) );
  1614. }
  1615. else
  1616. {
  1617. Msg( "\t\"rndwave\"\n" );
  1618. Msg( "\t{\n" );
  1619. int wave;
  1620. for ( wave = 0; wave < waveCount; wave++ )
  1621. {
  1622. // Skip macro-expanded names
  1623. if ( p->GetSoundNames()[ wave ].gender != GENDER_NONE )
  1624. continue;
  1625. Msg( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetSoundNames()[ wave ].symbol ) );
  1626. }
  1627. for ( wave = 0; wave < convertedCount; wave++ )
  1628. {
  1629. Msg( "\t\t\"wave\"\t\"%s\"\n", GetWaveName( p->GetConvertedNames()[ wave ].symbol ) );
  1630. }
  1631. Msg( "\t}\n" );
  1632. }
  1633. }
  1634. Msg( "}\n" );
  1635. }
  1636. void CSoundEmitterSystemBase::Flush()
  1637. {
  1638. ShutdownSounds();
  1639. LoadGameSoundManifest();
  1640. }
  1641. CSoundEmitterSystemBase g_SoundEmitterSystemBase;
  1642. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSoundEmitterSystemBase, ISoundEmitterSystemBase,
  1643. SOUNDEMITTERSYSTEM_INTERFACE_VERSION, g_SoundEmitterSystemBase );