Team Fortress 2 Source Code as on 22/4/2020
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.

535 lines
14 KiB

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