/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see .
===========================================================================
*/
// Ambient Sound System (ASS!)
#include "../server/exe_headers.h"
#include "client.h"
#include "snd_ambient.h"
#include "snd_local.h"
static const int MAX_SET_VOLUME = 255;
static void AS_GetGeneralSet( ambientSet_t & );
static void AS_GetLocalSet( ambientSet_t & );
static void AS_GetBModelSet( ambientSet_t & );
//Current set and old set for crossfading
static int currentSet = -1;
static int oldSet = -1;
static int crossDelay = 1000; //1 second
static int currentSetTime = 0;
static int oldSetTime = 0;
// Globals for debug purposes
static int numSets = 0;
// Main ambient sound group
static CSetGroup* aSets = NULL;
// Globals for speed, blech
static char *parseBuffer = NULL;
static int parseSize = 0;
static int parsePos = 0;
static char tempBuffer[1024];
//NOTENOTE: Be sure to change the mirrored code in g_spawn.cpp, and cg_main.cpp
typedef std::map namePrecache_m;
static namePrecache_m *pMap;
// Used for enum / string matching
static const char *setNames[NUM_AS_SETS] =
{
"generalSet",
"localSet",
"bmodelSet",
};
// Used for enum / function matching
static const parseFunc_t parseFuncs[NUM_AS_SETS] =
{
AS_GetGeneralSet,
AS_GetLocalSet,
AS_GetBModelSet,
};
// Used for keyword / enum matching
static const char *keywordNames[NUM_AS_KEYWORDS]=
{
"timeBetweenWaves",
"subWaves",
"loopedWave",
"volRange",
"radius",
"type",
"amsdir",
"outdir",
"basedir",
};
CSetGroup::CSetGroup(void)
{
m_ambientSets = new std::vector;
m_setMap = new std::map;
m_numSets = 0;
}
CSetGroup::~CSetGroup(void)
{
delete m_ambientSets;
delete m_setMap;
}
/*
-------------------------
Free
-------------------------
*/
void CSetGroup::Free( void )
{
std::vector::iterator ai;
for ( ai = m_ambientSets->begin(); ai != m_ambientSets->end(); ++ai )
{
Z_Free ( (*ai) );
}
//Do this in place of clear() so it *really* frees the memory.
delete m_ambientSets;
delete m_setMap;
m_ambientSets = new std::vector;
m_setMap = new std::map;
m_numSets = 0;
}
/*
-------------------------
AddSet
-------------------------
*/
ambientSet_t *CSetGroup::AddSet( const char *name )
{
ambientSet_t *set;
//Allocate the memory
set = (ambientSet_t *) Z_Malloc( sizeof( ambientSet_t ), TAG_AMBIENTSET, qtrue);
//Set up some defaults
Q_strncpyz(set->name,name,sizeof(set->name));
set->loopedVolume = MAX_SET_VOLUME;
set->masterVolume = MAX_SET_VOLUME;
set->radius = 250;
set->time_start = 10;
set->time_end = 25;
set->volRange_start = MAX_SET_VOLUME;
set->volRange_end = MAX_SET_VOLUME;
m_ambientSets->insert( m_ambientSets->end(), set );
set->id = m_numSets++;
//Map the name to the pointer for reference later
(*m_setMap)[name] = set;
return set;
}
/*
-------------------------
GetSet
-------------------------
*/
ambientSet_t *CSetGroup::GetSet( const char *name )
{
std::map::iterator mi;
if ( name == NULL )
return NULL;
mi = m_setMap->find( name );
if ( mi == m_setMap->end() )
return NULL;
return (*mi).second;
}
ambientSet_t *CSetGroup::GetSet( int ID )
{
if ( m_ambientSets->empty() )
return NULL;
if ( ID < 0 )
return NULL;
if ( ID >= m_numSets )
return NULL;
return (*m_ambientSets)[ID];
}
/*
===============================================
File Parsing
===============================================
*/
/*
-------------------------
AS_GetSetNameIDForString
-------------------------
*/
static int AS_GetSetNameIDForString( const char *name )
{
//Make sure it's valid
if ( name == NULL || name[0] == '\0' )
return -1;
for ( int i = 0; i < NUM_AS_SETS; i++ )
{
if ( Q_stricmp( name, setNames[i] ) == 0 )
return i;
}
return -1;
}
/*
-------------------------
AS_GetKeywordIDForString
-------------------------
*/
static int AS_GetKeywordIDForString( const char *name )
{
//Make sure it's valid
if ( name == NULL || name[0] == '\0' )
return -1;
for ( int i = 0; i < NUM_AS_KEYWORDS; i++ )
{
if ( Q_stricmp( name, keywordNames[i] ) == 0 )
return i;
}
return -1;
}
/*
-------------------------
AS_SkipLine
Skips a line in the character buffer
-------------------------
*/
static void AS_SkipLine( void )
{
if ( parsePos > parseSize ) // needed to avoid a crash because of some OOR access that shouldn't be done
return;
while ( (parseBuffer[parsePos] != '\n') && (parseBuffer[parsePos] != '\r') )
{
parsePos++;
if ( parsePos > parseSize )
return;
}
parsePos++;
}
/*
-------------------------
AS_GetTimeBetweenWaves
getTimeBetweenWaves
-------------------------
*/
static void AS_GetTimeBetweenWaves( ambientSet_t &set )
{
int startTime, endTime;
//Get the data
sscanf( parseBuffer+parsePos, "%s %d %d", tempBuffer, &startTime, &endTime );
//Check for swapped start / end
if ( startTime > endTime )
{
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_YELLOW"WARNING: Corrected swapped start / end times in a \"timeBetweenWaves\" keyword\n");
#endif
int swap = startTime;
startTime = endTime;
endTime = swap;
}
//Store it
set.time_start = startTime;
set.time_end = endTime;
AS_SkipLine();
}
/*
-------------------------
AS_GetSubWaves
subWaves ...
-------------------------
*/
static void AS_GetSubWaves( ambientSet_t &set )
{
char dirBuffer[512], waveBuffer[256], waveName[1024];
//Get the directory for these sets
sscanf( parseBuffer+parsePos, "%s %s", tempBuffer, dirBuffer );
//Move the pointer past these two strings
parsePos += ((strlen(keywordNames[SET_KEYWORD_SUBWAVES])+1) + (strlen(dirBuffer)+1));
//Get all the subwaves
while ( parsePos <= parseSize )
{
//Get the data
sscanf( parseBuffer+parsePos, "%s", waveBuffer );
if ( set.numSubWaves >= MAX_WAVES_PER_GROUP )
{
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_YELLOW"WARNING: Too many subwaves on set \"%s\"\n", set.name );
#endif
}
else
{
//Construct the wave name (pretty, huh?)
Com_sprintf( waveName, sizeof(waveName), "sound/%s/%s.wav", dirBuffer, waveBuffer );
//Place this onto the sound directory name
//Precache the file at this point and store off the ID instead of the name
if ( ( set.subWaves[set.numSubWaves++] = S_RegisterSound( waveName ) ) <= 0 )
{
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_RED"ERROR: Unable to load ambient sound \"%s\"\n", waveName);
#endif
}
}
//Move the pointer past this string
parsePos += strlen(waveBuffer)+1;
if ( ( (parseBuffer+parsePos)[0] == '\n') || ( (parseBuffer+parsePos)[0] == '\r') )
break;
}
AS_SkipLine();
}
/*
-------------------------
AS_GetLoopedWave
loopedWave
-------------------------
*/
static void AS_GetLoopedWave( ambientSet_t &set )
{
char waveBuffer[256], waveName[1024];
//Get the looped wave name
sscanf( parseBuffer+parsePos, "%s %s", tempBuffer, waveBuffer );
//Construct the wave name
Com_sprintf( waveName, sizeof(waveName), "sound/%s.wav", waveBuffer );
//Precache the file at this point and store off the ID instead of the name
if ( ( set.loopedWave = S_RegisterSound( waveName ) ) <= 0 )
{
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_RED"ERROR: Unable to load ambient sound \"%s\"\n", waveName);
#endif
}
AS_SkipLine();
}
/*
-------------------------
AS_GetVolumeRange
-------------------------
*/
static void AS_GetVolumeRange( ambientSet_t &set )
{
int min, max;
//Get the data
sscanf( parseBuffer+parsePos, "%s %d %d", tempBuffer, &min, &max );
//Check for swapped min / max
if ( min > max )
{
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_YELLOW"WARNING: Corrected swapped min / max range in a \"volRange\" keyword\n");
#endif
int swap = min;
min = max;
max = swap;
}
//Store the data
set.volRange_start = min;
set.volRange_end = max;
AS_SkipLine();
}
/*
-------------------------
AS_GetRadius
-------------------------
*/
static void AS_GetRadius( ambientSet_t &set )
{
//Get the data
sscanf( parseBuffer+parsePos, "%s %d", tempBuffer, &set.radius );
AS_SkipLine();
}
/*
-------------------------
AS_GetGeneralSet
-------------------------
*/
static void AS_GetGeneralSet( ambientSet_t &set )
{
int keywordID;
//The other parameters of the set come in a specific order
while ( parsePos <= parseSize )
{
int iFieldsScanned = sscanf( parseBuffer+parsePos, "%s", tempBuffer );
if (iFieldsScanned <= 0)
return;
keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer );
//Find and parse the keyword info
switch ( keywordID )
{
case SET_KEYWORD_TIMEBETWEENWAVES:
AS_GetTimeBetweenWaves( set );
break;
case SET_KEYWORD_SUBWAVES:
AS_GetSubWaves( set );
break;
case SET_KEYWORD_LOOPEDWAVE:
AS_GetLoopedWave( set );
break;
case SET_KEYWORD_VOLRANGE:
AS_GetVolumeRange( set );
break;
default:
//Check to see if we've finished this group
if ( AS_GetSetNameIDForString( (const char *) &tempBuffer ) == -1 )
{
//Ignore comments
if ( tempBuffer[0] == ';' )
return;
//This wasn't a set name, so it's an error
#ifndef FINAL_BUILD
Com_Printf( S_COLOR_YELLOW"WARNING: Unknown ambient set keyword \"%s\"\n", tempBuffer );
#endif
}
return;
break;
}
}
}
/*
-------------------------
AS_GetLocalSet
-------------------------
*/
static void AS_GetLocalSet( ambientSet_t &set )
{
int keywordID;
//The other parameters of the set come in a specific order
while ( parsePos <= parseSize )
{
int iFieldsScanned = sscanf( parseBuffer+parsePos, "%s", tempBuffer );
if (iFieldsScanned <= 0)
return;
keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer );
//Find and parse the keyword info
switch ( keywordID )
{
case SET_KEYWORD_TIMEBETWEENWAVES:
AS_GetTimeBetweenWaves( set );
break;
case SET_KEYWORD_SUBWAVES:
AS_GetSubWaves( set );
break;
case SET_KEYWORD_LOOPEDWAVE:
AS_GetLoopedWave( set );
break;
case SET_KEYWORD_VOLRANGE:
AS_GetVolumeRange( set );
break;
case SET_KEYWORD_RADIUS:
AS_GetRadius( set );
break;
default:
//Check to see if we've finished this group
if ( AS_GetSetNameIDForString( (const char *) &tempBuffer ) == -1 )
{
//Ignore comments
if ( tempBuffer[0] == ';' )
return;
//This wasn't a set name, so it's an error
#ifndef FINAL_BUILD
Com_Printf( S_COLOR_YELLOW"WARNING: Unknown ambient set keyword \"%s\"\n", tempBuffer );
#endif
}
return;
break;
}
}
}
/*
-------------------------
AS_GetBModelSet
-------------------------
*/
static void AS_GetBModelSet( ambientSet_t &set )
{
int keywordID;
//The other parameters of the set come in a specific order
while ( parsePos <= parseSize )
{
int iFieldsScanned = sscanf( parseBuffer+parsePos, "%s", tempBuffer );
if (iFieldsScanned <= 0)
return;
keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer );
//Find and parse the keyword info
switch ( keywordID )
{
case SET_KEYWORD_SUBWAVES:
AS_GetSubWaves( set );
break;
default:
//Check to see if we've finished this group
if ( AS_GetSetNameIDForString( (const char *) &tempBuffer ) == -1 )
{
//Ignore comments
if ( tempBuffer[0] == ';' )
return;
//This wasn't a set name, so it's an error
#ifndef FINAL_BUILD
Com_Printf( S_COLOR_YELLOW"WARNING: Unknown ambient set keyword \"%s\"\n", tempBuffer );
#endif
}
return;
break;
}
}
}
/*
-------------------------
AS_ParseSet
Parses an individual set group out of a set file buffer
-------------------------
*/
static qboolean AS_ParseSet( int setID, CSetGroup *sg )
{
ambientSet_t *set;
const char *name;
//Make sure we're not overstepping the name array
if ( setID >= NUM_AS_SETS )
return qfalse;
//Reset the pointers for this run through
parsePos = 0;
name = setNames[setID];
//Iterate through the whole file and find every occurance of a set
while ( parsePos <= parseSize )
{
//Check for a valid set group
if ( Q_strncmp( parseBuffer+parsePos, name, strlen(name) ) == 0 )
{
//Update the debug info
numSets++;
//Push past the set specifier and on to the name
parsePos+=strlen(name)+1; //Also take the following space out
//Get the set name (this MUST be first)
sscanf( parseBuffer+parsePos, "%s", tempBuffer );
AS_SkipLine();
//Test the string against the precaches
if ( tempBuffer[0] )
{
//Not in our precache listings, so skip it
if ( ( pMap->find( (const char *) &tempBuffer ) == pMap->end() ) )
continue;
}
//Create a new set
set = sg->AddSet( (const char *) &tempBuffer );
//Run the function to parse the data out
parseFuncs[setID]( *set );
continue;
}
//If not found on this line, go down another and check again
AS_SkipLine();
}
return qtrue;
}
/*
-------------------------
AS_ParseHeader
Parses the directory information out of the beginning of the file
-------------------------
*/
static void AS_ParseHeader( void )
{
char typeBuffer[128];
int keywordID;
while ( parsePos <= parseSize )
{
sscanf( parseBuffer+parsePos, "%s", tempBuffer );
keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer );
switch ( keywordID )
{
case SET_KEYWORD_TYPE:
sscanf( parseBuffer+parsePos, "%s %s", tempBuffer, typeBuffer );
if ( !Q_stricmp( (const char *) typeBuffer, "ambientSet" ) )
{
return;
}
Com_Error( ERR_DROP, "AS_ParseHeader: Set type \"%s\" is not a valid set type!\n", typeBuffer );
break;
case SET_KEYWORD_AMSDIR:
//TODO: Implement
break;
case SET_KEYWORD_OUTDIR:
//TODO: Implement
break;
case SET_KEYWORD_BASEDIR:
//TODO: Implement
break;
}
AS_SkipLine();
}
}
/*
-------------------------
AS_ParseFile
Opens and parses a sound set file
-------------------------
*/
static qboolean AS_ParseFile( const char *filename, CSetGroup *sg )
{
//Open the file and read the information from it
parseSize = FS_ReadFile( filename, (void **) &parseBuffer );
if ( parseSize <= 0 )
return qfalse;
//Parse the directory information out of the file
AS_ParseHeader();
//Parse all the relevent sets out of it
for ( int i = 0; i < NUM_AS_SETS; i++ )
AS_ParseSet( i, sg );
//Free the memory and close the file
FS_FreeFile( parseBuffer );
return qtrue;
}
/*
===============================================
Main code
===============================================
*/
/*
-------------------------
AS_Init
Loads the ambient sound sets and prepares to play them when needed
-------------------------
*/
static namePrecache_m *TheNamePrecache()
{
// we use these singletons so we can find memory leaks
// if you let things like this leak, you never can tell
// what is really leaking and what is merely not ever freed
static namePrecache_m singleton;
return &singleton;
}
void AS_Init( void )
{
if (!aSets)
{
numSets = 0;
pMap = TheNamePrecache();
//Setup the structure
aSets = new CSetGroup();
aSets->Init();
}
}
/*
-------------------------
AS_AddPrecacheEntry
-------------------------
*/
void AS_AddPrecacheEntry( const char *name ) {
if ( !pMap ) { // s_initsound 0 probably
return;
}
if (!Q_stricmp(name,"#clear"))
{
pMap->clear();
currentSet = -1;
oldSet = -1;
}
else
{
(*pMap)[ name ] = 1;
}
}
/*
-------------------------
AS_ParseSets
Called on the client side to load and precache all the ambient sound sets
-------------------------
*/
void AS_ParseSets( void )
{
if ( !s_initsound->integer ) {
return;
}
AS_Init();
//Parse all the sets
if ( AS_ParseFile( AMBIENT_SET_FILENAME, aSets ) == qfalse )
{
Com_Error ( ERR_FATAL, S_COLOR_RED"ERROR: Couldn't load ambient sound sets from %s", AMBIENT_SET_FILENAME );
}
//Com_Printf( "AS_ParseFile: Loaded %d of %d ambient set(s)\n", pMap.size(), numSets );
int iErrorsOccured = 0;
for (namePrecache_m::iterator it = pMap->begin(); it != pMap->end(); ++it)
{
const char* str = (*it).first.c_str();
ambientSet_t *aSet = aSets->GetSet( str );
if (!aSet)
{
// I print these red instead of yellow because they're going to cause an ERR_DROP if they occur
Com_Printf( S_COLOR_RED"ERROR: AS_ParseSets: Unable to find ambient soundset \"%s\"!\n",str);
iErrorsOccured++;
}
}
if (iErrorsOccured)
{
Com_Error( ERR_DROP, "....%d missing sound sets! (see above)\n", iErrorsOccured);
}
// //Done with the precache info, it will be rebuilt on a restart
// pMap->clear(); // do NOT do this here now
}
/*
-------------------------
AS_Free
Frees up the ambient sound system
-------------------------
*/
void AS_Free( void )
{
if (aSets)
{
aSets->Free();
delete aSets;
aSets = NULL;
currentSet = -1;
oldSet = -1;
currentSetTime = 0;
oldSetTime = 0;
numSets = 0;
}
}
void AS_FreePartial(void)
{
if (aSets)
{
aSets->Free();
currentSet = -1;
oldSet = -1;
currentSetTime = 0;
oldSetTime = 0;
numSets = 0;
pMap = TheNamePrecache();
pMap->clear();
}
}
/*
===============================================
Sound code
===============================================
*/
/*
-------------------------
AS_UpdateSetVolumes
Fades volumes up or down depending on the action being taken on them.
-------------------------
*/
static void AS_UpdateSetVolumes( void ) {
if ( !aSets ) {
return;
}
//Get the sets and validate them
ambientSet_t *current = aSets->GetSet( currentSet );
if ( !current ) {
return;
}
float scale;
int deltaTime;
if ( current->masterVolume < MAX_SET_VOLUME ) {
deltaTime = cls.realtime - current->fadeTime;
scale = ((float)(deltaTime)/(float)(crossDelay));
current->masterVolume = (int)((scale) * (float)MAX_SET_VOLUME);
}
if ( current->masterVolume > MAX_SET_VOLUME ) {
current->masterVolume = MAX_SET_VOLUME;
}
//Only update the old set if it's still valid
if ( oldSet == -1 ) {
return;
}
ambientSet_t *old = aSets->GetSet( oldSet );
if ( !old ) {
return;
}
//Update the volumes
if ( old->masterVolume > 0 ) {
deltaTime = cls.realtime - old->fadeTime;
scale = ((float)(deltaTime)/(float)(crossDelay));
old->masterVolume = MAX_SET_VOLUME - (int)((scale) * (float)MAX_SET_VOLUME);
}
if ( old->masterVolume <= 0 ) {
old->masterVolume = 0;
oldSet = -1;
}
}
/*
-------------------------
S_UpdateCurrentSet
Does internal maintenance to keep track of changing sets.
-------------------------
*/
static void AS_UpdateCurrentSet( int id ) {
if ( !aSets ) {
return;
}
//Check for a change
if ( id != currentSet ) {
//This is new, so start the fading
oldSet = currentSet;
currentSet = id;
ambientSet_t *current = aSets->GetSet( currentSet );
// Ste, I just put this null check in for now, not sure if there's a more graceful way to exit this function - dmv
if ( !current ) {
return;
}
ambientSet_t *old = aSets->GetSet( oldSet );
if ( old ) {
old->masterVolume = MAX_SET_VOLUME;
old->fadeTime = cls.realtime;
}
current->masterVolume = 0;
//Set the fading starts
current->fadeTime = cls.realtime;
}
//Update their volumes if fading
AS_UpdateSetVolumes();
}
/*
-------------------------
AS_PlayLocalSet
Plays a local set taking volume and subwave playing into account.
Alters lastTime to reflect the time updates.
-------------------------
*/
static void AS_PlayLocalSet( vec3_t listener_origin, vec3_t origin, const ambientSet_t *set, int entID, int *lastTime ) {
//Make sure it's valid
if ( !set ) {
return;
}
vec3_t dir;
VectorSubtract( origin, listener_origin, dir );
float dist = VectorLength( dir );
//Determine the volume based on distance (NOTE: This sits on top of what SpatializeOrigin does)
float distScale = ( dist < ( set->radius * 0.5f ) ) ? 1 : ( set->radius - dist ) / ( set->radius * 0.5f );
unsigned char volume = ( distScale > 1.0f || distScale < 0.0f ) ? 0 : (unsigned char) ( set->masterVolume * distScale );
//Add the looping sound
if ( set->loopedWave ) {
S_AddAmbientLoopingSound( origin, volume, set->loopedWave );
}
//Check the time to start another one-shot subwave
int time = cl.serverTime;
if ( ( time - *lastTime ) < ( ( Q_irand( set->time_start, set->time_end ) ) * 1000 ) ) {
return;
}
//Update the time
*lastTime = time;
//Scale the volume ranges for the subwaves based on the overall master volume
float volScale = (float) volume / (float) MAX_SET_VOLUME;
volume = (unsigned char) Q_irand( (int)(volScale*set->volRange_start), (int)(volScale*set->volRange_end) );
//Add the random subwave
if ( set->numSubWaves ) {
S_StartAmbientSound( origin, entID, volume, set->subWaves[Q_irand( 0, set->numSubWaves-1)] );
}
}
/*
-------------------------
AS_PlayAmbientSet
Plays an ambient set taking volume and subwave playing into account.
Alters lastTime to reflect the time updates.
-------------------------
*/
static void AS_PlayAmbientSet( vec3_t origin, const ambientSet_t *set, int *lastTime ) {
//Make sure it's valid
if ( !set ) {
return;
}
//Add the looping sound
if ( set->loopedWave ) {
S_AddAmbientLoopingSound( origin, (unsigned char) set->masterVolume, set->loopedWave );
}
//Check the time to start another one-shot subwave
int time = cls.realtime;
if ( ( time - *lastTime ) < ( ( Q_irand( set->time_start, set->time_end ) ) * 1000 ) ) {
return;
}
//Update the time
*lastTime = time;
//Scale the volume ranges for the subwaves based on the overall master volume
float volScale = (float) set->masterVolume / (float) MAX_SET_VOLUME;
unsigned char volume = Q_irand( (int)(volScale*set->volRange_start), (int)(volScale*set->volRange_end) );
//Allow for softer noises than the masterVolume, but not louder
if ( volume > set->masterVolume ) {
volume = set->masterVolume;
}
//Add the random subwave
if ( set->numSubWaves ) {
S_StartAmbientSound( origin, 0, volume, set->subWaves[Q_irand( 0, set->numSubWaves-1)] );
}
}
/*
-------------------------
S_UpdateAmbientSet
Does maintenance and plays the ambient sets (two if crossfading)
-------------------------
*/
void S_UpdateAmbientSet( const char *name, vec3_t origin ) {
if ( !aSets ) {
return;
}
const ambientSet_t *set = aSets->GetSet( name );
if ( !set ) {
return;
}
//Update the current and old set for crossfading
AS_UpdateCurrentSet( set->id );
const ambientSet_t *current = aSets->GetSet( currentSet );
if ( current ) {
AS_PlayAmbientSet( origin, set, ¤tSetTime );
}
const ambientSet_t *old = aSets->GetSet( oldSet );
if ( old ) {
AS_PlayAmbientSet( origin, old, &oldSetTime );
}
}
/*
-------------------------
S_AddLocalSet
-------------------------
*/
int S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ) {
if ( !aSets ) {
return cl.serverTime;
}
const ambientSet_t *set = aSets->GetSet( name );
if ( !set ) {
return cl.serverTime;
}
int currentTime = time;
AS_PlayLocalSet( listener_origin, origin, set, entID, ¤tTime );
return currentTime;
}
/*
-------------------------
AS_GetBModelSound
-------------------------
*/
sfxHandle_t AS_GetBModelSound( const char *name, int stage ) {
if ( !aSets ) {
return -1;
}
//Stage must be within a valid range
const ambientSet_t *set = aSets->GetSet( name );
if ( !set || stage < 0 || stage > (set->numSubWaves - 1) ) {
return -1;
}
return set->subWaves[stage];
}