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.

531 lines
13 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "trains.h"
  9. #include "entitylist.h"
  10. #include "soundenvelope.h"
  11. #include "engine/IEngineSound.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. void UTIL_RemoveHierarchy( CBaseEntity *pDead )
  15. {
  16. if ( !pDead )
  17. return;
  18. if ( pDead->edict() )
  19. {
  20. CBaseEntity *pChild = pDead->FirstMoveChild();
  21. while ( pChild )
  22. {
  23. CBaseEntity *pEntity = pChild;
  24. pChild = pChild->NextMovePeer();
  25. UTIL_RemoveHierarchy( pEntity );
  26. }
  27. }
  28. UTIL_Remove( pDead );
  29. }
  30. class CFuncTankTrain : public CFuncTrackTrain
  31. {
  32. public:
  33. DECLARE_CLASS( CFuncTankTrain, CFuncTrackTrain );
  34. void Spawn( void );
  35. // Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
  36. int OnTakeDamage( const CTakeDamageInfo &info );
  37. void Event_Killed( const CTakeDamageInfo &info );
  38. void Blocked( CBaseEntity *pOther )
  39. {
  40. // FIxme, set speed to zero?
  41. }
  42. DECLARE_DATADESC();
  43. private:
  44. COutputEvent m_OnDeath;
  45. };
  46. LINK_ENTITY_TO_CLASS( func_tanktrain, CFuncTankTrain );
  47. BEGIN_DATADESC( CFuncTankTrain )
  48. // Outputs
  49. DEFINE_OUTPUT(m_OnDeath, "OnDeath"),
  50. END_DATADESC()
  51. void CFuncTankTrain::Spawn( void )
  52. {
  53. m_takedamage = true;
  54. BaseClass::Spawn();
  55. }
  56. // Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
  57. int CFuncTankTrain::OnTakeDamage( const CTakeDamageInfo &info )
  58. {
  59. if ( ! (info.GetDamageType() & DMG_BLAST) )
  60. return 0;
  61. return BaseClass::OnTakeDamage( info );
  62. }
  63. //-----------------------------------------------------------------------------
  64. // Purpose: Called when the train is killed.
  65. // Input : pInflictor - What killed us.
  66. // pAttacker - Who killed us.
  67. // flDamage - The damage that the killing blow inflicted.
  68. // bitsDamageType - Bitfield of damage types that were inflicted.
  69. //-----------------------------------------------------------------------------
  70. void CFuncTankTrain::Event_Killed( const CTakeDamageInfo &info )
  71. {
  72. m_takedamage = DAMAGE_NO;
  73. m_lifeState = LIFE_DEAD;
  74. m_OnDeath.FireOutput( info.GetInflictor(), this );
  75. }
  76. //-----------------------------------------------------------------------------
  77. // Purpose: Changes the target entity for a func_tank or tanktrain_ai
  78. //-----------------------------------------------------------------------------
  79. class CTankTargetChange : public CPointEntity
  80. {
  81. public:
  82. DECLARE_CLASS( CTankTargetChange, CPointEntity );
  83. void Precache( void );
  84. void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  85. DECLARE_DATADESC();
  86. private:
  87. variant_t m_newTarget;
  88. string_t m_newTargetName;
  89. };
  90. LINK_ENTITY_TO_CLASS( tanktrain_aitarget, CTankTargetChange );
  91. BEGIN_DATADESC( CTankTargetChange )
  92. // DEFINE_FIELD( m_newTarget, variant_t ),
  93. DEFINE_KEYFIELD( m_newTargetName, FIELD_STRING, "newtarget" ),
  94. END_DATADESC()
  95. void CTankTargetChange::Precache( void )
  96. {
  97. BaseClass::Precache();
  98. // This needs to be in Precache so save/load works
  99. m_newTarget.SetString( m_newTargetName );
  100. }
  101. void CTankTargetChange::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  102. {
  103. CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_target, NULL, pActivator, pCaller );
  104. // UNDONE: This should use more of the event system
  105. while ( pTarget )
  106. {
  107. // Change the target over
  108. pTarget->AcceptInput( "TargetEntity", this, this, m_newTarget, 0 );
  109. pTarget = gEntList.FindEntityByName( pTarget, m_target, NULL, pActivator, pCaller );
  110. }
  111. }
  112. // UNDONE: Should be just a logical entity, but we act as another static sound channel for the train
  113. class CTankTrainAI : public CPointEntity
  114. {
  115. public:
  116. DECLARE_CLASS( CTankTrainAI, CPointEntity );
  117. virtual ~CTankTrainAI( void );
  118. void Precache( void );
  119. void Spawn( void );
  120. void Activate( void );
  121. void Think( void );
  122. int SoundEnginePitch( void );
  123. void SoundEngineStart( void );
  124. void SoundEngineStop( void );
  125. void SoundShutdown( void );
  126. CBaseEntity *FindTarget( string_t target, CBaseEntity *pActivator );
  127. DECLARE_DATADESC();
  128. // INPUTS
  129. void InputTargetEntity( inputdata_t &inputdata );
  130. private:
  131. CHandle<CFuncTrackTrain> m_hTrain;
  132. EHANDLE m_hTargetEntity;
  133. int m_soundPlaying;
  134. CSoundPatch *m_soundTreads;
  135. CSoundPatch *m_soundEngine;
  136. string_t m_startSoundName;
  137. string_t m_engineSoundName;
  138. string_t m_movementSoundName;
  139. string_t m_targetEntityName;
  140. };
  141. LINK_ENTITY_TO_CLASS( tanktrain_ai, CTankTrainAI );
  142. BEGIN_DATADESC( CTankTrainAI )
  143. DEFINE_FIELD( m_hTrain, FIELD_EHANDLE),
  144. DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE),
  145. DEFINE_FIELD( m_soundPlaying, FIELD_INTEGER),
  146. DEFINE_SOUNDPATCH( m_soundTreads ),
  147. DEFINE_SOUNDPATCH( m_soundEngine ),
  148. DEFINE_KEYFIELD( m_startSoundName, FIELD_STRING, "startsound" ),
  149. DEFINE_KEYFIELD( m_engineSoundName, FIELD_STRING, "enginesound" ),
  150. DEFINE_KEYFIELD( m_movementSoundName, FIELD_STRING, "movementsound" ),
  151. DEFINE_FIELD( m_targetEntityName, FIELD_STRING),
  152. // Inputs
  153. DEFINE_INPUTFUNC( FIELD_STRING, "TargetEntity", InputTargetEntity ),
  154. END_DATADESC()
  155. //-----------------------------------------------------------------------------
  156. // Purpose: Input handler for setting the target entity by name.
  157. //-----------------------------------------------------------------------------
  158. void CTankTrainAI::InputTargetEntity( inputdata_t &inputdata )
  159. {
  160. m_targetEntityName = inputdata.value.StringID();
  161. m_hTargetEntity = FindTarget( m_targetEntityName, inputdata.pActivator );
  162. SetNextThink( gpGlobals->curtime );
  163. }
  164. //-----------------------------------------------------------------------------
  165. // Purpose: Finds the first entity in the entity list with the given name.
  166. // Input : target - String ID of the entity to find.
  167. // pActivator - The activating entity if this is called from an input
  168. // or Use handler, NULL otherwise.
  169. //-----------------------------------------------------------------------------
  170. CBaseEntity *CTankTrainAI::FindTarget( string_t target, CBaseEntity *pActivator )
  171. {
  172. return gEntList.FindEntityGeneric( NULL, STRING( target ), this, pActivator );
  173. }
  174. CTankTrainAI::~CTankTrainAI( void )
  175. {
  176. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  177. if ( m_soundTreads )
  178. {
  179. controller.SoundDestroy( m_soundTreads );
  180. }
  181. if ( m_soundEngine )
  182. {
  183. controller.SoundDestroy( m_soundEngine );
  184. }
  185. }
  186. void CTankTrainAI::Precache( void )
  187. {
  188. PrecacheScriptSound( STRING( m_startSoundName ) );
  189. PrecacheScriptSound( STRING( m_engineSoundName ) );
  190. PrecacheScriptSound( STRING( m_movementSoundName ) );
  191. }
  192. int CTankTrainAI::SoundEnginePitch( void )
  193. {
  194. CFuncTrackTrain *pTrain = m_hTrain;
  195. // we know this isn't NULL here
  196. if ( pTrain->GetMaxSpeed() )
  197. {
  198. return 90 + (fabs(pTrain->GetCurrentSpeed()) * (20) / pTrain->GetMaxSpeed());
  199. }
  200. return 100;
  201. }
  202. void CTankTrainAI::SoundEngineStart( void )
  203. {
  204. CFuncTrackTrain *pTrain = m_hTrain;
  205. SoundEngineStop();
  206. // play startup sound for train
  207. if ( m_startSoundName != NULL_STRING )
  208. {
  209. CPASAttenuationFilter filter( pTrain );
  210. EmitSound_t ep;
  211. ep.m_nChannel = CHAN_ITEM;
  212. ep.m_pSoundName = STRING(m_startSoundName);
  213. ep.m_flVolume = 1.0f;
  214. ep.m_SoundLevel = SNDLVL_NORM;
  215. EmitSound( filter, pTrain->entindex(), ep );
  216. }
  217. // play the looping sounds using the envelope controller
  218. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  219. if ( m_soundTreads )
  220. {
  221. controller.Play( m_soundTreads, 1.0, 100 );
  222. }
  223. if ( m_soundEngine )
  224. {
  225. controller.Play( m_soundEngine, 0.5, 90 );
  226. controller.CommandClear( m_soundEngine );
  227. controller.CommandAdd( m_soundEngine, 0, SOUNDCTRL_CHANGE_PITCH, 1.5, random->RandomInt(130, 145) );
  228. controller.CommandAdd( m_soundEngine, 1.5, SOUNDCTRL_CHANGE_PITCH, 2, random->RandomInt(105, 115) );
  229. }
  230. m_soundPlaying = true;
  231. }
  232. void CTankTrainAI::SoundEngineStop( void )
  233. {
  234. if ( !m_soundPlaying )
  235. return;
  236. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  237. if ( m_soundTreads )
  238. {
  239. controller.SoundFadeOut( m_soundTreads, 0.25 );
  240. }
  241. if ( m_soundEngine )
  242. {
  243. controller.CommandClear( m_soundEngine );
  244. controller.SoundChangePitch( m_soundEngine, 70, 3.0 );
  245. }
  246. m_soundPlaying = false;
  247. }
  248. void CTankTrainAI::SoundShutdown( void )
  249. {
  250. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  251. if ( m_soundTreads )
  252. {
  253. controller.Shutdown( m_soundTreads );
  254. }
  255. if ( m_soundEngine )
  256. {
  257. controller.Shutdown( m_soundEngine );
  258. }
  259. m_soundPlaying = false;
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose: Set up think and AI
  263. //-----------------------------------------------------------------------------
  264. void CTankTrainAI::Spawn( void )
  265. {
  266. Precache();
  267. m_soundPlaying = false;
  268. m_hTargetEntity = NULL;
  269. }
  270. void CTankTrainAI::Activate( void )
  271. {
  272. BaseClass::Activate();
  273. CBaseEntity *pTarget = NULL;
  274. CFuncTrackTrain *pTrain = NULL;
  275. if ( m_target != NULL_STRING )
  276. {
  277. do
  278. {
  279. pTarget = gEntList.FindEntityByName( pTarget, m_target );
  280. pTrain = dynamic_cast<CFuncTrackTrain *>(pTarget);
  281. } while (!pTrain && pTarget);
  282. }
  283. m_hTrain = pTrain;
  284. if ( pTrain )
  285. {
  286. SetNextThink( gpGlobals->curtime + 0.5f );
  287. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  288. if ( m_movementSoundName != NULL_STRING )
  289. {
  290. CPASAttenuationFilter filter( this, ATTN_NORM * 0.5 );
  291. m_soundTreads = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_movementSoundName), ATTN_NORM*0.5 );
  292. }
  293. if ( m_engineSoundName != NULL_STRING )
  294. {
  295. CPASAttenuationFilter filter( this );
  296. m_soundEngine = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_engineSoundName), ATTN_NORM );
  297. }
  298. }
  299. }
  300. //-----------------------------------------------------------------------------
  301. // Purpose: Dumb linear serach of the path
  302. // Input : *pStart - starting path node
  303. // &startPosition - starting position
  304. // &destination - position to move close to
  305. // Output : int move direction 1 = forward, -1 = reverse, 0 = stop
  306. //-----------------------------------------------------------------------------
  307. int PathFindDirection( CPathTrack *pStart, const Vector &startPosition, const Vector &destination )
  308. {
  309. if ( !pStart )
  310. return 0; // no path, don't move
  311. CPathTrack *pPath = pStart->m_pnext;
  312. CPathTrack *pNearest = pStart;
  313. float nearestDist = (pNearest->GetLocalOrigin() - destination).LengthSqr();
  314. float length = 0;
  315. float nearestForward = 0, nearestReverse = 0;
  316. do
  317. {
  318. float dist = (pPath->GetLocalOrigin() - destination).LengthSqr();
  319. // This is closer than our current estimate
  320. if ( dist < nearestDist )
  321. {
  322. nearestDist = dist;
  323. pNearest = pPath;
  324. nearestForward = length; // current path length forward
  325. nearestReverse = 0; // count until we hit the start again
  326. }
  327. CPathTrack *pNext = pPath->m_pnext;
  328. if ( pNext )
  329. {
  330. // UNDONE: Cache delta in path?
  331. float delta = (pNext->GetLocalOrigin() - pPath->GetLocalOrigin()).LengthSqr();
  332. length += delta;
  333. // add to current reverse estimate
  334. nearestReverse += delta;
  335. pPath = pNext;
  336. }
  337. else
  338. {
  339. // not a looping path
  340. // traverse back to other end of the path
  341. int fail = 0;
  342. while ( pPath->m_pprevious )
  343. {
  344. fail++;
  345. // HACKHACK: Don't infinite loop
  346. if ( fail > 256 )
  347. break;
  348. pPath = pPath->m_pprevious;
  349. }
  350. // don't take the reverse path to old node
  351. nearestReverse = nearestForward + 1;
  352. // dont' take forward path to new node (if we find one)
  353. length = (float)COORD_EXTENT * (float)COORD_EXTENT; // HACKHACK: Max quad length
  354. }
  355. } while ( pPath != pStart );
  356. // UNDONE: Fix this fudge factor
  357. // if you are already at the path, or <100 units away, don't move
  358. if ( pNearest == pStart || (pNearest->GetLocalOrigin() - startPosition).LengthSqr() < 100 )
  359. return 0;
  360. if ( nearestForward <= nearestReverse )
  361. return 1;
  362. return -1;
  363. }
  364. //-----------------------------------------------------------------------------
  365. // Purpose: Find a point on my path near to the target and move toward it
  366. //-----------------------------------------------------------------------------
  367. void CTankTrainAI::Think( void )
  368. {
  369. CFuncTrackTrain *pTrain = m_hTrain;
  370. if ( !pTrain || pTrain->m_lifeState != LIFE_ALIVE )
  371. {
  372. SoundShutdown();
  373. if ( pTrain )
  374. UTIL_RemoveHierarchy( pTrain );
  375. UTIL_Remove( this );
  376. return;
  377. }
  378. int desired = 0;
  379. CBaseEntity *pTarget = m_hTargetEntity;
  380. if ( pTarget )
  381. {
  382. desired = PathFindDirection( pTrain->m_ppath, pTrain->GetLocalOrigin(), pTarget->GetLocalOrigin() );
  383. }
  384. // If the train wants to stop, figure out throttle
  385. // otherwise, just throttle in the indicated direction and let the train logic
  386. // clip the speed
  387. if ( !desired )
  388. {
  389. if ( pTrain->m_flSpeed > 0 )
  390. {
  391. desired = -1;
  392. }
  393. else if ( pTrain->m_flSpeed < 0 )
  394. {
  395. desired = 1;
  396. }
  397. }
  398. // UNDONE: Align the think time with arrival, and bump this up to a few seconds
  399. SetNextThink( gpGlobals->curtime + 0.5f );
  400. if ( desired != 0 )
  401. {
  402. int wasMoving = (pTrain->m_flSpeed == 0) ? false : true;
  403. // chaser wants train to move, send message
  404. pTrain->SetSpeed( desired );
  405. int isMoving = (pTrain->m_flSpeed == 0) ? false : true;
  406. if ( !isMoving && wasMoving )
  407. {
  408. SoundEngineStop();
  409. }
  410. else if ( isMoving )
  411. {
  412. if ( !wasMoving )
  413. {
  414. SoundEngineStart();
  415. }
  416. }
  417. }
  418. else
  419. {
  420. SoundEngineStop();
  421. // UNDONE: Align the think time with arrival, and bump this up to a few seconds
  422. SetNextThink( gpGlobals->curtime + 1.0f );
  423. }
  424. }