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.
453 lines
13 KiB
453 lines
13 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
|
|
#include "cbase.h" |
|
#include "soundscape_system.h" |
|
#include "soundscape.h" |
|
#include "KeyValues.h" |
|
#include "filesystem.h" |
|
#include "game.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define SOUNDSCAPE_MANIFEST_FILE "scripts/soundscapes_manifest.txt" |
|
|
|
CON_COMMAND(soundscape_flush, "Flushes the server & client side soundscapes") |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() ); |
|
if ( engine->IsDedicatedServer() ) |
|
{ |
|
// If it's a dedicated server, only the server console can run this. |
|
if ( pPlayer ) |
|
return; |
|
} |
|
else |
|
{ |
|
// If it's a listen server, only the listen server host can run this. |
|
if ( !pPlayer || pPlayer != UTIL_GetListenServerHost() ) |
|
return; |
|
} |
|
|
|
g_SoundscapeSystem.FlushSoundscapes(); // don't bother forgetting about the entities |
|
g_SoundscapeSystem.Init(); |
|
|
|
|
|
if ( engine->IsDedicatedServer() ) |
|
{ |
|
// If the ds console typed it, send it to everyone. |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pSendToPlayer = UTIL_PlayerByIndex( i ); |
|
if ( pSendToPlayer ) |
|
engine->ClientCommand( pSendToPlayer->edict(), "cl_soundscape_flush\n" ); |
|
} |
|
} |
|
else |
|
{ |
|
engine->ClientCommand( pPlayer->edict(), "cl_soundscape_flush\n" ); |
|
} |
|
} |
|
|
|
CSoundscapeSystem g_SoundscapeSystem( "CSoundscapeSystem" ); |
|
|
|
extern ConVar soundscape_debug; |
|
|
|
void CSoundscapeSystem::AddSoundscapeFile( const char *filename ) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
// Open the soundscape data file, and abort if we can't |
|
KeyValues *pKeyValuesData = new KeyValues( filename ); |
|
if ( filesystem->LoadKeyValues( *pKeyValuesData, IFileSystem::TYPE_SOUNDSCAPE, filename, "GAME" ) ) |
|
{ |
|
// parse out all of the top level sections and save their names |
|
KeyValues *pKeys = pKeyValuesData; |
|
while ( pKeys ) |
|
{ |
|
if ( pKeys->GetFirstSubKey() ) |
|
{ |
|
if ( g_pDeveloper->GetBool() ) |
|
{ |
|
if ( strstr( pKeys->GetName(), "{" ) ) |
|
{ |
|
Msg("Error parsing soundscape file %s after %s\n", filename, m_soundscapeCount>0 ?m_soundscapes.GetStringText( m_soundscapeCount-1 ) : "FIRST" ); |
|
} |
|
} |
|
m_soundscapes.AddString( pKeys->GetName(), m_soundscapeCount ); |
|
|
|
if ( IsX360() ) |
|
{ |
|
AddSoundscapeSounds( pKeys, m_soundscapeCount ); |
|
} |
|
m_soundscapeCount++; |
|
} |
|
pKeys = pKeys->GetNextKey(); |
|
} |
|
} |
|
pKeyValuesData->deleteThis(); |
|
} |
|
|
|
CON_COMMAND_F(sv_soundscape_printdebuginfo, "print soundscapes", FCVAR_CHEAT) |
|
{ |
|
if ( !UTIL_IsCommandIssuedByServerAdmin() ) |
|
return; |
|
|
|
g_SoundscapeSystem.PrintDebugInfo(); |
|
} |
|
|
|
|
|
void CSoundscapeSystem::PrintDebugInfo() |
|
{ |
|
Msg( "\n------- SERVER SOUNDSCAPES -------\n" ); |
|
for ( int key=m_soundscapes.First(); key != m_soundscapes.InvalidIndex(); key = m_soundscapes.Next( key ) ) |
|
{ |
|
int id = m_soundscapes.GetIDForKey( key ); |
|
const char *pName = m_soundscapes.GetStringForKey( key ); |
|
|
|
Msg( "- %d: %s\n", id, pName ); |
|
} |
|
Msg( "-------- SOUNDSCAPE ENTITIES -----\n" ); |
|
for( int entityIndex = 0; entityIndex < m_soundscapeEntities.Count(); ++entityIndex ) |
|
{ |
|
CEnvSoundscape *currentSoundscape = m_soundscapeEntities[entityIndex]; |
|
Msg("- %d: %s x:%.4f y:%.4f z:%.4f\n", |
|
entityIndex, |
|
STRING(currentSoundscape->GetSoundscapeName()), |
|
currentSoundscape->GetAbsOrigin().x, |
|
currentSoundscape->GetAbsOrigin().y, |
|
currentSoundscape->GetAbsOrigin().z |
|
); |
|
} |
|
Msg( "----------------------------------\n\n" ); |
|
} |
|
|
|
bool CSoundscapeSystem::Init() |
|
{ |
|
m_soundscapeCount = 0; |
|
|
|
const char *mapname = STRING( gpGlobals->mapname ); |
|
const char *mapSoundscapeFilename = NULL; |
|
if ( mapname && *mapname ) |
|
{ |
|
mapSoundscapeFilename = UTIL_VarArgs( "scripts/soundscapes_%s.txt", mapname ); |
|
} |
|
|
|
KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE ); |
|
if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) ) |
|
{ |
|
for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) |
|
{ |
|
if ( !Q_stricmp( sub->GetName(), "file" ) ) |
|
{ |
|
// Add |
|
AddSoundscapeFile( sub->GetString() ); |
|
if ( mapSoundscapeFilename && FStrEq( sub->GetString(), mapSoundscapeFilename ) ) |
|
{ |
|
mapSoundscapeFilename = NULL; // we've already loaded the map's soundscape |
|
} |
|
continue; |
|
} |
|
|
|
Warning( "CSoundscapeSystem::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n", |
|
SOUNDSCAPE_MANIFEST_FILE, sub->GetName() ); |
|
} |
|
|
|
if ( mapSoundscapeFilename && filesystem->FileExists( mapSoundscapeFilename ) ) |
|
{ |
|
AddSoundscapeFile( mapSoundscapeFilename ); |
|
} |
|
} |
|
else |
|
{ |
|
Error( "Unable to load manifest file '%s'\n", SOUNDSCAPE_MANIFEST_FILE ); |
|
} |
|
manifest->deleteThis(); |
|
m_activeIndex = 0; |
|
|
|
return true; |
|
} |
|
|
|
void CSoundscapeSystem::FlushSoundscapes( void ) |
|
{ |
|
m_soundscapeCount = 0; |
|
m_soundscapes.ClearStrings(); |
|
} |
|
|
|
void CSoundscapeSystem::Shutdown() |
|
{ |
|
FlushSoundscapes(); |
|
m_soundscapeEntities.RemoveAll(); |
|
m_activeIndex = 0; |
|
|
|
if ( IsX360() ) |
|
{ |
|
m_soundscapeSounds.Purge(); |
|
} |
|
} |
|
|
|
void CSoundscapeSystem::LevelInitPreEntity() |
|
{ |
|
g_SoundscapeSystem.Shutdown(); |
|
g_SoundscapeSystem.Init(); |
|
} |
|
|
|
void CSoundscapeSystem::LevelInitPostEntity() |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
m_soundscapeSounds.Purge(); |
|
} |
|
CUtlVector<bbox_t> clusterbounds; |
|
int clusterCount = engine->GetClusterCount(); |
|
clusterbounds.SetCount( clusterCount ); |
|
engine->GetAllClusterBounds( clusterbounds.Base(), clusterCount ); |
|
m_soundscapesInCluster.SetCount(clusterCount); |
|
for ( int i = 0; i < clusterCount; i++ ) |
|
{ |
|
m_soundscapesInCluster[i].soundscapeCount = 0; |
|
m_soundscapesInCluster[i].firstSoundscape = 0; |
|
} |
|
unsigned char myPVS[16 * 1024]; |
|
CUtlVector<short> clusterIndexList; |
|
CUtlVector<short> soundscapeIndexList; |
|
|
|
// find the clusters visible from each soundscape |
|
// add this soundscape to the list of soundscapes for that cluster, clip cluster bounds to radius |
|
for ( int i = 0; i < m_soundscapeEntities.Count(); i++ ) |
|
{ |
|
Vector position = m_soundscapeEntities[i]->GetAbsOrigin(); |
|
float radius = m_soundscapeEntities[i]->m_flRadius; |
|
float radiusSq = radius * radius; |
|
engine->GetPVSForCluster( engine->GetClusterForOrigin( position ), sizeof( myPVS ), myPVS ); |
|
for ( int j = 0; j < clusterCount; j++ ) |
|
{ |
|
if ( myPVS[ j >> 3 ] & (1<<(j&7)) ) |
|
{ |
|
float distSq = CalcSqrDistanceToAABB( clusterbounds[j].mins, clusterbounds[j].maxs, position ); |
|
if ( distSq < radiusSq ) |
|
{ |
|
m_soundscapesInCluster[j].soundscapeCount++; |
|
clusterIndexList.AddToTail(j); |
|
// UNDONE: Technically you just need a soundscape index and a count for this list. |
|
soundscapeIndexList.AddToTail(i); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// basically this part is like a radix sort |
|
// this is how many entries we need in the soundscape index list |
|
m_soundscapeIndexList.SetCount(soundscapeIndexList.Count()); |
|
|
|
// now compute the starting index of each cluster |
|
int firstSoundscape = 0; |
|
for ( int i = 0; i < clusterCount; i++ ) |
|
{ |
|
m_soundscapesInCluster[i].firstSoundscape = firstSoundscape; |
|
firstSoundscape += m_soundscapesInCluster[i].soundscapeCount; |
|
m_soundscapesInCluster[i].soundscapeCount = 0; |
|
} |
|
// now add each soundscape index to the appropriate cluster's list |
|
// The resulting list is precomputing all soundscapes that need to be checked for a player |
|
// in each cluster. This is used to accelerate the per-frame operations |
|
for ( int i = 0; i < soundscapeIndexList.Count(); i++ ) |
|
{ |
|
int cluster = clusterIndexList[i]; |
|
int outIndex = m_soundscapesInCluster[cluster].soundscapeCount + m_soundscapesInCluster[cluster].firstSoundscape; |
|
m_soundscapesInCluster[cluster].soundscapeCount++; |
|
m_soundscapeIndexList[outIndex] = soundscapeIndexList[i]; |
|
} |
|
} |
|
|
|
int CSoundscapeSystem::GetSoundscapeIndex( const char *pName ) |
|
{ |
|
return m_soundscapes.GetStringID( pName ); |
|
} |
|
|
|
bool CSoundscapeSystem::IsValidIndex( int index ) |
|
{ |
|
if ( index >= 0 && index < m_soundscapeCount ) |
|
return true; |
|
return false; |
|
} |
|
|
|
void CSoundscapeSystem::AddSoundscapeEntity( CEnvSoundscape *pSoundscape ) |
|
{ |
|
if ( m_soundscapeEntities.Find( pSoundscape ) == -1 ) |
|
{ |
|
int index = m_soundscapeEntities.AddToTail( pSoundscape ); |
|
pSoundscape->m_soundscapeEntityId = index + 1; |
|
} |
|
} |
|
|
|
void CSoundscapeSystem::RemoveSoundscapeEntity( CEnvSoundscape *pSoundscape ) |
|
{ |
|
m_soundscapeEntities.FindAndRemove( pSoundscape ); |
|
pSoundscape->m_soundscapeEntityId = -1; |
|
} |
|
|
|
void CSoundscapeSystem::FrameUpdatePostEntityThink() |
|
{ |
|
int total = m_soundscapeEntities.Count(); |
|
if ( total > 0 ) |
|
{ |
|
int traceCount = 0; |
|
int playerCount = 0; |
|
// budget to do 2 players with 2 traces each tick. Anything more expensive than that should |
|
// get spread across multiple ticks |
|
int maxPlayers = 2; |
|
int maxTraces = maxPlayers * 2; |
|
if ( soundscape_debug.GetBool() ) |
|
{ |
|
maxTraces = 9999; |
|
maxPlayers = MAX_PLAYERS; |
|
} |
|
|
|
// load balance across server ticks a bit by limiting the numbers of players (get cluster for origin) |
|
// and traces processed in a single tick. In single player this will update the player every tick |
|
// because it always does at least one player's full load of work |
|
for ( int i = 0; i < gpGlobals->maxClients && traceCount <= maxTraces && playerCount <= maxPlayers; i++ ) |
|
{ |
|
m_activeIndex = (m_activeIndex+1) % gpGlobals->maxClients; |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( m_activeIndex + 1 ); |
|
if ( pPlayer && pPlayer->IsNetClient() ) |
|
{ |
|
// check to see if this is the sound entity that is |
|
// currently affecting this player |
|
audioparams_t &audio = pPlayer->GetAudioParams(); |
|
|
|
// if we got this far, we're looking at an entity that is contending |
|
// for current player sound. the closest entity to player wins. |
|
CEnvSoundscape *pCurrent = NULL; |
|
if ( audio.entIndex > 0 && audio.entIndex <= m_soundscapeEntities.Count() ) |
|
{ |
|
int ssIndex = audio.entIndex - 1; |
|
pCurrent = m_soundscapeEntities[ssIndex]; |
|
} |
|
ss_update_t update; |
|
update.pPlayer = pPlayer; |
|
update.pCurrentSoundscape = pCurrent; |
|
update.playerPosition = pPlayer->EarPosition(); |
|
update.bInRange = false; |
|
update.currentDistance = 0; |
|
update.traceCount = 0; |
|
if ( pCurrent ) |
|
{ |
|
pCurrent->UpdateForPlayer(update); |
|
} |
|
|
|
int clusterIndex = engine->GetClusterForOrigin( update.playerPosition ); |
|
|
|
if ( clusterIndex >= 0 && clusterIndex < m_soundscapesInCluster.Count() ) |
|
{ |
|
// find all soundscapes that could possibly attach to this player and update them |
|
for ( int j = 0; j < m_soundscapesInCluster[clusterIndex].soundscapeCount; j++ ) |
|
{ |
|
int ssIndex = m_soundscapeIndexList[m_soundscapesInCluster[clusterIndex].firstSoundscape + j]; |
|
if ( m_soundscapeEntities[ssIndex] == update.pCurrentSoundscape ) |
|
continue; |
|
m_soundscapeEntities[ssIndex]->UpdateForPlayer( update ); |
|
} |
|
} |
|
playerCount++; |
|
traceCount += update.traceCount; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CSoundscapeSystem::AddSoundscapeSounds( KeyValues *pSoundscape, int soundscapeIndex ) |
|
{ |
|
if ( !IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
int i = m_soundscapeSounds.AddToTail(); |
|
Assert( i == soundscapeIndex ); |
|
|
|
KeyValues *pKey = pSoundscape->GetFirstSubKey(); |
|
while ( pKey ) |
|
{ |
|
if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) ) |
|
{ |
|
KeyValues *pAmbientKey = pKey->GetFirstSubKey(); |
|
while ( pAmbientKey ) |
|
{ |
|
if ( !Q_strcasecmp( pAmbientKey->GetName(), "wave" ) ) |
|
{ |
|
char const *pSoundName = pAmbientKey->GetString(); |
|
m_soundscapeSounds[i].AddToTail( pSoundName ); |
|
} |
|
pAmbientKey = pAmbientKey->GetNextKey(); |
|
} |
|
} |
|
else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) ) |
|
{ |
|
KeyValues *pRandomKey = pKey->GetFirstSubKey(); |
|
while ( pRandomKey ) |
|
{ |
|
if ( !Q_strcasecmp( pRandomKey->GetName(), "rndwave" ) ) |
|
{ |
|
KeyValues *pRndWaveKey = pRandomKey->GetFirstSubKey(); |
|
while ( pRndWaveKey ) |
|
{ |
|
if ( !Q_strcasecmp( pRndWaveKey->GetName(), "wave" ) ) |
|
{ |
|
char const *pSoundName = pRndWaveKey->GetString(); |
|
m_soundscapeSounds[i].AddToTail( pSoundName ); |
|
} |
|
pRndWaveKey = pRndWaveKey->GetNextKey(); |
|
} |
|
} |
|
pRandomKey = pRandomKey->GetNextKey(); |
|
} |
|
} |
|
else if ( !Q_strcasecmp( pKey->GetName(), "playsoundscape" ) ) |
|
{ |
|
KeyValues *pPlayKey = pKey->GetFirstSubKey(); |
|
while ( pPlayKey ) |
|
{ |
|
if ( !Q_strcasecmp( pPlayKey->GetName(), "name" ) ) |
|
{ |
|
char const *pSoundName = pPlayKey->GetString(); |
|
m_soundscapeSounds[i].AddToTail( pSoundName ); |
|
} |
|
pPlayKey = pPlayKey->GetNextKey(); |
|
} |
|
} |
|
pKey = pKey->GetNextKey(); |
|
} |
|
} |
|
|
|
void CSoundscapeSystem::PrecacheSounds( int soundscapeIndex ) |
|
{ |
|
if ( !IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( !IsValidIndex( soundscapeIndex ) ) |
|
{ |
|
return; |
|
} |
|
|
|
int count = m_soundscapeSounds[soundscapeIndex].Count(); |
|
for ( int i=0; i<count; i++ ) |
|
{ |
|
const char *pSound = m_soundscapeSounds[soundscapeIndex][i]; |
|
if ( Q_stristr( pSound, ".wav" ) ) |
|
{ |
|
CBaseEntity::PrecacheSound( pSound ); |
|
} |
|
else |
|
{ |
|
// recurse into new soundscape |
|
PrecacheSounds( GetSoundscapeIndex( pSound ) ); |
|
} |
|
} |
|
}
|
|
|