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.
808 lines
22 KiB
808 lines
22 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "soundent.h" |
|
#include "game.h" |
|
#include "world.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Some enumerations needed by CSoundEnt |
|
//----------------------------------------------------------------------------- |
|
|
|
// identifiers passed to functions that can operate on either list, to indicate which list to operate on. |
|
#define SOUNDLISTTYPE_FREE 1 |
|
#define SOUNDLISTTYPE_ACTIVE 2 |
|
|
|
|
|
|
|
LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); |
|
|
|
static CSoundEnt *g_pSoundEnt = NULL; |
|
|
|
BEGIN_SIMPLE_DATADESC( CSound ) |
|
|
|
DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_iVolume, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flOcclusionScale, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_iType, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_iNextAudible, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_bNoExpirationTime, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flExpireTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_iNext, FIELD_SHORT ), |
|
DEFINE_FIELD( m_ownerChannelIndex, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_vecOrigin, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_bHasOwner, FIELD_BOOLEAN ), |
|
// DEFINE_FIELD( m_iMyIndex, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
//========================================================= |
|
// CSound - Clear - zeros all fields for a sound |
|
//========================================================= |
|
void CSound::Clear ( void ) |
|
{ |
|
m_vecOrigin = vec3_origin; |
|
m_iType = 0; |
|
m_iVolume = 0; |
|
m_flOcclusionScale = 0; |
|
m_flExpireTime = 0; |
|
m_bNoExpirationTime = false; |
|
m_iNext = SOUNDLIST_EMPTY; |
|
m_iNextAudible = 0; |
|
} |
|
|
|
//========================================================= |
|
// Reset - clears the volume, origin, and type for a sound, |
|
// but doesn't expire or unlink it. |
|
//========================================================= |
|
void CSound::Reset ( void ) |
|
{ |
|
m_vecOrigin = vec3_origin; |
|
m_iType = 0; |
|
m_iVolume = 0; |
|
m_iNext = SOUNDLIST_EMPTY; |
|
} |
|
|
|
//========================================================= |
|
// FIsSound - returns true if the sound is an Audible sound |
|
//========================================================= |
|
bool CSound::FIsSound ( void ) |
|
{ |
|
switch( SoundTypeNoContext() ) |
|
{ |
|
case SOUND_COMBAT: |
|
case SOUND_WORLD: |
|
case SOUND_PLAYER: |
|
case SOUND_DANGER: |
|
case SOUND_DANGER_SNIPERONLY: |
|
case SOUND_THUMPER: |
|
case SOUND_BULLET_IMPACT: |
|
case SOUND_BUGBAIT: |
|
case SOUND_PHYSICS_DANGER: |
|
case SOUND_MOVE_AWAY: |
|
case SOUND_PLAYER_VEHICLE: |
|
return true; |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// FIsScent - returns true if the sound is actually a scent |
|
// do we really need this function? If a sound isn't a sound, |
|
// it must be a scent. (sjb) |
|
//========================================================= |
|
bool CSound::FIsScent ( void ) |
|
{ |
|
switch( m_iType ) |
|
{ |
|
case SOUND_CARCASS: |
|
case SOUND_MEAT: |
|
case SOUND_GARBAGE: |
|
return true; |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
// This function returns the spot the listener should be |
|
// interested in if he hears the sound. MOST of the time, |
|
// this spot is the same as the sound's origin. But sometimes |
|
// (like with bullet impacts) the entity that owns the |
|
// sound is more interesting than the actual location of the |
|
// sound effect. |
|
//--------------------------------------------------------- |
|
const Vector &CSound::GetSoundReactOrigin( void ) |
|
{ |
|
|
|
// Check pure types. |
|
switch( m_iType ) |
|
{ |
|
case SOUND_BULLET_IMPACT: |
|
case SOUND_PHYSICS_DANGER: |
|
if( m_hOwner.Get() != NULL ) |
|
{ |
|
// We really want the origin of this sound's |
|
// owner. |
|
return m_hOwner->GetAbsOrigin(); |
|
} |
|
else |
|
{ |
|
// If the owner is somehow invalid, we'll settle |
|
// for the sound's origin rather than a crash. |
|
return GetSoundOrigin(); |
|
} |
|
break; |
|
} |
|
|
|
if( m_iType & SOUND_CONTEXT_REACT_TO_SOURCE ) |
|
{ |
|
if( m_hOwner.Get() != NULL ) |
|
{ |
|
return m_hOwner->GetAbsOrigin(); |
|
} |
|
} |
|
|
|
// Check for types with additional context. |
|
if( m_iType & SOUND_DANGER ) |
|
{ |
|
if( (m_iType & SOUND_CONTEXT_FROM_SNIPER) ) |
|
{ |
|
if( m_hOwner.Get() != NULL ) |
|
{ |
|
// Be afraid of the sniper's location, not where the bullet will hit. |
|
return m_hOwner->GetAbsOrigin(); |
|
} |
|
else |
|
{ |
|
return GetSoundOrigin(); |
|
} |
|
} |
|
} |
|
|
|
|
|
return GetSoundOrigin(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Save/load |
|
//----------------------------------------------------------------------------- |
|
BEGIN_DATADESC( CSoundEnt ) |
|
|
|
DEFINE_FIELD( m_iFreeSound, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iActiveSound, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_cLastActiveSounds, FIELD_INTEGER ), |
|
DEFINE_EMBEDDED_ARRAY( m_SoundPool, MAX_WORLD_SOUNDS_SP ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Class factory methods |
|
//----------------------------------------------------------------------------- |
|
bool CSoundEnt::InitSoundEnt() |
|
{ |
|
///!!!LATER - do we want a sound ent in deathmatch? (sjb) |
|
g_pSoundEnt = (CSoundEnt*)CBaseEntity::Create( "soundent", vec3_origin, vec3_angle, GetWorldEntity() ); |
|
if ( !g_pSoundEnt ) |
|
{ |
|
Warning( "**COULD NOT CREATE SOUNDENT**\n" ); |
|
return false; |
|
} |
|
g_pSoundEnt->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES ); |
|
return true; |
|
} |
|
|
|
void CSoundEnt::ShutdownSoundEnt() |
|
{ |
|
if ( g_pSoundEnt ) |
|
{ |
|
g_pSoundEnt->FreeList(); |
|
g_pSoundEnt = NULL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Construction, destruction |
|
//----------------------------------------------------------------------------- |
|
CSoundEnt::CSoundEnt() |
|
{ |
|
} |
|
|
|
CSoundEnt::~CSoundEnt() |
|
{ |
|
} |
|
|
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CSoundEnt::Spawn( void ) |
|
{ |
|
SetSolid( SOLID_NONE ); |
|
Initialize(); |
|
|
|
SetNextThink( gpGlobals->curtime + 1 ); |
|
} |
|
|
|
void CSoundEnt::OnRestore() |
|
{ |
|
BaseClass::OnRestore(); |
|
|
|
// Make sure the singleton points to the restored version of this. |
|
if ( g_pSoundEnt ) |
|
{ |
|
Assert( g_pSoundEnt != this ); |
|
UTIL_Remove( g_pSoundEnt ); |
|
} |
|
g_pSoundEnt = this; |
|
} |
|
|
|
|
|
//========================================================= |
|
// Think - at interval, the entire active sound list is checked |
|
// for sounds that have ExpireTimes less than or equal |
|
// to the current world time, and these sounds are deallocated. |
|
//========================================================= |
|
void CSoundEnt::Think ( void ) |
|
{ |
|
int iSound; |
|
int iPreviousSound; |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1 );// how often to check the sound list. |
|
|
|
iPreviousSound = SOUNDLIST_EMPTY; |
|
iSound = m_iActiveSound; |
|
|
|
while ( iSound != SOUNDLIST_EMPTY ) |
|
{ |
|
if ( (m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->curtime && (!m_SoundPool[ iSound ].m_bNoExpirationTime)) || !m_SoundPool[iSound].ValidateOwner() ) |
|
{ |
|
int iNext = m_SoundPool[ iSound ].m_iNext; |
|
|
|
if( displaysoundlist.GetInt() == 1 ) |
|
{ |
|
Msg(" Removed Sound: %d (Time:%f)\n", m_SoundPool[ iSound ].SoundType(), gpGlobals->curtime ); |
|
} |
|
if( displaysoundlist.GetInt() == 2 && m_SoundPool[ iSound ].IsSoundType( SOUND_DANGER ) ) |
|
{ |
|
Msg(" Removed Danger Sound: %d (time:%f)\n", m_SoundPool[ iSound ].SoundType(), gpGlobals->curtime ); |
|
} |
|
|
|
// move this sound back into the free list |
|
FreeSound( iSound, iPreviousSound ); |
|
|
|
iSound = iNext; |
|
} |
|
else |
|
{ |
|
if( displaysoundlist.GetBool() ) |
|
{ |
|
Vector forward, right, up; |
|
GetVectors( &forward, &right, &up ); |
|
byte r, g, b; |
|
|
|
// Default to yellow. |
|
r = 255; |
|
g = 255; |
|
b = 0; |
|
|
|
CSound *pSound = &m_SoundPool[ iSound ]; |
|
|
|
if( pSound->IsSoundType( SOUND_DANGER ) ) |
|
{ |
|
r = 255; |
|
g = 0; |
|
b = 0; |
|
} |
|
|
|
if( displaysoundlist.GetInt() == 1 || (displaysoundlist.GetInt() == 2 && pSound->IsSoundType( SOUND_DANGER ) ) ) |
|
{ |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() + forward * pSound->Volume(), r,g,b, false, 0.1 ); |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() - forward * pSound->Volume(), r,g,b, false, 0.1 ); |
|
|
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() + right * pSound->Volume(), r,g,b, false, 0.1 ); |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() - right * pSound->Volume(), r,g,b, false, 0.1 ); |
|
|
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() + up * pSound->Volume(), r,g,b, false, 0.1 ); |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() - up * pSound->Volume(), r,g,b, false, 0.1 ); |
|
|
|
if( pSound->m_flOcclusionScale != 1.0 ) |
|
{ |
|
// Draw the occluded radius, too. |
|
r = 0; g = 150; b = 255; |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() + forward * pSound->OccludedVolume(), r,g,b, false, 0.1 ); |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() - forward * pSound->OccludedVolume(), r,g,b, false, 0.1 ); |
|
|
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() + right * pSound->OccludedVolume(), r,g,b, false, 0.1 ); |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() - right * pSound->OccludedVolume(), r,g,b, false, 0.1 ); |
|
|
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() + up * pSound->OccludedVolume(), r,g,b, false, 0.1 ); |
|
NDebugOverlay::Line( pSound->GetSoundOrigin(), pSound->GetSoundOrigin() - up * pSound->OccludedVolume(), r,g,b, false, 0.1 ); |
|
} |
|
} |
|
|
|
DevMsg( 2, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); |
|
m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); |
|
} |
|
|
|
iPreviousSound = iSound; |
|
iSound = m_SoundPool[ iSound ].m_iNext; |
|
} |
|
} |
|
|
|
} |
|
|
|
//========================================================= |
|
// Precache - dummy function |
|
//========================================================= |
|
void CSoundEnt::Precache ( void ) |
|
{ |
|
} |
|
|
|
//========================================================= |
|
// FreeSound - clears the passed active sound and moves it |
|
// to the top of the free list. TAKE CARE to only call this |
|
// function for sounds in the Active list!! |
|
//========================================================= |
|
void CSoundEnt::FreeSound ( int iSound, int iPrevious ) |
|
{ |
|
if ( !g_pSoundEnt ) |
|
{ |
|
// no sound ent! |
|
return; |
|
} |
|
|
|
if ( iPrevious != SOUNDLIST_EMPTY ) |
|
{ |
|
// iSound is not the head of the active list, so |
|
// must fix the index for the Previous sound |
|
g_pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = g_pSoundEnt->m_SoundPool[ iSound ].m_iNext; |
|
} |
|
else |
|
{ |
|
// the sound we're freeing IS the head of the active list. |
|
g_pSoundEnt->m_iActiveSound = g_pSoundEnt->m_SoundPool [ iSound ].m_iNext; |
|
} |
|
|
|
// make iSound the head of the Free list. |
|
g_pSoundEnt->m_SoundPool[ iSound ].m_iNext = g_pSoundEnt->m_iFreeSound; |
|
g_pSoundEnt->m_iFreeSound = iSound; |
|
} |
|
|
|
//========================================================= |
|
// IAllocSound - moves a sound from the Free list to the |
|
// Active list returns the index of the alloc'd sound |
|
//========================================================= |
|
int CSoundEnt::IAllocSound( void ) |
|
{ |
|
int iNewSound; |
|
|
|
if ( m_iFreeSound == SOUNDLIST_EMPTY ) |
|
{ |
|
// no free sound! |
|
if ( developer.GetInt() >= 2 ) |
|
Msg( "Free Sound List is full!\n" ); |
|
|
|
return SOUNDLIST_EMPTY; |
|
} |
|
|
|
// there is at least one sound available, so move it to the |
|
// Active sound list, and return its SoundPool index. |
|
|
|
iNewSound = m_iFreeSound;// copy the index of the next free sound |
|
|
|
m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. |
|
|
|
m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. |
|
|
|
m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. |
|
|
|
#ifdef DEBUG |
|
m_SoundPool[ iNewSound ].m_iMyIndex = iNewSound; |
|
#endif // DEBUG |
|
|
|
return iNewSound; |
|
} |
|
|
|
//========================================================= |
|
// InsertSound - Allocates a free sound and fills it with |
|
// sound info. |
|
//========================================================= |
|
void CSoundEnt::InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration, CBaseEntity *pOwner, int soundChannelIndex, CBaseEntity *pSoundTarget ) |
|
{ |
|
int iThisSound; |
|
|
|
if ( !g_pSoundEnt ) |
|
return; |
|
|
|
if( soundChannelIndex == SOUNDENT_CHANNEL_UNSPECIFIED ) |
|
{ |
|
// No sound channel specified. So just make a new sound. |
|
iThisSound = g_pSoundEnt->IAllocSound(); |
|
} |
|
else |
|
{ |
|
// If this entity has already got a sound in the soundlist that's on this |
|
// channel, update that sound. Otherwise add a new one. |
|
iThisSound = g_pSoundEnt->FindOrAllocateSound( pOwner, soundChannelIndex ); |
|
} |
|
|
|
if ( iThisSound == SOUNDLIST_EMPTY ) |
|
{ |
|
DevMsg( "Could not AllocSound() for InsertSound() (Game DLL)\n" ); |
|
return; |
|
} |
|
|
|
CSound *pSound; |
|
|
|
pSound = &g_pSoundEnt->m_SoundPool[ iThisSound ]; |
|
|
|
pSound->SetSoundOrigin( vecOrigin ); |
|
pSound->m_iType = iType; |
|
pSound->m_iVolume = iVolume; |
|
pSound->m_flOcclusionScale = 0.5; |
|
pSound->m_flExpireTime = gpGlobals->curtime + flDuration; |
|
pSound->m_bNoExpirationTime = false; |
|
pSound->m_hOwner.Set( pOwner ); |
|
pSound->m_hTarget.Set( pSoundTarget ); |
|
pSound->m_ownerChannelIndex = soundChannelIndex; |
|
|
|
// Keep track of whether this sound had an owner when it was made. If the sound has a long duration, |
|
// the owner could disappear by the time someone hears this sound, so we have to look at this boolean |
|
// and throw out sounds who have a NULL owner but this field set to true. (sjb) 12/2/2005 |
|
if( pOwner ) |
|
{ |
|
pSound->m_bHasOwner = true; |
|
} |
|
else |
|
{ |
|
pSound->m_bHasOwner = false; |
|
} |
|
|
|
if( displaysoundlist.GetInt() == 1 ) |
|
{ |
|
Msg(" Added Sound! Type:%d Duration:%f (Time:%f)\n", pSound->SoundType(), flDuration, gpGlobals->curtime ); |
|
} |
|
if( displaysoundlist.GetInt() == 2 && (iType & SOUND_DANGER) ) |
|
{ |
|
Msg(" Added Danger Sound! Duration:%f (Time:%f)\n", flDuration, gpGlobals->curtime ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
int CSoundEnt::FindOrAllocateSound( CBaseEntity *pOwner, int soundChannelIndex ) |
|
{ |
|
int iSound = m_iActiveSound; |
|
|
|
while ( iSound != SOUNDLIST_EMPTY ) |
|
{ |
|
CSound &sound = m_SoundPool[iSound]; |
|
|
|
if ( sound.m_ownerChannelIndex == soundChannelIndex && sound.m_hOwner == pOwner ) |
|
{ |
|
return iSound; |
|
} |
|
|
|
iSound = sound.m_iNext; |
|
} |
|
|
|
return IAllocSound(); |
|
} |
|
|
|
//========================================================= |
|
// Initialize - clears all sounds and moves them into the |
|
// free sound list. |
|
//========================================================= |
|
void CSoundEnt::Initialize ( void ) |
|
{ |
|
int i; |
|
int iSound; |
|
|
|
m_cLastActiveSounds; |
|
m_iFreeSound = 0; |
|
m_iActiveSound = SOUNDLIST_EMPTY; |
|
|
|
// In SP, we should only use the first 64 slots so save/load works right. |
|
// In MP, have one for each player and 32 extras. |
|
int nTotalSoundsInPool = MAX_WORLD_SOUNDS_SP; |
|
if ( gpGlobals->maxClients > 1 ) |
|
nTotalSoundsInPool = MIN( MAX_WORLD_SOUNDS_MP, gpGlobals->maxClients + 32 ); |
|
|
|
if ( gpGlobals->maxClients+16 > nTotalSoundsInPool ) |
|
{ |
|
Warning( "CSoundEnt pool is low on sounds due to high number of clients.\n" ); |
|
} |
|
|
|
for ( i = 0 ; i < nTotalSoundsInPool ; i++ ) |
|
{ |
|
// clear all sounds, and link them into the free sound list. |
|
m_SoundPool[ i ].Clear(); |
|
m_SoundPool[ i ].m_iNext = i + 1; |
|
} |
|
|
|
m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. |
|
|
|
|
|
// now reserve enough sounds for each client |
|
for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) |
|
{ |
|
iSound = IAllocSound(); |
|
|
|
if ( iSound == SOUNDLIST_EMPTY ) |
|
{ |
|
DevMsg( "Could not AllocSound() for Client Reserve! (DLL)\n" ); |
|
return; |
|
} |
|
|
|
m_SoundPool[ iSound ].m_bNoExpirationTime = true; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// ISoundsInList - returns the number of sounds in the desired |
|
// sound list. |
|
//========================================================= |
|
int CSoundEnt::ISoundsInList ( int iListType ) |
|
{ |
|
int i; |
|
int iThisSound = SOUNDLIST_EMPTY; |
|
|
|
if ( iListType == SOUNDLISTTYPE_FREE ) |
|
{ |
|
iThisSound = m_iFreeSound; |
|
} |
|
else if ( iListType == SOUNDLISTTYPE_ACTIVE ) |
|
{ |
|
iThisSound = m_iActiveSound; |
|
} |
|
else |
|
{ |
|
Msg( "Unknown Sound List Type!\n" ); |
|
} |
|
|
|
if ( iThisSound == SOUNDLIST_EMPTY ) |
|
{ |
|
return 0; |
|
} |
|
|
|
i = 0; |
|
|
|
while ( iThisSound != SOUNDLIST_EMPTY ) |
|
{ |
|
i++; |
|
|
|
iThisSound = m_SoundPool[ iThisSound ].m_iNext; |
|
} |
|
|
|
return i; |
|
} |
|
|
|
//========================================================= |
|
// ActiveList - returns the head of the active sound list |
|
//========================================================= |
|
int CSoundEnt::ActiveList ( void ) |
|
{ |
|
if ( !g_pSoundEnt ) |
|
{ |
|
return SOUNDLIST_EMPTY; |
|
} |
|
|
|
return g_pSoundEnt->m_iActiveSound; |
|
} |
|
|
|
//========================================================= |
|
// FreeList - returns the head of the free sound list |
|
//========================================================= |
|
int CSoundEnt::FreeList ( void ) |
|
{ |
|
if ( !g_pSoundEnt ) |
|
{ |
|
return SOUNDLIST_EMPTY; |
|
} |
|
|
|
return g_pSoundEnt->m_iFreeSound; |
|
} |
|
|
|
//========================================================= |
|
// SoundPointerForIndex - returns a pointer to the instance |
|
// of CSound at index's position in the sound pool. |
|
//========================================================= |
|
CSound* CSoundEnt::SoundPointerForIndex( int iIndex ) |
|
{ |
|
if ( !g_pSoundEnt ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
if ( iIndex > ( MAX_WORLD_SOUNDS_MP - 1 ) ) |
|
{ |
|
Msg( "SoundPointerForIndex() - Index too large!\n" ); |
|
return NULL; |
|
} |
|
|
|
if ( iIndex < 0 ) |
|
{ |
|
Msg( "SoundPointerForIndex() - Index < 0!\n" ); |
|
return NULL; |
|
} |
|
|
|
return &g_pSoundEnt->m_SoundPool[ iIndex ]; |
|
} |
|
|
|
//========================================================= |
|
// Clients are numbered from 1 to MAXCLIENTS, but the client |
|
// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, |
|
// so this function ensures that a client gets the proper index |
|
// to his reserved sound in the soundlist. |
|
//========================================================= |
|
int CSoundEnt::ClientSoundIndex ( edict_t *pClient ) |
|
{ |
|
int iReturn = ENTINDEX( pClient ) - 1; |
|
|
|
#ifdef _DEBUG |
|
if ( iReturn < 0 || iReturn >= gpGlobals->maxClients ) |
|
{ |
|
Msg( "** ClientSoundIndex returning a bogus value! **\n" ); |
|
} |
|
#endif // _DEBUG |
|
|
|
return iReturn; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the loudest sound of the specified type at "earposition" |
|
//----------------------------------------------------------------------------- |
|
CSound* CSoundEnt::GetLoudestSoundOfType( int iType, const Vector &vecEarPosition ) |
|
{ |
|
CSound *pLoudestSound = NULL; |
|
|
|
int iThisSound; |
|
int iBestSound = SOUNDLIST_EMPTY; |
|
float flBestDist = MAX_COORD_RANGE*MAX_COORD_RANGE;// so first nearby sound will become best so far. |
|
float flDist; |
|
CSound *pSound; |
|
|
|
iThisSound = ActiveList(); |
|
|
|
while ( iThisSound != SOUNDLIST_EMPTY ) |
|
{ |
|
pSound = SoundPointerForIndex( iThisSound ); |
|
|
|
if ( pSound && pSound->m_iType == iType && pSound->ValidateOwner() ) |
|
{ |
|
flDist = ( pSound->GetSoundOrigin() - vecEarPosition ).Length(); |
|
|
|
//FIXME: This doesn't match what's in Listen() |
|
//flDist = UTIL_DistApprox( pSound->GetSoundOrigin(), vecEarPosition ); |
|
|
|
if ( flDist <= pSound->m_iVolume && flDist < flBestDist ) |
|
{ |
|
pLoudestSound = pSound; |
|
|
|
iBestSound = iThisSound; |
|
flBestDist = flDist; |
|
} |
|
} |
|
|
|
iThisSound = pSound->m_iNext; |
|
} |
|
|
|
return pLoudestSound; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Inserts an AI sound into the world sound list. |
|
//----------------------------------------------------------------------------- |
|
class CAISound : public CPointEntity |
|
{ |
|
public: |
|
CAISound() |
|
{ |
|
// Initialize these new keyvalues appropriately |
|
// in order to support legacy instances of ai_sound. |
|
m_iSoundContext = 0x00000000; |
|
m_iVolume = 0; |
|
m_flDuration = 0.3; |
|
} |
|
|
|
DECLARE_CLASS( CAISound, CPointEntity ); |
|
|
|
DECLARE_DATADESC(); |
|
|
|
// data |
|
int m_iSoundType; |
|
int m_iSoundContext; |
|
int m_iVolume; |
|
float m_flDuration; |
|
string_t m_iszProxyEntityName; |
|
|
|
// Input handlers |
|
void InputInsertSound( inputdata_t &inputdata ); |
|
void InputEmitAISound( inputdata_t &inputdata ); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( ai_sound, CAISound ); |
|
|
|
BEGIN_DATADESC( CAISound ) |
|
|
|
DEFINE_KEYFIELD( m_iSoundType, FIELD_INTEGER, "soundtype" ), |
|
DEFINE_KEYFIELD( m_iSoundContext, FIELD_INTEGER, "soundcontext" ), |
|
DEFINE_KEYFIELD( m_iVolume, FIELD_INTEGER, "volume" ), |
|
DEFINE_KEYFIELD( m_flDuration, FIELD_FLOAT, "duration" ), |
|
DEFINE_KEYFIELD( m_iszProxyEntityName, FIELD_STRING, "locationproxy" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "InsertSound", InputInsertSound ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "EmitAISound", InputEmitAISound ), |
|
|
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: *** OBSOLETE **** Here for legacy support only! |
|
//----------------------------------------------------------------------------- |
|
void CAISound::InputInsertSound( inputdata_t &inputdata ) |
|
{ |
|
int iVolume; |
|
|
|
iVolume = inputdata.value.Int(); |
|
|
|
Vector vecLocation = GetAbsOrigin(); |
|
|
|
if( m_iszProxyEntityName != NULL_STRING ) |
|
{ |
|
CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_iszProxyEntityName ); |
|
|
|
if( pProxy ) |
|
{ |
|
vecLocation = pProxy->GetAbsOrigin(); |
|
} |
|
else |
|
{ |
|
DevWarning("Warning- ai_sound cannot find proxy entity named '%s'. Using self.\n", STRING(m_iszProxyEntityName) ); |
|
} |
|
} |
|
|
|
g_pSoundEnt->InsertSound( m_iSoundType, vecLocation, iVolume, m_flDuration, this ); |
|
} |
|
|
|
void CAISound::InputEmitAISound( inputdata_t &inputdata ) |
|
{ |
|
Vector vecLocation = GetAbsOrigin(); |
|
|
|
if( m_iszProxyEntityName != NULL_STRING ) |
|
{ |
|
CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_iszProxyEntityName ); |
|
|
|
if( pProxy ) |
|
{ |
|
vecLocation = pProxy->GetAbsOrigin(); |
|
} |
|
else |
|
{ |
|
DevWarning("Warning- ai_sound cannot find proxy entity named '%s'. Using self.\n", STRING(m_iszProxyEntityName) ); |
|
} |
|
} |
|
|
|
g_pSoundEnt->InsertSound( m_iSoundType | m_iSoundContext, vecLocation, m_iVolume, m_flDuration, this ); |
|
} |
|
|
|
|
|
|