Counter Strike : Global Offensive Source Code
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.

596 lines
16 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "cbase.h"
  7. #include "soundscape.h"
  8. #include "datamap.h"
  9. #include "soundscape_system.h"
  10. #include "triggers.h"
  11. #include "saverestore_utlvector.h"
  12. #include "gamerules.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. 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." );
  16. // ----------------------------------------------------------------------------- //
  17. // CEnvSoundscapeProxy stuff.
  18. // ----------------------------------------------------------------------------- //
  19. LINK_ENTITY_TO_CLASS( env_soundscape_proxy, CEnvSoundscapeProxy );
  20. BEGIN_DATADESC( CEnvSoundscapeProxy )
  21. DEFINE_KEYFIELD( m_MainSoundscapeName, FIELD_STRING, "MainSoundscapeName" )
  22. END_DATADESC()
  23. CEnvSoundscapeProxy::CEnvSoundscapeProxy()
  24. {
  25. m_MainSoundscapeName = NULL_STRING;
  26. }
  27. void CEnvSoundscapeProxy::Activate()
  28. {
  29. if ( m_MainSoundscapeName != NULL_STRING )
  30. {
  31. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_MainSoundscapeName );
  32. if ( pEntity )
  33. {
  34. m_hProxySoundscape = dynamic_cast< CEnvSoundscape* >( pEntity );
  35. }
  36. }
  37. if ( m_hProxySoundscape )
  38. {
  39. // Copy the relevant parameters from our main soundscape.
  40. m_soundscapeIndex = m_hProxySoundscape->m_soundscapeIndex;
  41. for ( int i=0; i < ARRAYSIZE( m_positionNames ); i++ )
  42. m_positionNames[i] = m_hProxySoundscape->m_positionNames[i];
  43. }
  44. else
  45. {
  46. Warning( "env_soundscape_proxy can't find target soundscape: '%s'\n", STRING( m_MainSoundscapeName ) );
  47. }
  48. BaseClass::Activate();
  49. }
  50. // ----------------------------------------------------------------------------- //
  51. // CEnvSoundscape stuff.
  52. // ----------------------------------------------------------------------------- //
  53. LINK_ENTITY_TO_CLASS( env_soundscape, CEnvSoundscape );
  54. BEGIN_DATADESC( CEnvSoundscape )
  55. DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
  56. // don't save, recomputed on load
  57. //DEFINE_FIELD( m_soundscapeIndex, FIELD_INTEGER ),
  58. //DEFINE_FIELD( m_soundscapeEntityId, FIELD_INTEGER ),
  59. DEFINE_FIELD( m_soundscapeName, FIELD_STRING ),
  60. DEFINE_FIELD( m_hProxySoundscape, FIELD_EHANDLE ),
  61. // Silence, Classcheck!
  62. // DEFINE_ARRAY( m_positionNames, FIELD_STRING, 4 ),
  63. DEFINE_KEYFIELD( m_positionNames[0], FIELD_STRING, "position0" ),
  64. DEFINE_KEYFIELD( m_positionNames[1], FIELD_STRING, "position1" ),
  65. DEFINE_KEYFIELD( m_positionNames[2], FIELD_STRING, "position2" ),
  66. DEFINE_KEYFIELD( m_positionNames[3], FIELD_STRING, "position3" ),
  67. DEFINE_KEYFIELD( m_positionNames[4], FIELD_STRING, "position4" ),
  68. DEFINE_KEYFIELD( m_positionNames[5], FIELD_STRING, "position5" ),
  69. DEFINE_KEYFIELD( m_positionNames[6], FIELD_STRING, "position6" ),
  70. DEFINE_KEYFIELD( m_positionNames[7], FIELD_STRING, "position7" ),
  71. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  72. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  73. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  74. DEFINE_INPUTFUNC( FIELD_VOID, "ToggleEnabled", InputToggleEnabled ),
  75. DEFINE_OUTPUT( m_OnPlay, "OnPlay" ),
  76. END_DATADESC()
  77. CEnvSoundscape::CEnvSoundscape()
  78. {
  79. m_soundscapeName = NULL_STRING;
  80. m_soundscapeIndex = -1;
  81. m_soundscapeEntityId = -1;
  82. m_bDisabled = false;
  83. g_SoundscapeSystem.AddSoundscapeEntity( this );
  84. }
  85. CEnvSoundscape::~CEnvSoundscape()
  86. {
  87. g_SoundscapeSystem.RemoveSoundscapeEntity( this );
  88. }
  89. void CEnvSoundscape::InputEnable( inputdata_t &inputdata )
  90. {
  91. if (!IsEnabled())
  92. {
  93. Enable();
  94. }
  95. }
  96. void CEnvSoundscape::InputDisable( inputdata_t &inputdata )
  97. {
  98. if (IsEnabled())
  99. {
  100. Disable();
  101. }
  102. }
  103. void CEnvSoundscape::InputToggleEnabled( inputdata_t &inputdata )
  104. {
  105. if ( IsEnabled() )
  106. {
  107. Disable();
  108. }
  109. else
  110. {
  111. Enable();
  112. }
  113. }
  114. //-----------------------------------------------------------------------------
  115. // Purpose: Returns whether the laser is currently active.
  116. //-----------------------------------------------------------------------------
  117. bool CEnvSoundscape::IsEnabled( void ) const
  118. {
  119. return !m_bDisabled && g_pGameRules->AllowSoundscapes();
  120. }
  121. //-----------------------------------------------------------------------------
  122. // Purpose:
  123. //-----------------------------------------------------------------------------
  124. void CEnvSoundscape::Disable( void )
  125. {
  126. m_bDisabled = true;
  127. // Reset if we are the currently active soundscape
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Purpose:
  131. //-----------------------------------------------------------------------------
  132. void CEnvSoundscape::Enable( void )
  133. {
  134. m_bDisabled = false;
  135. // Force the player to recheck soundscapes
  136. }
  137. bool CEnvSoundscape::KeyValue( const char *szKeyName, const char *szValue )
  138. {
  139. if (FStrEq(szKeyName, "soundscape"))
  140. {
  141. m_soundscapeName = AllocPooledString( szValue );
  142. }
  143. else
  144. return BaseClass::KeyValue( szKeyName, szValue );
  145. return true;
  146. }
  147. // returns true if the given sound entity is in range
  148. // and can see the given entity (pTarget)
  149. bool CEnvSoundscape::InRangeOfPlayer( CBasePlayer *pTarget )
  150. {
  151. Vector vecSpot1 = EarPosition();
  152. Vector vecSpot2 = pTarget->EarPosition();
  153. // calc range from sound entity to player
  154. Vector vecRange = vecSpot2 - vecSpot1;
  155. float range = vecRange.Length();
  156. if ( m_flRadius > range || m_flRadius == -1 )
  157. {
  158. trace_t tr;
  159. UTIL_TraceLine( vecSpot1, vecSpot2, MASK_SOLID_BRUSHONLY|MASK_WATER, pTarget, COLLISION_GROUP_NONE, &tr );
  160. if ( tr.fraction == 1 && !tr.startsolid )
  161. {
  162. return true;
  163. }
  164. }
  165. return false;
  166. }
  167. void CEnvSoundscape::WriteAudioParamsTo( audioparams_t &audio )
  168. {
  169. if ( !g_SoundscapeSystem.IsValidIndex( m_soundscapeIndex ) )
  170. {
  171. Warning("Setting invalid soundscape, %s, as the active soundscape. There is probably no script entry matching this name. BUG THIS!\n", STRING(m_soundscapeName) );
  172. }
  173. audio.entIndex = m_soundscapeEntityId;
  174. audio.soundscapeIndex = m_soundscapeIndex;
  175. audio.localBits = 0;
  176. for ( int i = 0; i < ARRAYSIZE(m_positionNames); i++ )
  177. {
  178. if ( m_positionNames[i] != NULL_STRING )
  179. {
  180. // We are a valid entity for a sound position
  181. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_positionNames[i], this, this );
  182. if ( pEntity )
  183. {
  184. audio.localBits |= 1<<i;
  185. audio.localSound.Set( i, pEntity->GetAbsOrigin() );
  186. }
  187. }
  188. }
  189. m_OnPlay.FireOutput( this, this );
  190. }
  191. //
  192. // A client that is visible and in range of a sound entity will
  193. // have its soundscape set by that sound entity. If two or more
  194. // sound entities are contending for a client, then the nearest
  195. // sound entity to the client will set the client's soundscape.
  196. // A client's soundscape will remain set to its prior value until
  197. // a new in-range, visible sound entity resets a new soundscape.
  198. //
  199. // CONSIDER: if player in water state, autoset and underwater soundscape?
  200. void CEnvSoundscape::UpdateForPlayer( ss_update_t &update )
  201. {
  202. if ( !IsEnabled() )
  203. {
  204. if ( update.pCurrentSoundscape == this )
  205. {
  206. update.pCurrentSoundscape = NULL;
  207. update.currentDistance = 0;
  208. update.bInRange = false;
  209. }
  210. return;
  211. }
  212. // calc range from sound entity to player
  213. Vector target = EarPosition();
  214. float range = (update.playerPosition - target).Length();
  215. if ( update.pCurrentSoundscape == this )
  216. {
  217. update.currentDistance = range;
  218. update.bInRange = false;
  219. if ( m_flRadius > range || m_flRadius == -1 )
  220. {
  221. trace_t tr;
  222. update.traceCount++;
  223. UTIL_TraceLine( target, update.playerPosition, MASK_SOLID_BRUSHONLY|MASK_WATER, update.pPlayer, COLLISION_GROUP_NONE, &tr );
  224. if ( tr.fraction == 1 && !tr.startsolid )
  225. {
  226. update.bInRange = true;
  227. }
  228. }
  229. }
  230. else
  231. {
  232. if ( (!update.bInRange || range < update.currentDistance ) && (m_flRadius > range || m_flRadius == -1) )
  233. {
  234. trace_t tr;
  235. update.traceCount++;
  236. UTIL_TraceLine( target, update.playerPosition, MASK_SOLID_BRUSHONLY|MASK_WATER, update.pPlayer, COLLISION_GROUP_NONE, &tr );
  237. if ( tr.fraction == 1 && !tr.startsolid )
  238. {
  239. audioparams_t &audio = update.pPlayer->GetAudioParams();
  240. WriteAudioParamsTo( audio );
  241. update.pCurrentSoundscape = this;
  242. update.bInRange = true;
  243. update.currentDistance = range;
  244. }
  245. }
  246. }
  247. if ( soundscape_debug.GetBool() )
  248. {
  249. // draw myself
  250. NDebugOverlay::Box(GetAbsOrigin(), Vector(-10,-10,-10), Vector(10,10,10), 255, 0, 255, 64, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  251. if ( update.pPlayer )
  252. {
  253. audioparams_t &audio = update.pPlayer->GetAudioParams();
  254. if ( audio.entIndex != m_soundscapeEntityId )
  255. {
  256. if ( InRangeOfPlayer( update.pPlayer ) )
  257. {
  258. NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 255, 255, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  259. }
  260. else
  261. {
  262. NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 255, 0, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  263. }
  264. }
  265. else
  266. {
  267. if ( InRangeOfPlayer( update.pPlayer ) )
  268. {
  269. NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 0, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  270. }
  271. else
  272. {
  273. NDebugOverlay::Line( GetAbsOrigin(), update.pPlayer->WorldSpaceCenter(), 255, 170, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  274. }
  275. // also draw lines to each sound position.
  276. // we don't store the number of local sound positions, just a bitvector of which ones are on.
  277. unsigned int soundbits = audio.localBits.Get();
  278. float periodic = 2.0f * sin((fmod(gpGlobals->curtime,2.0f) - 1.0f) * M_PI); // = -4f .. 4f
  279. for (int ii = 0 ; ii < NUM_AUDIO_LOCAL_SOUNDS ; ++ii )
  280. {
  281. if ( soundbits & (1 << ii) )
  282. {
  283. const Vector &soundLoc = audio.localSound.Get(ii);
  284. NDebugOverlay::Line( GetAbsOrigin(), soundLoc, 0, 32 , 255 , false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  285. NDebugOverlay::Cross3D( soundLoc, 16.0f + periodic, 0, 0, 255, false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  286. }
  287. }
  288. }
  289. }
  290. NDebugOverlay::EntityTextAtPosition( GetAbsOrigin(), 0, STRING(m_soundscapeName), NDEBUG_PERSIST_TILL_NEXT_SERVER );
  291. }
  292. }
  293. //
  294. // env_soundscape - spawn a sound entity that will set player soundscape
  295. // when player moves in range and sight.
  296. //
  297. //
  298. void CEnvSoundscape::Spawn( )
  299. {
  300. Precache();
  301. }
  302. void CEnvSoundscape::Precache()
  303. {
  304. if ( m_soundscapeName == NULL_STRING )
  305. {
  306. DevMsg("Found soundscape entity with no soundscape name.\n" );
  307. return;
  308. }
  309. m_soundscapeIndex = g_SoundscapeSystem.GetSoundscapeIndex( STRING(m_soundscapeName) );
  310. if ( IsGameConsole())
  311. {
  312. g_SoundscapeSystem.PrecacheSounds( m_soundscapeIndex );
  313. }
  314. if ( !g_SoundscapeSystem.IsValidIndex( m_soundscapeIndex ) )
  315. {
  316. DevWarning("Can't find soundscape: %s\n", STRING(m_soundscapeName) );
  317. }
  318. }
  319. void CEnvSoundscape::DrawDebugGeometryOverlays( void )
  320. {
  321. if ( m_debugOverlays & (OVERLAY_BBOX_BIT|OVERLAY_PIVOT_BIT|OVERLAY_ABSBOX_BIT) )
  322. {
  323. CBasePlayer *pPlayer = UTIL_GetListenServerHost();
  324. if ( pPlayer )
  325. {
  326. audioparams_t &audio = pPlayer->GetAudioParams();
  327. if ( audio.entIndex != m_soundscapeEntityId )
  328. {
  329. CBaseEntity *pEnt = pPlayer->GetSoundscapeListener();
  330. if ( pEnt )
  331. {
  332. NDebugOverlay::Line(GetAbsOrigin(), pEnt->WorldSpaceCenter(), 255, 0, 255, false, 0 );
  333. }
  334. }
  335. }
  336. }
  337. BaseClass::DrawDebugGeometryOverlays();
  338. }
  339. // ---------------------------------------------------------------------------------------------------- //
  340. // CEnvSoundscapeTriggerable
  341. // ---------------------------------------------------------------------------------------------------- //
  342. LINK_ENTITY_TO_CLASS( env_soundscape_triggerable, CEnvSoundscapeTriggerable );
  343. BEGIN_DATADESC( CEnvSoundscapeTriggerable )
  344. END_DATADESC()
  345. CEnvSoundscapeTriggerable::CEnvSoundscapeTriggerable()
  346. {
  347. }
  348. void CEnvSoundscapeTriggerable::DelegateStartTouch( CBaseEntity *pEnt )
  349. {
  350. CBasePlayer *pPlayer = dynamic_cast< CBasePlayer* >( pEnt );
  351. if ( !pPlayer )
  352. return;
  353. // Just in case.. we shouldn't already be in the player's list because it should have
  354. // called DelegateEndTouch, but this seems to happen when they're noclipping.
  355. pPlayer->m_hTriggerSoundscapeList.FindAndRemove( this );
  356. // Add us to the player's list of soundscapes and
  357. pPlayer->m_hTriggerSoundscapeList.AddToHead( this );
  358. WriteAudioParamsTo( pPlayer->GetAudioParams() );
  359. }
  360. void CEnvSoundscapeTriggerable::DelegateEndTouch( CBaseEntity *pEnt )
  361. {
  362. CBasePlayer *pPlayer = dynamic_cast< CBasePlayer* >( pEnt );
  363. if ( !pPlayer )
  364. return;
  365. // Remove us from the ent's list of soundscapes.
  366. pPlayer->m_hTriggerSoundscapeList.FindAndRemove( this );
  367. while ( pPlayer->m_hTriggerSoundscapeList.Count() > 0 )
  368. {
  369. CEnvSoundscapeTriggerable *pSS = dynamic_cast< CEnvSoundscapeTriggerable* >( pPlayer->m_hTriggerSoundscapeList[0].Get() );
  370. if ( pSS )
  371. {
  372. // Make this one current.
  373. pSS->WriteAudioParamsTo( pPlayer->GetAudioParams() );
  374. return;
  375. }
  376. else
  377. {
  378. pPlayer->m_hTriggerSoundscapeList.Remove( 0 );
  379. }
  380. }
  381. // No soundscapes left.
  382. pPlayer->GetAudioParams().entIndex = 0;
  383. }
  384. void CEnvSoundscapeTriggerable::Think()
  385. {
  386. // Overrides the base class's think and prevents it from running at all.
  387. }
  388. // ---------------------------------------------------------------------------------------------------- //
  389. // CTriggerSoundscape
  390. // ---------------------------------------------------------------------------------------------------- //
  391. class CTriggerSoundscape : public CBaseTrigger
  392. {
  393. public:
  394. DECLARE_CLASS( CTriggerSoundscape, CBaseTrigger );
  395. DECLARE_DATADESC();
  396. CTriggerSoundscape();
  397. virtual void StartTouch( CBaseEntity *pOther );
  398. virtual void EndTouch( CBaseEntity *pOther );
  399. virtual void Spawn();
  400. virtual void Activate();
  401. void PlayerUpdateThink();
  402. private:
  403. CHandle<CEnvSoundscapeTriggerable> m_hSoundscape;
  404. string_t m_SoundscapeName;
  405. CUtlVector<CBasePlayerHandle> m_spectators; // spectators in our volume
  406. };
  407. LINK_ENTITY_TO_CLASS( trigger_soundscape, CTriggerSoundscape );
  408. BEGIN_DATADESC( CTriggerSoundscape )
  409. DEFINE_THINKFUNC( PlayerUpdateThink ),
  410. DEFINE_KEYFIELD( m_SoundscapeName, FIELD_STRING, "soundscape" ),
  411. DEFINE_FIELD( m_hSoundscape, FIELD_EHANDLE ),
  412. DEFINE_UTLVECTOR( m_spectators, FIELD_EHANDLE ),
  413. END_DATADESC()
  414. CTriggerSoundscape::CTriggerSoundscape()
  415. {
  416. }
  417. void CTriggerSoundscape::StartTouch( CBaseEntity *pOther )
  418. {
  419. if ( m_hSoundscape )
  420. m_hSoundscape->DelegateStartTouch( pOther );
  421. BaseClass::StartTouch( pOther );
  422. }
  423. void CTriggerSoundscape::EndTouch( CBaseEntity *pOther )
  424. {
  425. if ( m_hSoundscape )
  426. m_hSoundscape->DelegateEndTouch( pOther );
  427. BaseClass::EndTouch( pOther );
  428. }
  429. void CTriggerSoundscape::Spawn()
  430. {
  431. BaseClass::Spawn();
  432. InitTrigger();
  433. SetThink( &CTriggerSoundscape::PlayerUpdateThink );
  434. SetNextThink( gpGlobals->curtime + 0.2 );
  435. }
  436. void CTriggerSoundscape::Activate()
  437. {
  438. m_hSoundscape = dynamic_cast< CEnvSoundscapeTriggerable* >( gEntList.FindEntityByName( NULL, m_SoundscapeName ) );
  439. BaseClass::Activate();
  440. }
  441. // look for dead/spectating players in our volume, to call touch on
  442. void CTriggerSoundscape::PlayerUpdateThink()
  443. {
  444. int i;
  445. SetNextThink( gpGlobals->curtime + 0.2 );
  446. CUtlVector<CBasePlayerHandle> oldSpectators;
  447. oldSpectators = m_spectators;
  448. m_spectators.RemoveAll();
  449. for ( i=1; i <= gpGlobals->maxClients; ++i )
  450. {
  451. CBasePlayer *player = UTIL_PlayerByIndex( i );
  452. if ( !player )
  453. continue;
  454. if ( player->IsAlive() )
  455. continue;
  456. // if the spectator is intersecting the trigger, track it, and start a touch if it is just starting to touch
  457. if ( Intersects( player ) )
  458. {
  459. if ( !oldSpectators.HasElement( player ) )
  460. {
  461. StartTouch( player );
  462. }
  463. m_spectators.AddToTail( player );
  464. }
  465. }
  466. // check for spectators who are no longer intersecting
  467. for ( i=0; i<oldSpectators.Count(); ++i )
  468. {
  469. CBasePlayer *player = oldSpectators[i];
  470. if ( !player )
  471. continue;
  472. if ( !m_spectators.HasElement( player ) )
  473. {
  474. EndTouch( player );
  475. }
  476. }
  477. }