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
539 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Engine implementation of services required by the audio subsystem |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "quakedef.h" |
|
#include "soundservice.h" |
|
#include "zone.h" |
|
#include "cdll_engine_int.h" |
|
#include "gl_model_private.h" |
|
#include "icliententity.h" |
|
#include "icliententitylist.h" |
|
#include "mouthinfo.h" |
|
#include "host.h" |
|
#include "vstdlib/random.h" |
|
#include "tier0/icommandline.h" |
|
#include "igame.h" |
|
#include "client.h" |
|
#include "server.h" |
|
#include "filesystem.h" |
|
#include "filesystem_engine.h" |
|
#include "sound.h" |
|
#include "vgui_controls/Controls.h" |
|
#include "vgui/ILocalize.h" |
|
#include "vgui_baseui_interface.h" |
|
#include "datacache/idatacache.h" |
|
#include "sys_dll.h" |
|
#include "toolframework/itoolframework.h" |
|
#include "tier0/vprof.h" |
|
#include "cl_steamauth.h" |
|
#include "tier1/fmtstr.h" |
|
#include "MapReslistGenerator.h" |
|
#include "cl_main.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
void Snd_Restart_f(); |
|
|
|
#define MAPLIST_FILE "maplist.txt" |
|
|
|
class CEngineSoundServices : public ISoundServices |
|
{ |
|
public: |
|
CEngineSoundServices() { m_frameTime = 0; } |
|
|
|
virtual void *LevelAlloc( int nBytes, const char *pszTag ) |
|
{ |
|
return Hunk_AllocName(nBytes, pszTag); |
|
} |
|
|
|
virtual void OnExtraUpdate() |
|
{ |
|
if ( IsPC() && g_ClientDLL && game && game->IsActiveApp() ) |
|
{ |
|
g_ClientDLL->IN_Accumulate(); |
|
} |
|
} |
|
|
|
virtual bool GetSoundSpatialization( int entIndex, SpatializationInfo_t& info ) |
|
{ |
|
if ( !entitylist ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Entity has been deleted |
|
IClientEntity *pClientEntity = entitylist->GetClientEntity( entIndex ); |
|
if ( !pClientEntity ) |
|
{ |
|
// FIXME: Should this assert? |
|
return false; |
|
} |
|
|
|
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); |
|
bool bResult = pClientEntity->GetSoundSpatialization( info ); |
|
|
|
return bResult; |
|
} |
|
|
|
virtual bool GetToolSpatialization( int iUserData, int guid, SpatializationInfo_t& info ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return false; |
|
} |
|
|
|
return toolframework->GetSoundSpatialization( iUserData, guid, info ); |
|
} |
|
|
|
virtual float GetClientTime() |
|
{ |
|
return cl.GetTime(); |
|
} |
|
|
|
// Filtered local time |
|
virtual float GetHostTime() |
|
{ |
|
return host_time; |
|
} |
|
|
|
virtual int GetViewEntity() |
|
{ |
|
return cl.m_nViewEntity; |
|
} |
|
|
|
virtual void SetSoundFrametime( float realDt, float hostDt ) |
|
{ |
|
extern bool IsReplayRendering(); |
|
if ( cl_movieinfo.IsRecording() || IsReplayRendering() ) |
|
{ |
|
m_frameTime = hostDt; |
|
} |
|
else |
|
{ |
|
m_frameTime = realDt; |
|
} |
|
} |
|
|
|
virtual float GetHostFrametime() |
|
{ |
|
return m_frameTime; |
|
} |
|
|
|
virtual int GetServerCount() |
|
{ |
|
return cl.m_nServerCount; |
|
} |
|
|
|
virtual bool IsPlayer( SoundSource source ) |
|
{ |
|
return ( source == cl.m_nPlayerSlot + 1 ); |
|
} |
|
|
|
virtual void OnChangeVoiceStatus( int entity, bool status) |
|
{ |
|
ClientDLL_VoiceStatus(entity, status); |
|
} |
|
|
|
virtual bool IsConnected() |
|
{ |
|
return cl.IsConnected(); |
|
} |
|
|
|
// Calls into client .dll with list of close caption tokens to construct a caption out of |
|
virtual void EmitSentenceCloseCaption( char const *tokenstream ) |
|
{ |
|
if ( g_ClientDLL ) |
|
{ |
|
g_ClientDLL->EmitSentenceCloseCaption( tokenstream ); |
|
} |
|
} |
|
|
|
virtual void EmitCloseCaption( char const *captionname, float duration ) |
|
{ |
|
if ( g_ClientDLL ) |
|
{ |
|
g_ClientDLL->EmitCloseCaption( captionname, duration ); |
|
} |
|
} |
|
|
|
virtual char const *GetGameDir() |
|
{ |
|
return com_gamedir; |
|
} |
|
|
|
// If the game is paused, certain audio will pause, too (anything with phoneme/sentence data for now) |
|
virtual bool IsGamePaused() |
|
{ |
|
extern IVEngineClient *engineClient; |
|
if ( !engineClient ) |
|
{ |
|
Assert( !"No engineClient, bug???" ); |
|
return false; |
|
} |
|
|
|
return engineClient->IsPaused(); |
|
} |
|
|
|
virtual bool IsGameActive() |
|
{ |
|
extern IVEngineClient *engineClient; |
|
if ( !engineClient ) |
|
{ |
|
Assert( !"No engineClient, bug???" ); |
|
return true; |
|
} |
|
|
|
return engineClient->IsActiveApp(); |
|
} |
|
|
|
virtual void RestartSoundSystem() |
|
{ |
|
Snd_Restart_f(); |
|
} |
|
|
|
virtual void GetAllManifestFiles( CUtlRBTree< FileNameHandle_t, int >& list ) |
|
{ |
|
list.RemoveAll(); |
|
|
|
// Load them in |
|
FileHandle_t resfilehandle = g_pFileSystem->Open( MAPLIST_FILE, "rb", "MOD" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) |
|
{ |
|
// Read in and parse mapcycle.txt |
|
int length = g_pFileSystem->Size(resfilehandle); |
|
if ( length > 0 ) |
|
{ |
|
char *pStart = (char *)new char[ length + 1 ]; |
|
if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) |
|
) |
|
{ |
|
pStart[ length ] = 0; |
|
const char *pFileList = pStart; |
|
|
|
while ( 1 ) |
|
{ |
|
pFileList = COM_Parse( pFileList ); |
|
if ( strlen( com_token ) <= 0 ) |
|
break; |
|
|
|
char manifest_file[ 512 ]; |
|
Q_snprintf( manifest_file, sizeof( manifest_file ), "%s/%s.manifest", AUDIOSOURCE_CACHE_ROOTDIR, com_token ); |
|
|
|
if ( g_pFileSystem->FileExists( manifest_file, "MOD" ) ) |
|
{ |
|
FileNameHandle_t handle = g_pFileSystem->FindOrAddFileName( manifest_file ); |
|
if ( list.Find( handle ) == list.InvalidIndex() ) |
|
{ |
|
list.Insert( handle ); |
|
} |
|
} |
|
|
|
// Any more tokens on this line? |
|
while ( COM_TokenWaiting( pFileList ) ) |
|
{ |
|
pFileList = COM_Parse( pFileList ); |
|
} |
|
} |
|
} |
|
delete[] pStart; |
|
} |
|
|
|
g_pFileSystem->Close(resfilehandle); |
|
} |
|
else |
|
{ |
|
Warning( "GetAllManifestFiles: Unable to load %s\n", MAPLIST_FILE ); |
|
} |
|
} |
|
|
|
virtual void GetAllSoundFilesInManifest( CUtlRBTree< FileNameHandle_t, int >& list, char const *manifestfile ) |
|
{ |
|
list.RemoveAll(); |
|
CacheSoundsFromResFile( true, list, manifestfile, false ); |
|
} |
|
|
|
virtual void GetAllSoundFilesReferencedInReslists( CUtlRBTree< FileNameHandle_t, int >& list ) |
|
{ |
|
char reslistdir[ MAX_PATH ]; |
|
Q_strncpy( reslistdir, MapReslistGenerator().GetResListDirectory(), sizeof( reslistdir ) ); |
|
list.RemoveAll(); |
|
|
|
// Load them in |
|
FileHandle_t resfilehandle = g_pFileSystem->Open( MAPLIST_FILE, "rb", "MOD" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) |
|
{ |
|
// Read in and parse mapcycle.txt |
|
int length = g_pFileSystem->Size(resfilehandle); |
|
if ( length > 0 ) |
|
{ |
|
char *pStart = (char *)new char[ length + 1 ]; |
|
if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) |
|
) |
|
{ |
|
pStart[ length ] = 0; |
|
const char *pFileList = pStart; |
|
|
|
while ( 1 ) |
|
{ |
|
char resfile[ 512 ]; |
|
|
|
pFileList = COM_Parse( pFileList ); |
|
if ( strlen( com_token ) <= 0 ) |
|
break; |
|
|
|
Q_snprintf( resfile, sizeof( resfile ), "%s\\%s.lst", reslistdir, com_token ); |
|
|
|
CacheSoundsFromResFile( false, list, resfile ); |
|
|
|
// Any more tokens on this line? |
|
while ( COM_TokenWaiting( pFileList ) ) |
|
{ |
|
pFileList = COM_Parse( pFileList ); |
|
} |
|
} |
|
} |
|
delete[] pStart; |
|
} |
|
|
|
g_pFileSystem->Close(resfilehandle); |
|
|
|
CacheSoundsFromResFile( false, list, CFmtStr( "%s\\engine.lst", reslistdir ) ); |
|
CacheSoundsFromResFile( false, list, CFmtStr( "%s\\all.lst", reslistdir ) ); |
|
} |
|
else |
|
{ |
|
Warning( "GetAllSoundFilesReferencedInReslists: Unable to load file %s\n", MAPLIST_FILE ); |
|
} |
|
} |
|
|
|
virtual void CacheBuildingStart() |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
EngineVGui()->ActivateGameUI(); |
|
EngineVGui()->StartCustomProgress(); |
|
const wchar_t *str = g_pVGuiLocalize->Find( "#Valve_CreatingCache" ); |
|
if ( str ) |
|
{ |
|
EngineVGui()->UpdateCustomProgressBar( 0.0f, str ); |
|
} |
|
} |
|
|
|
virtual void CacheBuildingUpdateProgress( float percent, char const *cachefile ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
const wchar_t *format = g_pVGuiLocalize->Find( "Valve_CreatingSpecificSoundCache" ); |
|
if ( format ) |
|
{ |
|
wchar_t constructed[ 1024 ]; |
|
wchar_t file[ 256 ]; |
|
g_pVGuiLocalize->ConvertANSIToUnicode( cachefile, file, sizeof( file ) ); |
|
|
|
g_pVGuiLocalize->ConstructString_safe( |
|
constructed, |
|
( wchar_t * )format, |
|
1, |
|
file ); |
|
|
|
EngineVGui()->UpdateCustomProgressBar( percent, constructed ); |
|
} |
|
} |
|
|
|
virtual void CacheBuildingFinish() |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
EngineVGui()->FinishCustomProgress(); |
|
EngineVGui()->HideGameUI(); |
|
} |
|
|
|
virtual int GetPrecachedSoundCount() |
|
{ |
|
if ( !sv.IsActive() ) |
|
return 0; |
|
|
|
INetworkStringTable *table = sv.GetSoundPrecacheTable(); |
|
if ( !table ) |
|
return 0; |
|
|
|
return table->GetNumStrings(); |
|
} |
|
|
|
virtual char const *GetPrecachedSound( int index ) |
|
{ |
|
Assert( sv.IsActive() ); |
|
|
|
INetworkStringTable *table = sv.GetSoundPrecacheTable(); |
|
if ( !table ) |
|
return ""; |
|
|
|
return table->GetString( index ); |
|
} |
|
|
|
virtual bool ShouldSuppressNonUISounds() |
|
{ |
|
return EngineVGui()->IsGameUIVisible() || IsGamePaused(); |
|
} |
|
|
|
virtual char const *GetUILanguage() |
|
{ |
|
extern ConVar cl_language; |
|
return cl_language.GetString(); |
|
} |
|
|
|
private: |
|
|
|
float m_frameTime; |
|
|
|
void CacheSoundsFromResFile( bool quiet, CUtlRBTree< FileNameHandle_t, int >& list, char const *resfile, bool checkandcleanname = true ) |
|
{ |
|
if ( !g_pFileSystem->FileExists( resfile, "MOD" ) ) |
|
{ |
|
Warning( "CacheSoundsFromResFile: Unable to find '%s'\n", resfile ); |
|
return; |
|
} |
|
|
|
int oldCount = list.Count(); |
|
|
|
FileHandle_t resfilehandle = g_pFileSystem->Open( resfile, "rb", "MOD" ); |
|
if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) |
|
{ |
|
// Read in and parse mapcycle.txt |
|
int length = g_pFileSystem->Size(resfilehandle); |
|
if ( length > 0 ) |
|
{ |
|
char *pStart = (char *)new char[ length + 1 ]; |
|
if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) |
|
) |
|
{ |
|
pStart[ length ] = 0; |
|
const char *pFileList = pStart; |
|
|
|
while ( 1 ) |
|
{ |
|
pFileList = COM_Parse( pFileList ); |
|
if ( strlen( com_token ) <= 0 ) |
|
break; |
|
|
|
if ( checkandcleanname ) |
|
{ |
|
if ( Q_stristr( com_token, ".wav" ) || |
|
Q_stristr( com_token, ".mp3" ) ) |
|
{ |
|
// skip past the game/mod directory "hl2/sound/player/footstep.wav" |
|
Q_FixSlashes(com_token); // "hl2\sound\player\footstep.wav" |
|
const char *pName = com_token; |
|
while (pName[0] && pName[0] != CORRECT_PATH_SEPARATOR) |
|
{ |
|
pName++; |
|
} // "\sound\player\footstep.wav" |
|
FileNameHandle_t handle = g_pFileSystem->FindOrAddFileName( pName+1 ); // "sound\player\footstep.wav" |
|
if ( list.Find( handle ) == list.InvalidIndex() ) |
|
{ |
|
list.Insert( handle ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
FileNameHandle_t handle = g_pFileSystem->FindOrAddFileName( com_token ); |
|
if ( list.Find( handle ) == list.InvalidIndex() ) |
|
{ |
|
list.Insert( handle ); |
|
} |
|
} |
|
} |
|
} |
|
delete[] pStart; |
|
} |
|
|
|
g_pFileSystem->Close(resfilehandle); |
|
} |
|
|
|
int newCount = list.Count(); |
|
|
|
if ( !quiet ) |
|
{ |
|
Msg( "Processing (%i new) from %s\n", newCount - oldCount, resfile ); |
|
} |
|
} |
|
|
|
virtual void OnSoundStarted( int guid, StartSoundParams_t& params, char const *soundname ) |
|
{ |
|
VPROF("OnSoundStarted"); |
|
|
|
if ( IsX360() || !toolframework->IsToolRecording() || params.suppressrecording ) |
|
return; |
|
|
|
KeyValues *msg = new KeyValues( "StartSound" ); |
|
msg->SetInt( "guid", guid ); |
|
msg->SetFloat( "time", cl.GetTime() ); |
|
msg->SetInt( "staticsound", params.staticsound ? 1 : 0 ); |
|
msg->SetInt( "soundsource", params.soundsource ); |
|
msg->SetInt( "entchannel", params.entchannel ); |
|
msg->SetString( "soundname", soundname ); |
|
msg->SetFloat( "originx", params.origin.x ); |
|
msg->SetFloat( "originy", params.origin.y ); |
|
msg->SetFloat( "originz", params.origin.z ); |
|
msg->SetFloat( "directionx", params.direction.x ); |
|
msg->SetFloat( "directiony", params.direction.y ); |
|
msg->SetFloat( "directionz", params.direction.z ); |
|
msg->SetInt( "updatepositions", params.bUpdatePositions ); |
|
msg->SetFloat( "fvol", params.fvol ); |
|
msg->SetInt( "soundlevel", (int)params.soundlevel ); |
|
msg->SetInt( "flags", params.flags ); |
|
msg->SetInt( "pitch", params.pitch ); |
|
msg->SetInt( "specialdsp", params.specialdsp ); |
|
msg->SetInt( "fromserver", params.fromserver ? 1 : 0 ); |
|
msg->SetFloat( "delay", params.delay ); |
|
msg->SetInt( "speakerentity", params.speakerentity ); |
|
|
|
toolframework->PostMessage( msg ); |
|
|
|
msg->deleteThis(); |
|
} |
|
|
|
virtual void OnSoundStopped( int guid, int soundsource, int channel, char const *soundname ) |
|
{ |
|
// NOTE: At the moment, if we don't receive a StartSound message but we do |
|
// receive a StopSound message, the StopSound message is ignored. In a perfect |
|
// world, if the StartSound message was not sent, a StopSound message should not |
|
// be sent for that guid either. This requires more plumbing, though, and |
|
// for the moment, it's not necessary to do that plumbing. |
|
|
|
VPROF("OnSoundStopped"); |
|
|
|
if ( IsX360() || !toolframework->IsToolRecording() ) |
|
return; |
|
|
|
KeyValues *msg = new KeyValues( "StopSound" ); |
|
msg->SetInt( "guid", guid ); |
|
msg->SetFloat( "time", cl.GetTime() ); |
|
msg->SetInt( "soundsource", soundsource ); |
|
msg->SetInt( "entchannel", channel ); |
|
msg->SetString( "soundname", soundname ); |
|
|
|
toolframework->PostMessage( msg ); |
|
|
|
msg->deleteThis(); |
|
} |
|
}; |
|
|
|
|
|
static CEngineSoundServices g_EngineSoundServices; |
|
ISoundServices *g_pSoundServices = &g_EngineSoundServices;
|
|
|