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.
598 lines
16 KiB
598 lines
16 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
|
|
#include "cbase.h" |
|
#include "soundscape.h" |
|
#include "datamap.h" |
|
#include "soundscape_system.h" |
|
#include "triggers.h" |
|
#include "saverestore_utlvector.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar soundscape_debug( "soundscape_debug", "0", FCVAR_CHEAT, "When on, draws lines to all env_soundscape entities. Green lines show the active soundscape, red lines show soundscapes that aren't in range, and white lines show soundscapes that are in range, but not the active soundscape." ); |
|
|
|
// ----------------------------------------------------------------------------- // |
|
// CEnvSoundscapeProxy stuff. |
|
// ----------------------------------------------------------------------------- // |
|
|
|
LINK_ENTITY_TO_CLASS( env_soundscape_proxy, CEnvSoundscapeProxy ); |
|
|
|
BEGIN_DATADESC( CEnvSoundscapeProxy ) |
|
|
|
DEFINE_KEYFIELD( m_MainSoundscapeName, FIELD_STRING, "MainSoundscapeName" ) |
|
|
|
END_DATADESC() |
|
|
|
|
|
CEnvSoundscapeProxy::CEnvSoundscapeProxy() |
|
{ |
|
m_MainSoundscapeName = NULL_STRING; |
|
} |
|
|
|
|
|
void CEnvSoundscapeProxy::Activate() |
|
{ |
|
if ( m_MainSoundscapeName != NULL_STRING ) |
|
{ |
|
CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_MainSoundscapeName ); |
|
if ( pEntity ) |
|
{ |
|
m_hProxySoundscape = dynamic_cast< CEnvSoundscape* >( pEntity ); |
|
} |
|
} |
|
|
|
if ( m_hProxySoundscape ) |
|
{ |
|
// Copy the relevant parameters from our main soundscape. |
|
m_soundscapeIndex = m_hProxySoundscape->m_soundscapeIndex; |
|
for ( int i=0; i < ARRAYSIZE( m_positionNames ); i++ ) |
|
m_positionNames[i] = m_hProxySoundscape->m_positionNames[i]; |
|
} |
|
else |
|
{ |
|
Warning( "env_soundscape_proxy can't find target soundscape: '%s'\n", STRING( m_MainSoundscapeName ) ); |
|
} |
|
|
|
BaseClass::Activate(); |
|
} |
|
|
|
|
|
// ----------------------------------------------------------------------------- // |
|
// CEnvSoundscape stuff. |
|
// ----------------------------------------------------------------------------- // |
|
|
|
LINK_ENTITY_TO_CLASS( env_soundscape, CEnvSoundscape ); |
|
|
|
BEGIN_DATADESC( CEnvSoundscape ) |
|
|
|
DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), |
|
// don't save, recomputed on load |
|
//DEFINE_FIELD( m_soundscapeIndex, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_soundscapeName, FIELD_STRING ), |
|
DEFINE_FIELD( m_hProxySoundscape, FIELD_EHANDLE ), |
|
|
|
// Silence, Classcheck! |
|
// DEFINE_ARRAY( m_positionNames, FIELD_STRING, 4 ), |
|
|
|
DEFINE_KEYFIELD( m_positionNames[0], FIELD_STRING, "position0" ), |
|
DEFINE_KEYFIELD( m_positionNames[1], FIELD_STRING, "position1" ), |
|
DEFINE_KEYFIELD( m_positionNames[2], FIELD_STRING, "position2" ), |
|
DEFINE_KEYFIELD( m_positionNames[3], FIELD_STRING, "position3" ), |
|
DEFINE_KEYFIELD( m_positionNames[4], FIELD_STRING, "position4" ), |
|
DEFINE_KEYFIELD( m_positionNames[5], FIELD_STRING, "position5" ), |
|
DEFINE_KEYFIELD( m_positionNames[6], FIELD_STRING, "position6" ), |
|
DEFINE_KEYFIELD( m_positionNames[7], FIELD_STRING, "position7" ), |
|
|
|
DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ToggleEnabled", InputToggleEnabled ), |
|
|
|
DEFINE_OUTPUT( m_OnPlay, "OnPlay" ), |
|
|
|
|
|
END_DATADESC() |
|
|
|
CEnvSoundscape::CEnvSoundscape() |
|
{ |
|
m_soundscapeName = NULL_STRING; |
|
m_soundscapeIndex = -1; |
|
m_soundscapeEntityId = -1; |
|
m_bDisabled = false; |
|
g_SoundscapeSystem.AddSoundscapeEntity( this ); |
|
} |
|
|
|
CEnvSoundscape::~CEnvSoundscape() |
|
{ |
|
g_SoundscapeSystem.RemoveSoundscapeEntity( this ); |
|
} |
|
|
|
void CEnvSoundscape::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
if (!IsEnabled()) |
|
{ |
|
Enable(); |
|
} |
|
} |
|
|
|
void CEnvSoundscape::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
if (IsEnabled()) |
|
{ |
|
Disable(); |
|
} |
|
} |
|
|
|
void CEnvSoundscape::InputToggleEnabled( inputdata_t &inputdata ) |
|
{ |
|
if ( IsEnabled() ) |
|
{ |
|
Disable(); |
|
} |
|
else |
|
{ |
|
Enable(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns whether the laser is currently active. |
|
//----------------------------------------------------------------------------- |
|
bool CEnvSoundscape::IsEnabled( void ) const |
|
{ |
|
return !m_bDisabled; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CEnvSoundscape::Disable( void ) |
|
{ |
|
m_bDisabled = true; |
|
|
|
// Reset if we are the currently active soundscape |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CEnvSoundscape::Enable( void ) |
|
{ |
|
m_bDisabled = false; |
|
|
|
// Force the player to recheck soundscapes |
|
} |
|
|
|
bool CEnvSoundscape::KeyValue( const char *szKeyName, const char *szValue ) |
|
{ |
|
if (FStrEq(szKeyName, "soundscape")) |
|
{ |
|
m_soundscapeName = AllocPooledString( szValue ); |
|
} |
|
else |
|
return BaseClass::KeyValue( szKeyName, szValue ); |
|
|
|
return true; |
|
} |
|
|
|
// returns true if the given sound entity is in range |
|
// and can see the given player entity (pTarget) |
|
|
|
bool CEnvSoundscape::InRangeOfPlayer( CBasePlayer *pTarget ) |
|
{ |
|
Vector vecSpot1 = EarPosition(); |
|
Vector vecSpot2 = pTarget->EarPosition(); |
|
|
|
// calc range from sound entity to player |
|
Vector vecRange = vecSpot2 - vecSpot1; |
|
float range = vecRange.Length(); |
|
if ( m_flRadius > range || m_flRadius == -1 ) |
|
{ |
|
trace_t tr; |
|
|
|
UTIL_TraceLine( vecSpot1, vecSpot2, MASK_SOLID_BRUSHONLY|MASK_WATER, pTarget, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction == 1 && !tr.startsolid ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
int CEnvSoundscape::UpdateTransmitState() |
|
{ |
|
// Always transmit all soundscapes to the player. |
|
return SetTransmitState( FL_EDICT_ALWAYS ); |
|
} |
|
|
|
void CEnvSoundscape::WriteAudioParamsTo( audioparams_t &audio ) |
|
{ |
|
audio.ent.Set( this ); |
|
audio.soundscapeIndex = m_soundscapeIndex; |
|
audio.localBits = 0; |
|
for ( int i = 0; i < ARRAYSIZE(m_positionNames); i++ ) |
|
{ |
|
if ( m_positionNames[i] != NULL_STRING ) |
|
{ |
|
// We are a valid entity for a sound position |
|
CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_positionNames[i], this, this ); |
|
if ( pEntity ) |
|
{ |
|
audio.localBits |= 1<<i; |
|
audio.localSound.Set( i, pEntity->GetAbsOrigin() ); |
|
} |
|
} |
|
} |
|
|
|
m_OnPlay.FireOutput( this, this ); |
|
} |
|
|
|
|
|
// |
|
// A client that is visible and in range of a sound entity will |
|
// have its soundscape set by that sound entity. If two or more |
|
// sound entities are contending for a client, then the nearest |
|
// sound entity to the client will set the client's soundscape. |
|
// A client's soundscape will remain set to its prior value until |
|
// a new in-range, visible sound entity resets a new soundscape. |
|
// |
|
|
|
// CONSIDER: if player in water state, autoset and underwater soundscape? |
|
void CEnvSoundscape::UpdateForPlayer( ss_update_t &update ) |
|
{ |
|
if ( !IsEnabled() ) |
|
{ |
|
if ( update.pCurrentSoundscape == this ) |
|
{ |
|
update.pCurrentSoundscape = NULL; |
|
update.currentDistance = 0; |
|
update.bInRange = false; |
|
} |
|
return; |
|
} |
|
|
|
// calc range from sound entity to player |
|
Vector target = EarPosition(); |
|
float range = (update.playerPosition - target).Length(); |
|
|
|
if ( update.pCurrentSoundscape == this ) |
|
{ |
|
update.currentDistance = range; |
|
update.bInRange = false; |
|
if ( m_flRadius > range || m_flRadius == -1 ) |
|
{ |
|
trace_t tr; |
|
|
|
update.traceCount++; |
|
UTIL_TraceLine( target, update.playerPosition, MASK_SOLID_BRUSHONLY|MASK_WATER, update.pPlayer, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fraction == 1 && !tr.startsolid ) |
|
{ |
|
update.bInRange = true; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( (!update.bInRange || range < update.currentDistance ) && (m_flRadius > range || m_flRadius == -1) ) |
|
{ |
|
trace_t tr; |
|
|
|
update.traceCount++; |
|
UTIL_TraceLine( target, update.playerPosition, MASK_SOLID_BRUSHONLY|MASK_WATER, update.pPlayer, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction == 1 && !tr.startsolid ) |
|
{ |
|
audioparams_t &audio = update.pPlayer->GetAudioParams(); |
|
WriteAudioParamsTo( audio ); |
|
update.pCurrentSoundscape = this; |
|
update.bInRange = true; |
|
update.currentDistance = range; |
|
} |
|
} |
|
} |
|
|
|
|
|
if ( soundscape_debug.GetBool() ) |
|
{ |
|
// draw myself |
|
NDebugOverlay::Box(GetAbsOrigin(), Vector(-10,-10,-10), Vector(10,10,10), 255, 0, 255, 64, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
|
|
if ( update.pPlayer ) |
|
{ |
|
audioparams_t &audio = update.pPlayer->GetAudioParams(); |
|
if ( audio.ent.Get() != this ) |
|
{ |
|
if ( InRangeOfPlayer( update.pPlayer ) ) |
|
{ |
|
NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 255, 255, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 255, 0, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( InRangeOfPlayer( update.pPlayer ) ) |
|
{ |
|
NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 0, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 255, 170, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
} |
|
|
|
// also draw lines to each sound position. |
|
// we don't store the number of local sound positions, just a bitvector of which ones are on. |
|
unsigned int soundbits = audio.localBits.Get(); |
|
float periodic = 2.0f * sin((fmod(gpGlobals->curtime,2.0f) - 1.0f) * M_PI); // = -4f .. 4f |
|
for (int ii = 0 ; ii < NUM_AUDIO_LOCAL_SOUNDS ; ++ii ) |
|
{ |
|
if ( soundbits & (1 << ii) ) |
|
{ |
|
const Vector &soundLoc = audio.localSound.Get(ii); |
|
NDebugOverlay::Line( GetAbsOrigin(), soundLoc, 0, 32 , 255 , false, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
NDebugOverlay::Cross3D( soundLoc, 16.0f + periodic, 0, 0, 255, false, NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
NDebugOverlay::EntityTextAtPosition( GetAbsOrigin(), 0, STRING(m_soundscapeName), NDEBUG_PERSIST_TILL_NEXT_SERVER ); |
|
} |
|
} |
|
|
|
// |
|
// env_soundscape - spawn a sound entity that will set player soundscape |
|
// when player moves in range and sight. |
|
// |
|
// |
|
void CEnvSoundscape::Spawn( ) |
|
{ |
|
Precache(); |
|
// Because the soundscape has no model, need to make sure it doesn't get culled from the PVS for this reason and therefore |
|
// never exist on the client, etc. |
|
AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); |
|
|
|
} |
|
|
|
void CEnvSoundscape::Precache() |
|
{ |
|
if ( m_soundscapeName == NULL_STRING ) |
|
{ |
|
DevMsg("Found soundscape entity with no soundscape name.\n" ); |
|
return; |
|
} |
|
|
|
m_soundscapeIndex = g_SoundscapeSystem.GetSoundscapeIndex( STRING(m_soundscapeName) ); |
|
if ( IsX360()) |
|
{ |
|
g_SoundscapeSystem.PrecacheSounds( m_soundscapeIndex ); |
|
} |
|
if ( !g_SoundscapeSystem.IsValidIndex( m_soundscapeIndex ) ) |
|
{ |
|
DevWarning("Can't find soundscape: %s\n", STRING(m_soundscapeName) ); |
|
} |
|
} |
|
|
|
void CEnvSoundscape::DrawDebugGeometryOverlays( void ) |
|
{ |
|
if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); |
|
if ( pPlayer ) |
|
{ |
|
audioparams_t &audio = pPlayer->GetAudioParams(); |
|
if ( audio.ent.Get() != this ) |
|
{ |
|
CBaseEntity *pEnt = pPlayer; // ->GetSoundscapeListener(); |
|
if ( pEnt ) |
|
{ |
|
NDebugOverlay::Line(GetAbsOrigin(), pEnt->WorldSpaceCenter(), 255, 0, 255, false, 0 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
BaseClass::DrawDebugGeometryOverlays(); |
|
} |
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------- // |
|
// CEnvSoundscapeTriggerable |
|
// ---------------------------------------------------------------------------------------------------- // |
|
|
|
LINK_ENTITY_TO_CLASS( env_soundscape_triggerable, CEnvSoundscapeTriggerable ); |
|
|
|
BEGIN_DATADESC( CEnvSoundscapeTriggerable ) |
|
END_DATADESC() |
|
|
|
|
|
CEnvSoundscapeTriggerable::CEnvSoundscapeTriggerable() |
|
{ |
|
} |
|
|
|
|
|
void CEnvSoundscapeTriggerable::DelegateStartTouch( CBaseEntity *pEnt ) |
|
{ |
|
CBasePlayer *pPlayer = dynamic_cast< CBasePlayer* >( pEnt ); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
// Just in case.. we shouldn't already be in the player's list because it should have |
|
// called DelegateEndTouch, but this seems to happen when they're noclipping. |
|
pPlayer->m_hTriggerSoundscapeList.FindAndRemove( this ); |
|
|
|
// Add us to the player's list of soundscapes and |
|
pPlayer->m_hTriggerSoundscapeList.AddToHead( this ); |
|
WriteAudioParamsTo( pPlayer->GetAudioParams() ); |
|
} |
|
|
|
|
|
void CEnvSoundscapeTriggerable::DelegateEndTouch( CBaseEntity *pEnt ) |
|
{ |
|
CBasePlayer *pPlayer = dynamic_cast< CBasePlayer* >( pEnt ); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
// Remove us from the ent's list of soundscapes. |
|
pPlayer->m_hTriggerSoundscapeList.FindAndRemove( this ); |
|
while ( pPlayer->m_hTriggerSoundscapeList.Count() > 0 ) |
|
{ |
|
CEnvSoundscapeTriggerable *pSS = dynamic_cast< CEnvSoundscapeTriggerable* >( pPlayer->m_hTriggerSoundscapeList[0].Get() ); |
|
if ( pSS ) |
|
{ |
|
// Make this one current. |
|
pSS->WriteAudioParamsTo( pPlayer->GetAudioParams() ); |
|
return; |
|
} |
|
else |
|
{ |
|
pPlayer->m_hTriggerSoundscapeList.Remove( 0 ); |
|
} |
|
} |
|
|
|
// No soundscapes left. |
|
pPlayer->GetAudioParams().ent = NULL; |
|
} |
|
|
|
|
|
void CEnvSoundscapeTriggerable::Think() |
|
{ |
|
// Overrides the base class's think and prevents it from running at all. |
|
} |
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------- // |
|
// CTriggerSoundscape |
|
// ---------------------------------------------------------------------------------------------------- // |
|
|
|
class CTriggerSoundscape : public CBaseTrigger |
|
{ |
|
public: |
|
DECLARE_CLASS( CTriggerSoundscape, CBaseTrigger ); |
|
DECLARE_DATADESC(); |
|
|
|
CTriggerSoundscape(); |
|
|
|
virtual void StartTouch( CBaseEntity *pOther ); |
|
virtual void EndTouch( CBaseEntity *pOther ); |
|
|
|
virtual void Spawn(); |
|
virtual void Activate(); |
|
|
|
void PlayerUpdateThink(); |
|
|
|
private: |
|
CHandle<CEnvSoundscapeTriggerable> m_hSoundscape; |
|
string_t m_SoundscapeName; |
|
|
|
CUtlVector<CBasePlayerHandle> m_spectators; // spectators in our volume |
|
}; |
|
|
|
|
|
LINK_ENTITY_TO_CLASS( trigger_soundscape, CTriggerSoundscape ); |
|
|
|
BEGIN_DATADESC( CTriggerSoundscape ) |
|
DEFINE_THINKFUNC( PlayerUpdateThink ), |
|
DEFINE_KEYFIELD( m_SoundscapeName, FIELD_STRING, "soundscape" ), |
|
DEFINE_FIELD( m_hSoundscape, FIELD_EHANDLE ), |
|
DEFINE_UTLVECTOR( m_spectators, FIELD_EHANDLE ), |
|
END_DATADESC() |
|
|
|
|
|
CTriggerSoundscape::CTriggerSoundscape() |
|
{ |
|
} |
|
|
|
|
|
void CTriggerSoundscape::StartTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( m_hSoundscape ) |
|
m_hSoundscape->DelegateStartTouch( pOther ); |
|
|
|
BaseClass::StartTouch( pOther ); |
|
} |
|
|
|
|
|
void CTriggerSoundscape::EndTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( m_hSoundscape ) |
|
m_hSoundscape->DelegateEndTouch( pOther ); |
|
|
|
BaseClass::EndTouch( pOther ); |
|
} |
|
|
|
|
|
void CTriggerSoundscape::Spawn() |
|
{ |
|
BaseClass::Spawn(); |
|
InitTrigger(); |
|
|
|
SetThink( &CTriggerSoundscape::PlayerUpdateThink ); |
|
SetNextThink( gpGlobals->curtime + 0.2 ); |
|
} |
|
|
|
|
|
void CTriggerSoundscape::Activate() |
|
{ |
|
m_hSoundscape = dynamic_cast< CEnvSoundscapeTriggerable* >( gEntList.FindEntityByName( NULL, m_SoundscapeName ) ); |
|
BaseClass::Activate(); |
|
} |
|
|
|
|
|
// look for dead/spectating players in our volume, to call touch on |
|
void CTriggerSoundscape::PlayerUpdateThink() |
|
{ |
|
int i; |
|
SetNextThink( gpGlobals->curtime + 0.2 ); |
|
|
|
CUtlVector<CBasePlayerHandle> oldSpectators; |
|
oldSpectators = m_spectators; |
|
m_spectators.RemoveAll(); |
|
|
|
for ( i=1; i <= gpGlobals->maxClients; ++i ) |
|
{ |
|
CBasePlayer *player = UTIL_PlayerByIndex( i ); |
|
|
|
if ( !player ) |
|
continue; |
|
|
|
if ( player->IsAlive() ) |
|
continue; |
|
|
|
// if the spectator is intersecting the trigger, track it, and start a touch if it is just starting to touch |
|
if ( Intersects( player ) ) |
|
{ |
|
if ( !oldSpectators.HasElement( player ) ) |
|
{ |
|
StartTouch( player ); |
|
} |
|
m_spectators.AddToTail( player ); |
|
} |
|
} |
|
|
|
// check for spectators who are no longer intersecting |
|
for ( i=0; i<oldSpectators.Count(); ++i ) |
|
{ |
|
CBasePlayer *player = oldSpectators[i]; |
|
|
|
if ( !player ) |
|
continue; |
|
|
|
if ( !m_spectators.HasElement( player ) ) |
|
{ |
|
EndTouch( player ); |
|
} |
|
} |
|
}
|
|
|