diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/envmicrophone.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/envmicrophone.cpp')
| -rw-r--r-- | game/server/envmicrophone.cpp | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/game/server/envmicrophone.cpp b/game/server/envmicrophone.cpp new file mode 100644 index 0000000..9e85b0d --- /dev/null +++ b/game/server/envmicrophone.cpp @@ -0,0 +1,560 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements an entity that measures sound volume at a point in a map. +// +// This entity listens as though it is an NPC, meaning it will only +// hear sounds that were emitted using the CSound::InsertSound function. +// +// It does not hear danger sounds since they are not technically sounds. +// +//============================================================================= + +#include "cbase.h" +#include "entityinput.h" +#include "entityoutput.h" +#include "eventqueue.h" +#include "mathlib/mathlib.h" +#include "soundent.h" +#include "envmicrophone.h" +#include "soundflags.h" +#include "engine/IEngineSound.h" +#include "filters.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//#define DEBUG_MICROPHONE + +const float MICROPHONE_SETTLE_EPSILON = 0.005; + +// List of env_microphones who want to be told whenever a sound is started +static CUtlVector< CHandle<CEnvMicrophone> > s_Microphones; + + +LINK_ENTITY_TO_CLASS(env_microphone, CEnvMicrophone); + +BEGIN_DATADESC( CEnvMicrophone ) + + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), + DEFINE_FIELD(m_hMeasureTarget, FIELD_EHANDLE), + DEFINE_KEYFIELD(m_nSoundMask, FIELD_INTEGER, "SoundMask"), + DEFINE_KEYFIELD(m_flSensitivity, FIELD_FLOAT, "Sensitivity"), + DEFINE_KEYFIELD(m_flSmoothFactor, FIELD_FLOAT, "SmoothFactor"), + DEFINE_KEYFIELD(m_iszSpeakerName, FIELD_STRING, "SpeakerName"), + DEFINE_KEYFIELD(m_iszListenFilter, FIELD_STRING, "ListenFilter"), + DEFINE_FIELD(m_hListenFilter, FIELD_EHANDLE), + DEFINE_FIELD(m_hSpeaker, FIELD_EHANDLE), + // DEFINE_FIELD(m_bAvoidFeedback, FIELD_BOOLEAN), // DONT SAVE + DEFINE_KEYFIELD(m_iSpeakerDSPPreset, FIELD_INTEGER, "speaker_dsp_preset" ), + DEFINE_KEYFIELD(m_flMaxRange, FIELD_FLOAT, "MaxRange"), + DEFINE_AUTO_ARRAY(m_szLastSound, FIELD_CHARACTER), + + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + DEFINE_INPUTFUNC(FIELD_STRING, "SetSpeakerName", InputSetSpeakerName), + + DEFINE_OUTPUT(m_SoundLevel, "SoundLevel"), + DEFINE_OUTPUT(m_OnRoutedSound, "OnRoutedSound" ), + DEFINE_OUTPUT(m_OnHeardSound, "OnHeardSound" ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEnvMicrophone::~CEnvMicrophone( void ) +{ + s_Microphones.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called before spawning, after keyvalues have been handled. +//----------------------------------------------------------------------------- +void CEnvMicrophone::Spawn(void) +{ + // + // Build our sound type mask from our spawnflags. + // + static int nFlags[][2] = + { + { SF_MICROPHONE_SOUND_COMBAT, SOUND_COMBAT }, + { SF_MICROPHONE_SOUND_WORLD, SOUND_WORLD }, + { SF_MICROPHONE_SOUND_PLAYER, SOUND_PLAYER }, + { SF_MICROPHONE_SOUND_BULLET_IMPACT, SOUND_BULLET_IMPACT }, + { SF_MICROPHONE_SOUND_EXPLOSION, SOUND_CONTEXT_EXPLOSION }, + }; + + for (int i = 0; i < sizeof(nFlags) / sizeof(nFlags[0]); i++) + { + if (m_spawnflags & nFlags[i][0]) + { + m_nSoundMask |= nFlags[i][1]; + } + } + + if (m_flSensitivity == 0) + { + // + // Avoid a divide by zero in CanHearSound. + // + m_flSensitivity = 1; + } + else if (m_flSensitivity > 10) + { + m_flSensitivity = 10; + } + + m_flSmoothFactor = clamp(m_flSmoothFactor, 0.f, 0.9f); + + if (!m_bDisabled) + { + SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called after all entities have spawned and after a load game. +// Finds the reference point at which to measure sound level. +//----------------------------------------------------------------------------- +void CEnvMicrophone::Activate(void) +{ + BaseClass::Activate(); + + // Get a handle to my filter entity if there is one + if (m_iszListenFilter != NULL_STRING) + { + m_hListenFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iszListenFilter )); + } + + if (m_target != NULL_STRING) + { + m_hMeasureTarget = gEntList.FindEntityByName(NULL, STRING(m_target) ); + + // + // If we were given a bad measure target, just measure sound where we are. + // + if ((m_hMeasureTarget == NULL) || (m_hMeasureTarget->edict() == NULL)) + { + // We've decided to disable this warning since this seems to be the 90% case. + //Warning( "EnvMicrophone - Measure target not found or measure target with no origin. Using Self.!\n"); + m_hMeasureTarget = this; + } + } + else + { + m_hMeasureTarget = this; + } + + ActivateSpeaker(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnvMicrophone::OnRestore( void ) +{ + BaseClass::OnRestore(); + + ActivateSpeaker(); +} + +//----------------------------------------------------------------------------- +// Purpose: If we've got a speaker, add ourselves to the list of microphones that want to listen +//----------------------------------------------------------------------------- +void CEnvMicrophone::ActivateSpeaker( void ) +{ + // If we're enabled, set the dsp_speaker preset to my specified one + if ( !m_bDisabled ) + { + ConVarRef dsp_speaker( "dsp_speaker" ); + if ( dsp_speaker.IsValid() ) + { + int iDSPPreset = m_iSpeakerDSPPreset; + if ( !iDSPPreset ) + { + // Reset it to the default + iDSPPreset = atoi( dsp_speaker.GetDefault() ); + } + DevMsg( 2, "Microphone %s set dsp_speaker to %d.\n", STRING(GetEntityName()), iDSPPreset); + dsp_speaker.SetValue( m_iSpeakerDSPPreset ); + } + } + + if ( m_iszSpeakerName != NULL_STRING ) + { + // We've got a speaker to play heard sounds through. To do this, we need to add ourselves + // to the list of microphones who want to be told whenever a sound is played. + if ( s_Microphones.Find(this) == -1 ) + { + s_Microphones.AddToTail( this ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stops the microphone from sampling the sound level and firing the +// SoundLevel output. +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputEnable( inputdata_t &inputdata ) +{ + if (m_bDisabled) + { + m_bDisabled = false; + SetNextThink( gpGlobals->curtime + 0.1f ); + + ActivateSpeaker(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Resumes sampling the sound level and firing the SoundLevel output. +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; + if ( m_hSpeaker ) + { + CBaseEntity::StopSound( m_hSpeaker->entindex(), CHAN_STATIC, m_szLastSound ); + m_szLastSound[0] = 0; + + // Remove ourselves from the list of active mics + s_Microphones.FindAndRemove( this ); + } + SetNextThink( TICK_NEVER_THINK ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputSetSpeakerName( inputdata_t &inputdata ) +{ + SetSpeakerName( inputdata.value.StringID() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Checks whether this microphone can hear a given sound, and at what +// relative volume level. +// Input : pSound - Sound to test. +// flVolume - Returns with the relative sound volume from 0 - 1. +// Output : Returns true if the sound could be heard at the sample point, false if not. +//----------------------------------------------------------------------------- +bool CEnvMicrophone::CanHearSound(CSound *pSound, float &flVolume) +{ + flVolume = 0; + + if ( m_bDisabled ) + { + return false; + } + + // Cull out sounds except from specific entities + CBaseFilter *pFilter = m_hListenFilter.Get(); + if ( pFilter ) + { + CBaseEntity *pSoundOwner = pSound->m_hOwner.Get(); + if ( !pSoundOwner || !pFilter->PassesFilter( this, pSoundOwner ) ) + { + return false; + } + } + + float flDistance = (pSound->GetSoundOrigin() - m_hMeasureTarget->GetAbsOrigin()).Length(); + + if (flDistance == 0) + { + flVolume = 1.0; + return true; + } + + // Over our max range? + if ( m_flMaxRange && flDistance > m_flMaxRange ) + { + return false; + } + + if (flDistance <= pSound->Volume() * m_flSensitivity) + { + flVolume = 1 - (flDistance / (pSound->Volume() * m_flSensitivity)); + flVolume = clamp(flVolume, 0.f, 1.f); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the microphone can hear the specified sound +//----------------------------------------------------------------------------- +bool CEnvMicrophone::CanHearSound( int entindex, soundlevel_t soundlevel, float &flVolume, const Vector *pOrigin ) +{ + if ( m_bDisabled ) + { + flVolume = 0; + return false; + } + + if ( ( m_spawnflags & SF_MICROPHONE_IGNORE_NONATTENUATED ) && soundlevel == SNDLVL_NONE ) + { + return false; + } + + // Sound might be coming from an origin or from an entity. + CBaseEntity *pEntity = NULL; + if ( entindex ) + { + pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex(entindex) ); + } + + // Cull out sounds except from specific entities + CBaseFilter *pFilter = m_hListenFilter.Get(); + if ( pFilter ) + { + if ( !pEntity || !pFilter->PassesFilter( this, pEntity ) ) + { + flVolume = 0; + return false; + } + } + + float flDistance = 0; + if ( pOrigin ) + { + flDistance = pOrigin->DistTo( m_hMeasureTarget->GetAbsOrigin() ); + } + else if ( pEntity ) + { + flDistance = pEntity->WorldSpaceCenter().DistTo( m_hMeasureTarget->GetAbsOrigin() ); + } + + // Over our max range? + if ( m_flMaxRange && flDistance > m_flMaxRange ) + { +#ifdef DEBUG_MICROPHONE + Msg("OUT OF RANGE.\n" ); +#endif + return false; + } + +#ifdef DEBUG_MICROPHONE + Msg(" flVolume %f ", flVolume ); +#endif + + // Reduce the volume by the amount it fell to get to the microphone + float gain = enginesound->GetDistGainFromSoundLevel( soundlevel, flDistance ); + flVolume *= gain; + +#ifdef DEBUG_MICROPHONE + Msg("dist %2f, soundlevel %d: gain %f", flDistance, (int)soundlevel, gain ); + if ( !flVolume ) + { + Msg(" : REJECTED\n" ); + } + else + { + Msg(" : SENT\n" ); + } +#endif + + return ( flVolume > 0 ); +} + +void CEnvMicrophone::SetSensitivity( float flSensitivity ) +{ + m_flSensitivity = flSensitivity; +} + +void CEnvMicrophone::SetSpeakerName( string_t iszSpeakerName ) +{ + m_iszSpeakerName = iszSpeakerName; + + // Set the speaker to null. This will force it to find the speaker next time a sound is routed. + m_hSpeaker = NULL; + ActivateSpeaker(); +} + +//----------------------------------------------------------------------------- +// Purpose: Listens for sounds and updates the value of the SoundLevel output. +//----------------------------------------------------------------------------- +void CEnvMicrophone::Think(void) +{ + int nSound = CSoundEnt::ActiveList(); + bool fHearSound = false; + + float flMaxVolume = 0; + + // + // Find the loudest sound that this microphone cares about. + // + while (nSound != SOUNDLIST_EMPTY) + { + CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex(nSound); + + if (pCurrentSound) + { + if (m_nSoundMask & pCurrentSound->SoundType()) + { + float flVolume = 0; + if (CanHearSound(pCurrentSound, flVolume) && (flVolume > flMaxVolume)) + { + flMaxVolume = flVolume; + fHearSound = true; + } + } + } + + nSound = pCurrentSound->NextSound(); + } + + if( fHearSound ) + { + m_OnHeardSound.FireOutput( this, this ); + } + + if (flMaxVolume != m_SoundLevel.Get()) + { + // + // Don't smooth if we are within an epsilon. This allows the output to stop firing + // much more quickly. + // + if (fabs(flMaxVolume - m_SoundLevel.Get()) < MICROPHONE_SETTLE_EPSILON) + { + m_SoundLevel.Set(flMaxVolume, this, this); + } + else + { + m_SoundLevel.Set(flMaxVolume * (1 - m_flSmoothFactor) + m_SoundLevel.Get() * m_flSmoothFactor, this, this); + } + } + + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Hook for the sound system to tell us when a sound's been played +//----------------------------------------------------------------------------- +MicrophoneResult_t CEnvMicrophone::SoundPlayed( int entindex, const char *soundname, soundlevel_t soundlevel, float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins ) +{ + if ( m_bAvoidFeedback ) + return MicrophoneResult_Ok; + + // Don't hear sounds that have already been heard by a microphone to avoid feedback! + if ( iFlags & SND_SPEAKER ) + return MicrophoneResult_Ok; + +#ifdef DEBUG_MICROPHONE + Msg("%s heard %s: ", STRING(GetEntityName()), soundname ); +#endif + + if ( !CanHearSound( entindex, soundlevel, flVolume, pOrigin ) ) + return MicrophoneResult_Ok; + + // We've heard it. Play it out our speaker. If our speaker's gone away, we're done. + if ( !m_hSpeaker ) + { + // First time, find our speaker. Done here, because finding it in Activate() wouldn't + // find players, and we need to be able to specify !player for a speaker. + if ( m_iszSpeakerName != NULL_STRING ) + { + m_hSpeaker = gEntList.FindEntityByName(NULL, STRING(m_iszSpeakerName) ); + + if ( !m_hSpeaker ) + { + Warning( "EnvMicrophone %s specifies a non-existent speaker name: %s\n", STRING(GetEntityName()), STRING(m_iszSpeakerName) ); + m_iszSpeakerName = NULL_STRING; + } + } + + if ( !m_hSpeaker ) + { + return MicrophoneResult_Remove; + } + } + + m_bAvoidFeedback = true; + + // Add the speaker flag. Detected at playback and applies the speaker filter. + iFlags |= SND_SPEAKER; + CPASAttenuationFilter filter( m_hSpeaker ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + ep.m_pSoundName = soundname; + ep.m_flVolume = flVolume; + ep.m_SoundLevel = soundlevel; + ep.m_nFlags = iFlags; + ep.m_nPitch = iPitch; + ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin(); + ep.m_flSoundTime = soundtime; + ep.m_nSpeakerEntity = entindex; + + CBaseEntity::EmitSound( filter, m_hSpeaker->entindex(), ep ); + + Q_strncpy( m_szLastSound, soundname, sizeof(m_szLastSound) ); + m_OnRoutedSound.FireOutput( this, this, 0 ); + + m_bAvoidFeedback = false; + + // Copy emitted origin to soundorigins array + for ( int i = 0; i < ep.m_UtlVecSoundOrigin.Count(); ++i ) + { + soundorigins.AddToTail( ep.m_UtlVecSoundOrigin[ i ] ); + } + + // Do we want to allow the original sound to play? + if ( m_spawnflags & SF_MICROPHONE_SWALLOW_ROUTED_SOUNDS ) + { + return MicrophoneResult_Swallow; + } + + return MicrophoneResult_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: Called by the sound system whenever a sound is played so that +// active microphones can have a chance to pick up the sound. +// Output : Returns whether or not the sound was swallowed by the microphone. +// Swallowed sounds should not be played by the sound system. +//----------------------------------------------------------------------------- +bool CEnvMicrophone::OnSoundPlayed( int entindex, const char *soundname, soundlevel_t soundlevel, float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins ) +{ + bool bSwallowed = false; + + // Loop through all registered microphones and tell them the sound was just played + int iCount = s_Microphones.Count(); + if ( iCount > 0 ) + { + // Iterate backwards because we might be deleting microphones. + for ( int i = iCount - 1; i >= 0; i-- ) + { + if ( s_Microphones[i] ) + { + MicrophoneResult_t eResult = s_Microphones[i]->SoundPlayed( + entindex, + soundname, + soundlevel, + flVolume, + iFlags, + iPitch, + pOrigin, + soundtime, + soundorigins ); + + if ( eResult == MicrophoneResult_Swallow ) + { + // Microphone told us to swallow it + bSwallowed = true; + } + else if ( eResult == MicrophoneResult_Remove ) + { + s_Microphones.FastRemove( i ); + } + } + } + } + + return bSwallowed; +} |