Team Fortress 2 Source Code as on 22/4/2020
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.

539 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Engine implementation of services required by the audio subsystem
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "quakedef.h"
  8. #include "soundservice.h"
  9. #include "zone.h"
  10. #include "cdll_engine_int.h"
  11. #include "gl_model_private.h"
  12. #include "icliententity.h"
  13. #include "icliententitylist.h"
  14. #include "mouthinfo.h"
  15. #include "host.h"
  16. #include "vstdlib/random.h"
  17. #include "tier0/icommandline.h"
  18. #include "igame.h"
  19. #include "client.h"
  20. #include "server.h"
  21. #include "filesystem.h"
  22. #include "filesystem_engine.h"
  23. #include "sound.h"
  24. #include "vgui_controls/Controls.h"
  25. #include "vgui/ILocalize.h"
  26. #include "vgui_baseui_interface.h"
  27. #include "datacache/idatacache.h"
  28. #include "sys_dll.h"
  29. #include "toolframework/itoolframework.h"
  30. #include "tier0/vprof.h"
  31. #include "cl_steamauth.h"
  32. #include "tier1/fmtstr.h"
  33. #include "MapReslistGenerator.h"
  34. #include "cl_main.h"
  35. // memdbgon must be the last include file in a .cpp file!!!
  36. #include "tier0/memdbgon.h"
  37. void Snd_Restart_f();
  38. #define MAPLIST_FILE "maplist.txt"
  39. class CEngineSoundServices : public ISoundServices
  40. {
  41. public:
  42. CEngineSoundServices() { m_frameTime = 0; }
  43. virtual void *LevelAlloc( int nBytes, const char *pszTag )
  44. {
  45. return Hunk_AllocName(nBytes, pszTag);
  46. }
  47. virtual void OnExtraUpdate()
  48. {
  49. if ( IsPC() && g_ClientDLL && game && game->IsActiveApp() )
  50. {
  51. g_ClientDLL->IN_Accumulate();
  52. }
  53. }
  54. virtual bool GetSoundSpatialization( int entIndex, SpatializationInfo_t& info )
  55. {
  56. if ( !entitylist )
  57. {
  58. return false;
  59. }
  60. // Entity has been deleted
  61. IClientEntity *pClientEntity = entitylist->GetClientEntity( entIndex );
  62. if ( !pClientEntity )
  63. {
  64. // FIXME: Should this assert?
  65. return false;
  66. }
  67. MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
  68. bool bResult = pClientEntity->GetSoundSpatialization( info );
  69. return bResult;
  70. }
  71. virtual bool GetToolSpatialization( int iUserData, int guid, SpatializationInfo_t& info )
  72. {
  73. if ( IsX360() )
  74. {
  75. return false;
  76. }
  77. return toolframework->GetSoundSpatialization( iUserData, guid, info );
  78. }
  79. virtual float GetClientTime()
  80. {
  81. return cl.GetTime();
  82. }
  83. // Filtered local time
  84. virtual float GetHostTime()
  85. {
  86. return host_time;
  87. }
  88. virtual int GetViewEntity()
  89. {
  90. return cl.m_nViewEntity;
  91. }
  92. virtual void SetSoundFrametime( float realDt, float hostDt )
  93. {
  94. extern bool IsReplayRendering();
  95. if ( cl_movieinfo.IsRecording() || IsReplayRendering() )
  96. {
  97. m_frameTime = hostDt;
  98. }
  99. else
  100. {
  101. m_frameTime = realDt;
  102. }
  103. }
  104. virtual float GetHostFrametime()
  105. {
  106. return m_frameTime;
  107. }
  108. virtual int GetServerCount()
  109. {
  110. return cl.m_nServerCount;
  111. }
  112. virtual bool IsPlayer( SoundSource source )
  113. {
  114. return ( source == cl.m_nPlayerSlot + 1 );
  115. }
  116. virtual void OnChangeVoiceStatus( int entity, bool status)
  117. {
  118. ClientDLL_VoiceStatus(entity, status);
  119. }
  120. virtual bool IsConnected()
  121. {
  122. return cl.IsConnected();
  123. }
  124. // Calls into client .dll with list of close caption tokens to construct a caption out of
  125. virtual void EmitSentenceCloseCaption( char const *tokenstream )
  126. {
  127. if ( g_ClientDLL )
  128. {
  129. g_ClientDLL->EmitSentenceCloseCaption( tokenstream );
  130. }
  131. }
  132. virtual void EmitCloseCaption( char const *captionname, float duration )
  133. {
  134. if ( g_ClientDLL )
  135. {
  136. g_ClientDLL->EmitCloseCaption( captionname, duration );
  137. }
  138. }
  139. virtual char const *GetGameDir()
  140. {
  141. return com_gamedir;
  142. }
  143. // If the game is paused, certain audio will pause, too (anything with phoneme/sentence data for now)
  144. virtual bool IsGamePaused()
  145. {
  146. extern IVEngineClient *engineClient;
  147. if ( !engineClient )
  148. {
  149. Assert( !"No engineClient, bug???" );
  150. return false;
  151. }
  152. return engineClient->IsPaused();
  153. }
  154. virtual bool IsGameActive()
  155. {
  156. extern IVEngineClient *engineClient;
  157. if ( !engineClient )
  158. {
  159. Assert( !"No engineClient, bug???" );
  160. return true;
  161. }
  162. return engineClient->IsActiveApp();
  163. }
  164. virtual void RestartSoundSystem()
  165. {
  166. Snd_Restart_f();
  167. }
  168. virtual void GetAllManifestFiles( CUtlRBTree< FileNameHandle_t, int >& list )
  169. {
  170. list.RemoveAll();
  171. // Load them in
  172. FileHandle_t resfilehandle = g_pFileSystem->Open( MAPLIST_FILE, "rb", "MOD" );
  173. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  174. {
  175. // Read in and parse mapcycle.txt
  176. int length = g_pFileSystem->Size(resfilehandle);
  177. if ( length > 0 )
  178. {
  179. char *pStart = (char *)new char[ length + 1 ];
  180. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) )
  181. )
  182. {
  183. pStart[ length ] = 0;
  184. const char *pFileList = pStart;
  185. while ( 1 )
  186. {
  187. pFileList = COM_Parse( pFileList );
  188. if ( strlen( com_token ) <= 0 )
  189. break;
  190. char manifest_file[ 512 ];
  191. Q_snprintf( manifest_file, sizeof( manifest_file ), "%s/%s.manifest", AUDIOSOURCE_CACHE_ROOTDIR, com_token );
  192. if ( g_pFileSystem->FileExists( manifest_file, "MOD" ) )
  193. {
  194. FileNameHandle_t handle = g_pFileSystem->FindOrAddFileName( manifest_file );
  195. if ( list.Find( handle ) == list.InvalidIndex() )
  196. {
  197. list.Insert( handle );
  198. }
  199. }
  200. // Any more tokens on this line?
  201. while ( COM_TokenWaiting( pFileList ) )
  202. {
  203. pFileList = COM_Parse( pFileList );
  204. }
  205. }
  206. }
  207. delete[] pStart;
  208. }
  209. g_pFileSystem->Close(resfilehandle);
  210. }
  211. else
  212. {
  213. Warning( "GetAllManifestFiles: Unable to load %s\n", MAPLIST_FILE );
  214. }
  215. }
  216. virtual void GetAllSoundFilesInManifest( CUtlRBTree< FileNameHandle_t, int >& list, char const *manifestfile )
  217. {
  218. list.RemoveAll();
  219. CacheSoundsFromResFile( true, list, manifestfile, false );
  220. }
  221. virtual void GetAllSoundFilesReferencedInReslists( CUtlRBTree< FileNameHandle_t, int >& list )
  222. {
  223. char reslistdir[ MAX_PATH ];
  224. Q_strncpy( reslistdir, MapReslistGenerator().GetResListDirectory(), sizeof( reslistdir ) );
  225. list.RemoveAll();
  226. // Load them in
  227. FileHandle_t resfilehandle = g_pFileSystem->Open( MAPLIST_FILE, "rb", "MOD" );
  228. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  229. {
  230. // Read in and parse mapcycle.txt
  231. int length = g_pFileSystem->Size(resfilehandle);
  232. if ( length > 0 )
  233. {
  234. char *pStart = (char *)new char[ length + 1 ];
  235. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) )
  236. )
  237. {
  238. pStart[ length ] = 0;
  239. const char *pFileList = pStart;
  240. while ( 1 )
  241. {
  242. char resfile[ 512 ];
  243. pFileList = COM_Parse( pFileList );
  244. if ( strlen( com_token ) <= 0 )
  245. break;
  246. Q_snprintf( resfile, sizeof( resfile ), "%s\\%s.lst", reslistdir, com_token );
  247. CacheSoundsFromResFile( false, list, resfile );
  248. // Any more tokens on this line?
  249. while ( COM_TokenWaiting( pFileList ) )
  250. {
  251. pFileList = COM_Parse( pFileList );
  252. }
  253. }
  254. }
  255. delete[] pStart;
  256. }
  257. g_pFileSystem->Close(resfilehandle);
  258. CacheSoundsFromResFile( false, list, CFmtStr( "%s\\engine.lst", reslistdir ) );
  259. CacheSoundsFromResFile( false, list, CFmtStr( "%s\\all.lst", reslistdir ) );
  260. }
  261. else
  262. {
  263. Warning( "GetAllSoundFilesReferencedInReslists: Unable to load file %s\n", MAPLIST_FILE );
  264. }
  265. }
  266. virtual void CacheBuildingStart()
  267. {
  268. if ( IsX360() )
  269. {
  270. return;
  271. }
  272. EngineVGui()->ActivateGameUI();
  273. EngineVGui()->StartCustomProgress();
  274. const wchar_t *str = g_pVGuiLocalize->Find( "#Valve_CreatingCache" );
  275. if ( str )
  276. {
  277. EngineVGui()->UpdateCustomProgressBar( 0.0f, str );
  278. }
  279. }
  280. virtual void CacheBuildingUpdateProgress( float percent, char const *cachefile )
  281. {
  282. if ( IsX360() )
  283. {
  284. return;
  285. }
  286. const wchar_t *format = g_pVGuiLocalize->Find( "Valve_CreatingSpecificSoundCache" );
  287. if ( format )
  288. {
  289. wchar_t constructed[ 1024 ];
  290. wchar_t file[ 256 ];
  291. g_pVGuiLocalize->ConvertANSIToUnicode( cachefile, file, sizeof( file ) );
  292. g_pVGuiLocalize->ConstructString_safe(
  293. constructed,
  294. ( wchar_t * )format,
  295. 1,
  296. file );
  297. EngineVGui()->UpdateCustomProgressBar( percent, constructed );
  298. }
  299. }
  300. virtual void CacheBuildingFinish()
  301. {
  302. if ( IsX360() )
  303. {
  304. return;
  305. }
  306. EngineVGui()->FinishCustomProgress();
  307. EngineVGui()->HideGameUI();
  308. }
  309. virtual int GetPrecachedSoundCount()
  310. {
  311. if ( !sv.IsActive() )
  312. return 0;
  313. INetworkStringTable *table = sv.GetSoundPrecacheTable();
  314. if ( !table )
  315. return 0;
  316. return table->GetNumStrings();
  317. }
  318. virtual char const *GetPrecachedSound( int index )
  319. {
  320. Assert( sv.IsActive() );
  321. INetworkStringTable *table = sv.GetSoundPrecacheTable();
  322. if ( !table )
  323. return "";
  324. return table->GetString( index );
  325. }
  326. virtual bool ShouldSuppressNonUISounds()
  327. {
  328. return EngineVGui()->IsGameUIVisible() || IsGamePaused();
  329. }
  330. virtual char const *GetUILanguage()
  331. {
  332. extern ConVar cl_language;
  333. return cl_language.GetString();
  334. }
  335. private:
  336. float m_frameTime;
  337. void CacheSoundsFromResFile( bool quiet, CUtlRBTree< FileNameHandle_t, int >& list, char const *resfile, bool checkandcleanname = true )
  338. {
  339. if ( !g_pFileSystem->FileExists( resfile, "MOD" ) )
  340. {
  341. Warning( "CacheSoundsFromResFile: Unable to find '%s'\n", resfile );
  342. return;
  343. }
  344. int oldCount = list.Count();
  345. FileHandle_t resfilehandle = g_pFileSystem->Open( resfile, "rb", "MOD" );
  346. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  347. {
  348. // Read in and parse mapcycle.txt
  349. int length = g_pFileSystem->Size(resfilehandle);
  350. if ( length > 0 )
  351. {
  352. char *pStart = (char *)new char[ length + 1 ];
  353. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) )
  354. )
  355. {
  356. pStart[ length ] = 0;
  357. const char *pFileList = pStart;
  358. while ( 1 )
  359. {
  360. pFileList = COM_Parse( pFileList );
  361. if ( strlen( com_token ) <= 0 )
  362. break;
  363. if ( checkandcleanname )
  364. {
  365. if ( Q_stristr( com_token, ".wav" ) ||
  366. Q_stristr( com_token, ".mp3" ) )
  367. {
  368. // skip past the game/mod directory "hl2/sound/player/footstep.wav"
  369. Q_FixSlashes(com_token); // "hl2\sound\player\footstep.wav"
  370. const char *pName = com_token;
  371. while (pName[0] && pName[0] != CORRECT_PATH_SEPARATOR)
  372. {
  373. pName++;
  374. } // "\sound\player\footstep.wav"
  375. FileNameHandle_t handle = g_pFileSystem->FindOrAddFileName( pName+1 ); // "sound\player\footstep.wav"
  376. if ( list.Find( handle ) == list.InvalidIndex() )
  377. {
  378. list.Insert( handle );
  379. }
  380. }
  381. }
  382. else
  383. {
  384. FileNameHandle_t handle = g_pFileSystem->FindOrAddFileName( com_token );
  385. if ( list.Find( handle ) == list.InvalidIndex() )
  386. {
  387. list.Insert( handle );
  388. }
  389. }
  390. }
  391. }
  392. delete[] pStart;
  393. }
  394. g_pFileSystem->Close(resfilehandle);
  395. }
  396. int newCount = list.Count();
  397. if ( !quiet )
  398. {
  399. Msg( "Processing (%i new) from %s\n", newCount - oldCount, resfile );
  400. }
  401. }
  402. virtual void OnSoundStarted( int guid, StartSoundParams_t& params, char const *soundname )
  403. {
  404. VPROF("OnSoundStarted");
  405. if ( IsX360() || !toolframework->IsToolRecording() || params.suppressrecording )
  406. return;
  407. KeyValues *msg = new KeyValues( "StartSound" );
  408. msg->SetInt( "guid", guid );
  409. msg->SetFloat( "time", cl.GetTime() );
  410. msg->SetInt( "staticsound", params.staticsound ? 1 : 0 );
  411. msg->SetInt( "soundsource", params.soundsource );
  412. msg->SetInt( "entchannel", params.entchannel );
  413. msg->SetString( "soundname", soundname );
  414. msg->SetFloat( "originx", params.origin.x );
  415. msg->SetFloat( "originy", params.origin.y );
  416. msg->SetFloat( "originz", params.origin.z );
  417. msg->SetFloat( "directionx", params.direction.x );
  418. msg->SetFloat( "directiony", params.direction.y );
  419. msg->SetFloat( "directionz", params.direction.z );
  420. msg->SetInt( "updatepositions", params.bUpdatePositions );
  421. msg->SetFloat( "fvol", params.fvol );
  422. msg->SetInt( "soundlevel", (int)params.soundlevel );
  423. msg->SetInt( "flags", params.flags );
  424. msg->SetInt( "pitch", params.pitch );
  425. msg->SetInt( "specialdsp", params.specialdsp );
  426. msg->SetInt( "fromserver", params.fromserver ? 1 : 0 );
  427. msg->SetFloat( "delay", params.delay );
  428. msg->SetInt( "speakerentity", params.speakerentity );
  429. toolframework->PostMessage( msg );
  430. msg->deleteThis();
  431. }
  432. virtual void OnSoundStopped( int guid, int soundsource, int channel, char const *soundname )
  433. {
  434. // NOTE: At the moment, if we don't receive a StartSound message but we do
  435. // receive a StopSound message, the StopSound message is ignored. In a perfect
  436. // world, if the StartSound message was not sent, a StopSound message should not
  437. // be sent for that guid either. This requires more plumbing, though, and
  438. // for the moment, it's not necessary to do that plumbing.
  439. VPROF("OnSoundStopped");
  440. if ( IsX360() || !toolframework->IsToolRecording() )
  441. return;
  442. KeyValues *msg = new KeyValues( "StopSound" );
  443. msg->SetInt( "guid", guid );
  444. msg->SetFloat( "time", cl.GetTime() );
  445. msg->SetInt( "soundsource", soundsource );
  446. msg->SetInt( "entchannel", channel );
  447. msg->SetString( "soundname", soundname );
  448. toolframework->PostMessage( msg );
  449. msg->deleteThis();
  450. }
  451. };
  452. static CEngineSoundServices g_EngineSoundServices;
  453. ISoundServices *g_pSoundServices = &g_EngineSoundServices;