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.

3418 lines
86 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Spawn, think, and touch functions for trains, etc.
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_basenpc.h"
  8. #include "trains.h"
  9. #include "ndebugoverlay.h"
  10. #include "entitylist.h"
  11. #include "engine/IEngineSound.h"
  12. #include "soundenvelope.h"
  13. #include "physics_npc_solver.h"
  14. #include "vphysics/friction.h"
  15. #include "hierarchy.h"
  16. #include "vscript_shared.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. static void PlatSpawnInsideTrigger(edict_t *pevPlatform);
  20. #define SF_PLAT_TOGGLE 0x0001
  21. class CBasePlatTrain : public CBaseToggle
  22. {
  23. DECLARE_CLASS( CBasePlatTrain, CBaseToggle );
  24. public:
  25. ~CBasePlatTrain();
  26. bool KeyValue( const char *szKeyName, const char *szValue );
  27. void Precache( void );
  28. // This is done to fix spawn flag collisions between this class and a derived class
  29. virtual bool IsTogglePlat( void ) { return (m_spawnflags & SF_PLAT_TOGGLE) ? true : false; }
  30. DECLARE_DATADESC();
  31. void PlayMovingSound();
  32. void StopMovingSound();
  33. string_t m_NoiseMoving; // sound a plat makes while moving
  34. string_t m_NoiseArrived;
  35. CSoundPatch *m_pMovementSound;
  36. float m_volume; // Sound volume
  37. float m_flTWidth;
  38. float m_flTLength;
  39. };
  40. BEGIN_DATADESC( CBasePlatTrain )
  41. DEFINE_KEYFIELD( m_NoiseMoving, FIELD_SOUNDNAME, "noise1" ),
  42. DEFINE_KEYFIELD( m_NoiseArrived, FIELD_SOUNDNAME, "noise2" ),
  43. DEFINE_SOUNDPATCH( m_pMovementSound ),
  44. DEFINE_KEYFIELD( m_volume, FIELD_FLOAT, "volume" ),
  45. DEFINE_FIELD( m_flTWidth, FIELD_FLOAT ),
  46. DEFINE_FIELD( m_flTLength, FIELD_FLOAT ),
  47. DEFINE_KEYFIELD( m_flLip, FIELD_FLOAT, "lip" ),
  48. DEFINE_KEYFIELD( m_flWait, FIELD_FLOAT, "wait" ),
  49. DEFINE_KEYFIELD( m_flHeight, FIELD_FLOAT, "height" ),
  50. END_DATADESC()
  51. bool CBasePlatTrain::KeyValue( const char *szKeyName, const char *szValue )
  52. {
  53. if (FStrEq(szKeyName, "rotation"))
  54. {
  55. m_vecFinalAngle.x = atof(szValue);
  56. }
  57. else
  58. {
  59. return BaseClass::KeyValue( szKeyName, szValue );
  60. }
  61. return true;
  62. }
  63. CBasePlatTrain::~CBasePlatTrain()
  64. {
  65. StopMovingSound();
  66. }
  67. void CBasePlatTrain::PlayMovingSound()
  68. {
  69. StopMovingSound();
  70. if(m_NoiseMoving != NULL_STRING )
  71. {
  72. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  73. CPASAttenuationFilter filter( this );
  74. m_pMovementSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, STRING(m_NoiseMoving), ATTN_NORM );
  75. controller.Play( m_pMovementSound, m_volume, PITCH_NORM );
  76. }
  77. }
  78. void CBasePlatTrain::StopMovingSound()
  79. {
  80. if ( m_pMovementSound )
  81. {
  82. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  83. controller.SoundDestroy( m_pMovementSound );
  84. m_pMovementSound = NULL;
  85. }
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Purpose:
  89. //-----------------------------------------------------------------------------
  90. void CBasePlatTrain::Precache( void )
  91. {
  92. //Fill in a default value if necessary
  93. UTIL_ValidateSoundName( m_NoiseMoving, "Plat.DefaultMoving" );
  94. UTIL_ValidateSoundName( m_NoiseArrived, "Plat.DefaultArrive" );
  95. //Precache them all
  96. PrecacheScriptSound( (char *) STRING(m_NoiseMoving) );
  97. PrecacheScriptSound( (char *) STRING(m_NoiseArrived) );
  98. }
  99. class CFuncPlat : public CBasePlatTrain
  100. {
  101. DECLARE_CLASS( CFuncPlat, CBasePlatTrain );
  102. public:
  103. void Spawn( void );
  104. void Precache( void );
  105. bool CreateVPhysics();
  106. void Setup( void );
  107. virtual void Blocked( CBaseEntity *pOther );
  108. void PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  109. void CallGoDown( void ) { GoDown(); }
  110. void CallHitTop( void ) { HitTop(); }
  111. void CallHitBottom( void ) { HitBottom(); }
  112. virtual void GoUp( void );
  113. virtual void GoDown( void );
  114. virtual void HitTop( void );
  115. virtual void HitBottom( void );
  116. void InputToggle(inputdata_t &data);
  117. void InputGoUp(inputdata_t &data);
  118. void InputGoDown(inputdata_t &data);
  119. DECLARE_DATADESC();
  120. private:
  121. string_t m_sNoise;
  122. };
  123. BEGIN_DATADESC( CFuncPlat )
  124. DEFINE_FIELD( m_sNoise, FIELD_STRING ),
  125. // Function Pointers
  126. DEFINE_FUNCTION( PlatUse ),
  127. DEFINE_FUNCTION( CallGoDown ),
  128. DEFINE_FUNCTION( CallHitTop ),
  129. DEFINE_FUNCTION( CallHitBottom ),
  130. // Inputs
  131. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  132. DEFINE_INPUTFUNC( FIELD_VOID, "GoUp", InputGoUp ),
  133. DEFINE_INPUTFUNC( FIELD_VOID, "GoDown", InputGoDown ),
  134. END_DATADESC()
  135. LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat );
  136. //==================================================
  137. // CPlatTrigger
  138. //==================================================
  139. class CPlatTrigger : public CBaseEntity
  140. {
  141. DECLARE_CLASS( CPlatTrigger, CBaseEntity );
  142. public:
  143. virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_DONT_SAVE; }
  144. void SpawnInsideTrigger( CFuncPlat *pPlatform );
  145. void Touch( CBaseEntity *pOther );
  146. CFuncPlat *m_pPlatform;
  147. };
  148. void CFuncPlat::Setup( void )
  149. {
  150. if (m_flTLength == 0)
  151. {
  152. m_flTLength = 80;
  153. }
  154. if (m_flTWidth == 0)
  155. {
  156. m_flTWidth = 10;
  157. }
  158. SetLocalAngles( vec3_angle );
  159. SetSolid( SOLID_BSP );
  160. SetMoveType( MOVETYPE_PUSH );
  161. // Set size and link into world
  162. SetModel( STRING( GetModelName() ) );
  163. m_vecPosition1 = GetLocalOrigin(); //Top
  164. m_vecPosition2 = GetLocalOrigin(); //Bottom
  165. if ( m_flHeight != 0 )
  166. {
  167. m_vecPosition2.z = GetLocalOrigin().z - m_flHeight;
  168. }
  169. else
  170. {
  171. // NOTE: This works because the angles were set to vec3_angle above
  172. m_vecPosition2.z = GetLocalOrigin().z - CollisionProp()->OBBSize().z + 8;
  173. }
  174. if (m_flSpeed == 0)
  175. {
  176. m_flSpeed = 150;
  177. }
  178. if ( m_volume == 0.0f )
  179. {
  180. m_volume = 0.85f;
  181. }
  182. }
  183. void CFuncPlat::Precache( )
  184. {
  185. BaseClass::Precache();
  186. if ( IsTogglePlat() == false )
  187. {
  188. // Create the "start moving" trigger
  189. PlatSpawnInsideTrigger( edict() );
  190. }
  191. }
  192. void CFuncPlat::Spawn( )
  193. {
  194. Setup();
  195. Precache();
  196. // If this platform is the target of some button, it starts at the TOP position,
  197. // and is brought down by that button. Otherwise, it starts at BOTTOM.
  198. if ( GetEntityName() != NULL_STRING )
  199. {
  200. UTIL_SetOrigin( this, m_vecPosition1);
  201. m_toggle_state = TS_AT_TOP;
  202. SetUse( &CFuncPlat::PlatUse );
  203. }
  204. else
  205. {
  206. UTIL_SetOrigin( this, m_vecPosition2);
  207. m_toggle_state = TS_AT_BOTTOM;
  208. }
  209. CreateVPhysics();
  210. }
  211. bool CFuncPlat::CreateVPhysics()
  212. {
  213. VPhysicsInitShadow( false, false );
  214. return true;
  215. }
  216. static void PlatSpawnInsideTrigger(edict_t* pevPlatform)
  217. {
  218. // old code: //GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) );
  219. CPlatTrigger *plattrig = CREATE_UNSAVED_ENTITY( CPlatTrigger, "plat_trigger" );
  220. plattrig->SpawnInsideTrigger( (CFuncPlat *)GetContainingEntity( pevPlatform ) );
  221. }
  222. //
  223. // Create a trigger entity for a platform.
  224. //
  225. void CPlatTrigger::SpawnInsideTrigger( CFuncPlat *pPlatform )
  226. {
  227. m_pPlatform = pPlatform;
  228. // Create trigger entity, "point" it at the owning platform, give it a touch method
  229. SetSolid( SOLID_BSP );
  230. AddSolidFlags( FSOLID_TRIGGER );
  231. SetMoveType( MOVETYPE_NONE );
  232. SetLocalOrigin( pPlatform->GetLocalOrigin() );
  233. // Establish the trigger field's size
  234. CCollisionProperty *pCollision = m_pPlatform->CollisionProp();
  235. Vector vecTMin = pCollision->OBBMins() + Vector ( 25 , 25 , 0 );
  236. Vector vecTMax = pCollision->OBBMaxs() + Vector ( 25 , 25 , 8 );
  237. vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 );
  238. if ( pCollision->OBBSize().x <= 50 )
  239. {
  240. vecTMin.x = (pCollision->OBBMins().x + pCollision->OBBMaxs().x) / 2;
  241. vecTMax.x = vecTMin.x + 1;
  242. }
  243. if ( pCollision->OBBSize().y <= 50 )
  244. {
  245. vecTMin.y = (pCollision->OBBMins().y + pCollision->OBBMaxs().y) / 2;
  246. vecTMax.y = vecTMin.y + 1;
  247. }
  248. UTIL_SetSize ( this, vecTMin, vecTMax );
  249. }
  250. //
  251. // When the platform's trigger field is touched, the platform ???
  252. //
  253. void CPlatTrigger::Touch( CBaseEntity *pOther )
  254. {
  255. // Ignore touches by non-players
  256. if ( !pOther->IsPlayer() )
  257. return;
  258. // Ignore touches by corpses
  259. if (!pOther->IsAlive())
  260. return;
  261. // Make linked platform go up/down.
  262. if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM)
  263. m_pPlatform->GoUp();
  264. else if (m_pPlatform->m_toggle_state == TS_AT_TOP)
  265. m_pPlatform->SetMoveDoneTime( 1 );// delay going down
  266. }
  267. //-----------------------------------------------------------------------------
  268. // Purpose: Used when a platform is the target of a button.
  269. // Start bringing platform down.
  270. // Input : pActivator -
  271. // pCaller -
  272. // useType -
  273. // value -
  274. //-----------------------------------------------------------------------------
  275. void CFuncPlat::InputToggle(inputdata_t &data)
  276. {
  277. if ( IsTogglePlat() )
  278. {
  279. if (m_toggle_state == TS_AT_TOP)
  280. GoDown();
  281. else if ( m_toggle_state == TS_AT_BOTTOM )
  282. GoUp();
  283. }
  284. else
  285. {
  286. SetUse( NULL );
  287. if (m_toggle_state == TS_AT_TOP)
  288. GoDown();
  289. }
  290. }
  291. void CFuncPlat::InputGoUp(inputdata_t &data)
  292. {
  293. if ( m_toggle_state == TS_AT_BOTTOM )
  294. GoUp();
  295. }
  296. void CFuncPlat::InputGoDown(inputdata_t &data)
  297. {
  298. if ( m_toggle_state == TS_AT_TOP )
  299. GoDown();
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose: Used when a platform is the target of a button.
  303. // Start bringing platform down.
  304. // Input : pActivator -
  305. // pCaller -
  306. // useType -
  307. // value -
  308. //-----------------------------------------------------------------------------
  309. void CFuncPlat::PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  310. {
  311. if ( IsTogglePlat() )
  312. {
  313. // Top is off, bottom is on
  314. bool on = (m_toggle_state == TS_AT_BOTTOM) ? true : false;
  315. if ( !ShouldToggle( useType, on ) )
  316. return;
  317. if (m_toggle_state == TS_AT_TOP)
  318. GoDown();
  319. else if ( m_toggle_state == TS_AT_BOTTOM )
  320. GoUp();
  321. }
  322. else
  323. {
  324. SetUse( NULL );
  325. if (m_toggle_state == TS_AT_TOP)
  326. GoDown();
  327. }
  328. }
  329. //
  330. // Platform is at top, now starts moving down.
  331. //
  332. void CFuncPlat::GoDown( void )
  333. {
  334. PlayMovingSound();
  335. ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP);
  336. m_toggle_state = TS_GOING_DOWN;
  337. SetMoveDone(&CFuncPlat::CallHitBottom);
  338. LinearMove(m_vecPosition2, m_flSpeed);
  339. }
  340. //
  341. // Platform has hit bottom. Stops and waits forever.
  342. //
  343. void CFuncPlat::HitBottom( void )
  344. {
  345. StopMovingSound();
  346. if ( m_NoiseArrived != NULL_STRING )
  347. {
  348. CPASAttenuationFilter filter( this );
  349. EmitSound_t ep;
  350. ep.m_nChannel = CHAN_WEAPON;
  351. ep.m_pSoundName = STRING(m_NoiseArrived);
  352. ep.m_flVolume = m_volume;
  353. ep.m_SoundLevel = SNDLVL_NORM;
  354. EmitSound( filter, entindex(), ep );
  355. }
  356. ASSERT(m_toggle_state == TS_GOING_DOWN);
  357. m_toggle_state = TS_AT_BOTTOM;
  358. }
  359. //
  360. // Platform is at bottom, now starts moving up
  361. //
  362. void CFuncPlat::GoUp( void )
  363. {
  364. PlayMovingSound();
  365. ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN);
  366. m_toggle_state = TS_GOING_UP;
  367. SetMoveDone(&CFuncPlat::CallHitTop);
  368. LinearMove(m_vecPosition1, m_flSpeed);
  369. }
  370. //
  371. // Platform has hit top. Pauses, then starts back down again.
  372. //
  373. void CFuncPlat::HitTop( void )
  374. {
  375. StopMovingSound();
  376. if ( m_NoiseArrived != NULL_STRING )
  377. {
  378. CPASAttenuationFilter filter( this );
  379. EmitSound_t ep;
  380. ep.m_nChannel = CHAN_WEAPON;
  381. ep.m_pSoundName = STRING(m_NoiseArrived);
  382. ep.m_flVolume = m_volume;
  383. ep.m_SoundLevel = SNDLVL_NORM;
  384. EmitSound( filter, entindex(), ep );
  385. }
  386. ASSERT(m_toggle_state == TS_GOING_UP);
  387. m_toggle_state = TS_AT_TOP;
  388. if ( !IsTogglePlat() )
  389. {
  390. // After a delay, the platform will automatically start going down again.
  391. SetMoveDone( &CFuncPlat::CallGoDown );
  392. SetMoveDoneTime( 3 );
  393. }
  394. }
  395. //-----------------------------------------------------------------------------
  396. // Purpose: Called when we are blocked.
  397. //-----------------------------------------------------------------------------
  398. void CFuncPlat::Blocked( CBaseEntity *pOther )
  399. {
  400. DevMsg( 2, "%s Blocked by %s\n", GetClassname(), pOther->GetClassname() );
  401. // Hurt the blocker a little
  402. pOther->TakeDamage( CTakeDamageInfo( this, this, 1, DMG_CRUSH ) );
  403. if (m_sNoise != NULL_STRING)
  404. {
  405. StopSound(entindex(), CHAN_STATIC, (char*)STRING(m_sNoise));
  406. }
  407. // Send the platform back where it came from
  408. ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN);
  409. if (m_toggle_state == TS_GOING_UP)
  410. {
  411. GoDown();
  412. }
  413. else if (m_toggle_state == TS_GOING_DOWN)
  414. {
  415. GoUp ();
  416. }
  417. }
  418. class CFuncPlatRot : public CFuncPlat
  419. {
  420. DECLARE_CLASS( CFuncPlatRot, CFuncPlat );
  421. public:
  422. void Spawn( void );
  423. void SetupRotation( void );
  424. virtual void GoUp( void );
  425. virtual void GoDown( void );
  426. virtual void HitTop( void );
  427. virtual void HitBottom( void );
  428. void RotMove( QAngle &destAngle, float time );
  429. DECLARE_DATADESC();
  430. QAngle m_end, m_start;
  431. };
  432. LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot );
  433. BEGIN_DATADESC( CFuncPlatRot )
  434. DEFINE_FIELD( m_end, FIELD_VECTOR ),
  435. DEFINE_FIELD( m_start, FIELD_VECTOR ),
  436. END_DATADESC()
  437. void CFuncPlatRot::SetupRotation( void )
  438. {
  439. if ( m_vecFinalAngle.x != 0 ) // This plat rotates too!
  440. {
  441. CBaseToggle::AxisDir();
  442. m_start = GetLocalAngles();
  443. m_end = GetLocalAngles() + m_vecMoveAng * m_vecFinalAngle.x;
  444. }
  445. else
  446. {
  447. m_start = vec3_angle;
  448. m_end = vec3_angle;
  449. }
  450. if ( GetEntityName() != NULL_STRING ) // Start at top
  451. {
  452. SetLocalAngles( m_end );
  453. }
  454. }
  455. void CFuncPlatRot::Spawn( void )
  456. {
  457. BaseClass::Spawn();
  458. SetupRotation();
  459. }
  460. void CFuncPlatRot::GoDown( void )
  461. {
  462. BaseClass::GoDown();
  463. RotMove( m_start, GetMoveDoneTime() );
  464. }
  465. //
  466. // Platform has hit bottom. Stops and waits forever.
  467. //
  468. void CFuncPlatRot::HitBottom( void )
  469. {
  470. BaseClass::HitBottom();
  471. SetLocalAngularVelocity( vec3_angle );
  472. SetLocalAngles( m_start );
  473. }
  474. //
  475. // Platform is at bottom, now starts moving up
  476. //
  477. void CFuncPlatRot::GoUp( void )
  478. {
  479. BaseClass::GoUp();
  480. RotMove( m_end, GetMoveDoneTime() );
  481. }
  482. //
  483. // Platform has hit top. Pauses, then starts back down again.
  484. //
  485. void CFuncPlatRot::HitTop( void )
  486. {
  487. BaseClass::HitTop();
  488. SetLocalAngularVelocity( vec3_angle );
  489. SetLocalAngles( m_end );
  490. }
  491. void CFuncPlatRot::RotMove( QAngle &destAngle, float time )
  492. {
  493. // set destdelta to the vector needed to move
  494. QAngle vecDestDelta = destAngle - GetLocalAngles();
  495. // Travel time is so short, we're practically there already; so make it so.
  496. if ( time >= 0.1)
  497. SetLocalAngularVelocity( vecDestDelta * (1.0 / time) );
  498. else
  499. {
  500. SetLocalAngularVelocity( vecDestDelta );
  501. SetMoveDoneTime( 1 );
  502. }
  503. }
  504. class CFuncTrain : public CBasePlatTrain
  505. {
  506. DECLARE_CLASS( CFuncTrain, CBasePlatTrain );
  507. public:
  508. void Spawn( void );
  509. void Precache( void );
  510. void Activate( void );
  511. void OnRestore( void );
  512. void SetupTarget( void );
  513. void Blocked( CBaseEntity *pOther );
  514. void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  515. void Wait( void );
  516. void Next( void );
  517. //Inputs
  518. void InputToggle(inputdata_t &data);
  519. void InputStart(inputdata_t &data);
  520. void InputStop(inputdata_t &data);
  521. void Start( void );
  522. void Stop( void );
  523. DECLARE_DATADESC();
  524. public:
  525. EHANDLE m_hCurrentTarget;
  526. bool m_activated;
  527. EHANDLE m_hEnemy;
  528. float m_flBlockDamage; // Damage to inflict when blocked.
  529. float m_flNextBlockTime;
  530. string_t m_iszLastTarget;
  531. };
  532. LINK_ENTITY_TO_CLASS( func_train, CFuncTrain );
  533. BEGIN_DATADESC( CFuncTrain )
  534. DEFINE_FIELD( m_hCurrentTarget, FIELD_EHANDLE ),
  535. DEFINE_FIELD( m_activated, FIELD_BOOLEAN ),
  536. DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ),
  537. DEFINE_FIELD( m_iszLastTarget, FIELD_STRING ),
  538. DEFINE_FIELD( m_flNextBlockTime, FIELD_TIME ),
  539. DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
  540. // Function Pointers
  541. DEFINE_FUNCTION( Wait ),
  542. DEFINE_FUNCTION( Next ),
  543. // Inputs
  544. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  545. DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStart ),
  546. DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ),
  547. END_DATADESC()
  548. //-----------------------------------------------------------------------------
  549. // Purpose: Handles a train being blocked by an entity.
  550. // Input : pOther - What was hit.
  551. //-----------------------------------------------------------------------------
  552. void CFuncTrain::Blocked( CBaseEntity *pOther )
  553. {
  554. if ( gpGlobals->curtime < m_flNextBlockTime )
  555. return;
  556. m_flNextBlockTime = gpGlobals->curtime + 0.5;
  557. //Inflict damage
  558. pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
  559. }
  560. void CFuncTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  561. {
  562. //If we've been waiting to be retriggered, move to the next destination
  563. if ( m_spawnflags & SF_TRAIN_WAIT_RETRIGGER )
  564. {
  565. // Move toward my target
  566. m_spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER;
  567. Next();
  568. }
  569. else
  570. {
  571. m_spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
  572. // Pop back to last target if it's available
  573. if ( m_hEnemy )
  574. {
  575. m_target = m_hEnemy->GetEntityName();
  576. }
  577. SetNextThink( TICK_NEVER_THINK );
  578. SetLocalVelocity( vec3_origin );
  579. if ( m_NoiseArrived != NULL_STRING )
  580. {
  581. CPASAttenuationFilter filter( this );
  582. EmitSound_t ep;
  583. ep.m_nChannel = CHAN_VOICE;
  584. ep.m_pSoundName = STRING(m_NoiseArrived);
  585. ep.m_flVolume = m_volume;
  586. ep.m_SoundLevel = SNDLVL_NORM;
  587. EmitSound( filter, entindex(), ep );
  588. }
  589. }
  590. }
  591. void CFuncTrain::Wait( void )
  592. {
  593. //If we're moving passed a path track, then trip its output
  594. variant_t emptyVariant;
  595. m_hCurrentTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 );
  596. // need pointer to LAST target.
  597. if ( m_hCurrentTarget->HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) || HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
  598. {
  599. AddSpawnFlags( SF_TRAIN_WAIT_RETRIGGER );
  600. // Clear the sound channel.
  601. StopMovingSound();
  602. if ( m_NoiseArrived != NULL_STRING )
  603. {
  604. CPASAttenuationFilter filter( this );
  605. EmitSound_t ep;
  606. ep.m_nChannel = CHAN_VOICE;
  607. ep.m_pSoundName = STRING(m_NoiseArrived);
  608. ep.m_flVolume = m_volume;
  609. ep.m_SoundLevel = SNDLVL_NORM;
  610. EmitSound( filter, entindex(), ep );
  611. }
  612. SetMoveDoneTime( -1 );
  613. return;
  614. }
  615. //NOTENOTE: -1 wait will wait forever
  616. if ( m_flWait != 0 )
  617. {
  618. SetMoveDoneTime( m_flWait );
  619. StopMovingSound();
  620. if ( m_NoiseArrived != NULL_STRING )
  621. {
  622. CPASAttenuationFilter filter( this );
  623. EmitSound_t ep;
  624. ep.m_nChannel = CHAN_VOICE;
  625. ep.m_pSoundName = STRING(m_NoiseArrived);
  626. ep.m_flVolume = m_volume;
  627. ep.m_SoundLevel = SNDLVL_NORM;
  628. EmitSound( filter, entindex(), ep );
  629. }
  630. SetMoveDone( &CFuncTrain::Next );
  631. }
  632. else
  633. {
  634. // Do it right now
  635. Next();
  636. }
  637. }
  638. //-----------------------------------------------------------------------------
  639. // Purpose: Advances the train to the next path corner on the path.
  640. //-----------------------------------------------------------------------------
  641. void CFuncTrain::Next( void )
  642. {
  643. //Find our next target
  644. CBaseEntity *pTarg = GetNextTarget();
  645. //If none, we're done
  646. if ( pTarg == NULL )
  647. {
  648. //Stop the moving sound
  649. StopMovingSound();
  650. // Play stop sound
  651. if ( m_NoiseArrived != NULL_STRING )
  652. {
  653. CPASAttenuationFilter filter( this );
  654. EmitSound_t ep;
  655. ep.m_nChannel = CHAN_VOICE;
  656. ep.m_pSoundName = STRING(m_NoiseArrived);
  657. ep.m_flVolume = m_volume;
  658. ep.m_SoundLevel = SNDLVL_NORM;
  659. EmitSound( filter, entindex(), ep );
  660. }
  661. return;
  662. }
  663. // Save last target in case we need to find it again
  664. m_iszLastTarget = m_target;
  665. m_target = pTarg->m_target;
  666. m_flWait = pTarg->GetDelay();
  667. // If our target has a speed, take it
  668. if ( m_hCurrentTarget && m_hCurrentTarget->m_flSpeed != 0 )
  669. {
  670. m_flSpeed = m_hCurrentTarget->m_flSpeed;
  671. DevMsg( 2, "Train %s speed to %4.2f\n", GetDebugName(), m_flSpeed );
  672. }
  673. // Keep track of this since path corners change our target for us
  674. m_hCurrentTarget = pTarg;
  675. m_hEnemy = pTarg;
  676. //Check for teleport
  677. if ( m_hCurrentTarget->HasSpawnFlags( SF_CORNER_TELEPORT ) )
  678. {
  679. AddEffects( EF_NOINTERP );
  680. // This is supposed to place the center of the func_train at the target's origin.
  681. // FIXME: This is totally busted! It's using the wrong space for the computation...
  682. UTIL_SetOrigin( this, pTarg->GetLocalOrigin() - CollisionProp()->OBBCenter() );
  683. // Get on with doing the next path corner.
  684. Wait();
  685. }
  686. else
  687. {
  688. // Normal linear move
  689. PlayMovingSound();
  690. RemoveEffects( EF_NOINTERP );
  691. SetMoveDone( &CFuncTrain::Wait );
  692. // This is supposed to place the center of the func_train at the target's origin.
  693. // FIXME: This is totally busted! It's using the wrong space for the computation...
  694. LinearMove ( pTarg->GetLocalOrigin() - CollisionProp()->OBBCenter(), m_flSpeed );
  695. }
  696. }
  697. //-----------------------------------------------------------------------------
  698. // Purpose: Called after all the entities spawn.
  699. //-----------------------------------------------------------------------------
  700. void CFuncTrain::Activate( void )
  701. {
  702. BaseClass::Activate();
  703. // Not yet active, so teleport to first target
  704. if ( m_activated == false )
  705. {
  706. SetupTarget();
  707. m_activated = true;
  708. if ( m_hCurrentTarget.Get() == NULL )
  709. return;
  710. // This is supposed to place the center of the func_train at the target's origin.
  711. // FIXME: This is totally busted! It's using the wrong space for the computation...
  712. UTIL_SetOrigin( this, m_hCurrentTarget->GetLocalOrigin() - CollisionProp()->OBBCenter() );
  713. if ( GetSolid() == SOLID_BSP )
  714. {
  715. VPhysicsInitShadow( false, false );
  716. }
  717. // Start immediately if not triggered
  718. if ( !GetEntityName() )
  719. {
  720. SetMoveDoneTime( 0.1 );
  721. SetMoveDone( &CFuncTrain::Next );
  722. }
  723. else
  724. {
  725. m_spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
  726. }
  727. }
  728. }
  729. //-----------------------------------------------------------------------------
  730. // Purpose:
  731. //-----------------------------------------------------------------------------
  732. void CFuncTrain::SetupTarget( void )
  733. {
  734. // Find our target whenever we don't have one (level transition)
  735. if ( !m_hCurrentTarget )
  736. {
  737. CBaseEntity *pTarg = gEntList.FindEntityByName( NULL, m_target );
  738. if ( pTarg == NULL )
  739. {
  740. Msg( "Can't find target of train %s\n", STRING(m_target) );
  741. return;
  742. }
  743. // Keep track of this since path corners change our target for us
  744. m_target = pTarg->m_target;
  745. m_hCurrentTarget = pTarg;
  746. }
  747. }
  748. //-----------------------------------------------------------------------------
  749. // Purpose:
  750. //-----------------------------------------------------------------------------
  751. void CFuncTrain::Spawn( void )
  752. {
  753. Precache();
  754. if ( m_flSpeed == 0 )
  755. {
  756. m_flSpeed = 100;
  757. }
  758. if ( !m_target )
  759. {
  760. Warning("FuncTrain '%s' has no target.\n", GetDebugName());
  761. }
  762. if ( m_flBlockDamage == 0 )
  763. {
  764. m_flBlockDamage = 2;
  765. }
  766. SetMoveType( MOVETYPE_PUSH );
  767. SetSolid( SOLID_BSP );
  768. SetModel( STRING( GetModelName() ) );
  769. if ( m_spawnflags & SF_TRACKTRAIN_PASSABLE )
  770. {
  771. AddSolidFlags( FSOLID_NOT_SOLID );
  772. }
  773. m_activated = false;
  774. if ( m_volume == 0.0f )
  775. {
  776. m_volume = 0.85f;
  777. }
  778. }
  779. void CFuncTrain::Precache( void )
  780. {
  781. BaseClass::Precache();
  782. }
  783. void CFuncTrain::OnRestore( void )
  784. {
  785. BaseClass::OnRestore();
  786. // Are we moving?
  787. if ( IsMoving() )
  788. {
  789. // Continue moving to the same target
  790. m_target = m_iszLastTarget;
  791. }
  792. SetupTarget();
  793. }
  794. void CFuncTrain::InputToggle( inputdata_t &data )
  795. {
  796. //If we've been waiting to be retriggered, move to the next destination
  797. if( HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
  798. {
  799. Start();
  800. }
  801. else
  802. {
  803. Stop();
  804. }
  805. }
  806. void CFuncTrain::InputStart( inputdata_t &data )
  807. {
  808. Start();
  809. }
  810. void CFuncTrain::InputStop( inputdata_t &data )
  811. {
  812. Stop();
  813. }
  814. void CFuncTrain::Start( void )
  815. {
  816. //start moving
  817. if( HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
  818. {
  819. // Move toward my target
  820. RemoveSpawnFlags( SF_TRAIN_WAIT_RETRIGGER );
  821. Next();
  822. }
  823. }
  824. void CFuncTrain::Stop( void )
  825. {
  826. //stop moving
  827. if( !HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
  828. {
  829. AddSpawnFlags( SF_TRAIN_WAIT_RETRIGGER );
  830. // Pop back to last target if it's available
  831. if ( m_hEnemy )
  832. {
  833. m_target = m_hEnemy->GetEntityName();
  834. }
  835. SetNextThink( TICK_NEVER_THINK );
  836. SetAbsVelocity( vec3_origin );
  837. if ( m_NoiseArrived != NULL_STRING )
  838. {
  839. CPASAttenuationFilter filter( this );
  840. EmitSound_t ep;
  841. ep.m_nChannel = CHAN_VOICE;
  842. ep.m_pSoundName = STRING(m_NoiseArrived);
  843. ep.m_flVolume = m_volume;
  844. ep.m_SoundLevel = SNDLVL_NORM;
  845. EmitSound( filter, entindex(), ep );
  846. }
  847. //Do not teleport to our final move destination
  848. SetMoveDone( NULL );
  849. SetMoveDoneTime( -1 );
  850. }
  851. }
  852. BEGIN_DATADESC( CFuncTrackTrain )
  853. DEFINE_KEYFIELD( m_length, FIELD_FLOAT, "wheels" ),
  854. DEFINE_KEYFIELD( m_height, FIELD_FLOAT, "height" ),
  855. DEFINE_KEYFIELD( m_maxSpeed, FIELD_FLOAT, "startspeed" ),
  856. DEFINE_KEYFIELD( m_flBank, FIELD_FLOAT, "bank" ),
  857. DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
  858. DEFINE_KEYFIELD( m_iszSoundMove, FIELD_SOUNDNAME, "MoveSound" ),
  859. DEFINE_KEYFIELD( m_iszSoundMovePing, FIELD_SOUNDNAME, "MovePingSound" ),
  860. DEFINE_KEYFIELD( m_iszSoundStart, FIELD_SOUNDNAME, "StartSound" ),
  861. DEFINE_KEYFIELD( m_iszSoundStop, FIELD_SOUNDNAME, "StopSound" ),
  862. DEFINE_KEYFIELD( m_nMoveSoundMinPitch, FIELD_INTEGER, "MoveSoundMinPitch" ),
  863. DEFINE_KEYFIELD( m_nMoveSoundMaxPitch, FIELD_INTEGER, "MoveSoundMaxPitch" ),
  864. DEFINE_KEYFIELD( m_flMoveSoundMinTime, FIELD_FLOAT, "MoveSoundMinTime" ),
  865. DEFINE_KEYFIELD( m_flMoveSoundMaxTime, FIELD_FLOAT, "MoveSoundMaxTime" ),
  866. DEFINE_FIELD( m_flNextMoveSoundTime, FIELD_TIME ),
  867. DEFINE_KEYFIELD( m_eVelocityType, FIELD_INTEGER, "velocitytype" ),
  868. DEFINE_KEYFIELD( m_eOrientationType, FIELD_INTEGER, "orientationtype" ),
  869. DEFINE_FIELD( m_ppath, FIELD_CLASSPTR ),
  870. DEFINE_FIELD( m_dir, FIELD_FLOAT ),
  871. DEFINE_FIELD( m_controlMins, FIELD_VECTOR ),
  872. DEFINE_FIELD( m_controlMaxs, FIELD_VECTOR ),
  873. DEFINE_FIELD( m_flVolume, FIELD_FLOAT ),
  874. DEFINE_FIELD( m_oldSpeed, FIELD_FLOAT ),
  875. DEFINE_FIELD( m_strPathTarget, FIELD_STRING ),
  876. //DEFINE_FIELD( m_lastBlockPos, FIELD_POSITION_VECTOR ), // temp values for blocking, don't save
  877. //DEFINE_FIELD( m_lastBlockTick, FIELD_INTEGER ),
  878. DEFINE_FIELD( m_bSoundPlaying, FIELD_BOOLEAN ),
  879. DEFINE_KEYFIELD( m_bManualSpeedChanges, FIELD_BOOLEAN, "ManualSpeedChanges" ),
  880. DEFINE_KEYFIELD( m_flAccelSpeed, FIELD_FLOAT, "ManualAccelSpeed" ),
  881. DEFINE_KEYFIELD( m_flDecelSpeed, FIELD_FLOAT, "ManualDecelSpeed" ),
  882. // Inputs
  883. DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ),
  884. DEFINE_INPUTFUNC( FIELD_VOID, "StartForward", InputStartForward ),
  885. DEFINE_INPUTFUNC( FIELD_VOID, "StartBackward", InputStartBackward ),
  886. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  887. DEFINE_INPUTFUNC( FIELD_VOID, "Resume", InputResume ),
  888. DEFINE_INPUTFUNC( FIELD_VOID, "Reverse", InputReverse ),
  889. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ),
  890. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDir", InputSetSpeedDir ),
  891. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedReal", InputSetSpeedReal ),
  892. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxSpeed", InputSetMaxSpeed ),
  893. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDirAccel", InputSetSpeedDirAccel ),
  894. DEFINE_INPUTFUNC( FIELD_STRING, "MoveToPathNode", InputMoveToPathNode ),
  895. DEFINE_INPUTFUNC( FIELD_STRING, "TeleportToPathNode", InputTeleportToPathNode ),
  896. DEFINE_INPUTFUNC( FIELD_VOID, "LockOrientation", InputLockOrientation ),
  897. DEFINE_INPUTFUNC( FIELD_VOID, "UnlockOrientation", InputUnlockOrientation ),
  898. // Outputs
  899. DEFINE_OUTPUT( m_OnStart, "OnStart" ),
  900. DEFINE_OUTPUT( m_OnNext, "OnNextPoint" ),
  901. DEFINE_OUTPUT( m_OnArrivedAtDestinationNode, "OnArrivedAtDestinationNode" ),
  902. // Function Pointers
  903. DEFINE_FUNCTION( Next ),
  904. DEFINE_FUNCTION( Find ),
  905. DEFINE_FUNCTION( NearestPath ),
  906. DEFINE_FUNCTION( DeadEnd ),
  907. END_DATADESC()
  908. BEGIN_ENT_SCRIPTDESC( CFuncTrackTrain, CBaseEntity, "func_train" )
  909. DEFINE_SCRIPTFUNC_NAMED( ScriptGetFuturePosition, "GetFuturePosition", "Get a position on the track x seconds in the future" )
  910. END_SCRIPTDESC()
  911. LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain );
  912. //-----------------------------------------------------------------------------
  913. // Datatable
  914. //-----------------------------------------------------------------------------
  915. IMPLEMENT_SERVERCLASS_ST( CFuncTrackTrain, DT_FuncTrackTrain )
  916. END_SEND_TABLE()
  917. //-----------------------------------------------------------------------------
  918. // Constructor
  919. //-----------------------------------------------------------------------------
  920. CFuncTrackTrain::CFuncTrackTrain()
  921. {
  922. #ifdef _DEBUG
  923. m_controlMins.Init();
  924. m_controlMaxs.Init();
  925. #endif
  926. // These defaults match old func_tracktrains. Changing these defaults would
  927. // require a vmf_tweak of older content to keep it from breaking.
  928. m_eOrientationType = TrainOrientation_AtPathTracks;
  929. m_eVelocityType = TrainVelocity_Instantaneous;
  930. m_lastBlockPos.Init();
  931. m_lastBlockTick = gpGlobals->tickcount;
  932. }
  933. //-----------------------------------------------------------------------------
  934. // Purpose:
  935. //-----------------------------------------------------------------------------
  936. int CFuncTrackTrain::DrawDebugTextOverlays( void )
  937. {
  938. int nOffset = BaseClass::DrawDebugTextOverlays();
  939. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  940. {
  941. char tempstr[512];
  942. Q_snprintf( tempstr,sizeof(tempstr), "angles: %g %g %g", (double)GetLocalAngles()[PITCH], (double)GetLocalAngles()[YAW], (double)GetLocalAngles()[ROLL] );
  943. EntityText( nOffset, tempstr, 0 );
  944. nOffset++;
  945. float flCurSpeed = GetLocalVelocity().Length();
  946. Q_snprintf( tempstr,sizeof(tempstr), "current speed (goal): %g (%g)", (double)flCurSpeed, (double)m_flSpeed );
  947. EntityText( nOffset, tempstr, 0 );
  948. nOffset++;
  949. Q_snprintf( tempstr,sizeof(tempstr), "max speed: %g", (double)m_maxSpeed );
  950. EntityText( nOffset, tempstr, 0 );
  951. nOffset++;
  952. }
  953. return nOffset;
  954. }
  955. void CFuncTrackTrain::DrawDebugGeometryOverlays()
  956. {
  957. BaseClass::DrawDebugGeometryOverlays();
  958. if (m_debugOverlays & OVERLAY_BBOX_BIT)
  959. {
  960. NDebugOverlay::Box( GetAbsOrigin(), -Vector(4,4,4),Vector(4,4,4), 255, 0, 255, 0, 0);
  961. Vector out;
  962. VectorTransform( Vector(m_length,0,0), EntityToWorldTransform(), out );
  963. NDebugOverlay::Box( out, -Vector(4,4,4),Vector(4,4,4), 255, 0, 255, 0, 0);
  964. }
  965. }
  966. //-----------------------------------------------------------------------------
  967. // Purpose:
  968. //-----------------------------------------------------------------------------
  969. bool CFuncTrackTrain::KeyValue( const char *szKeyName, const char *szValue )
  970. {
  971. if (FStrEq(szKeyName, "volume"))
  972. {
  973. m_flVolume = (float) (atoi(szValue));
  974. m_flVolume *= 0.1f;
  975. }
  976. else
  977. {
  978. return BaseClass::KeyValue( szKeyName, szValue );
  979. }
  980. return true;
  981. }
  982. //-----------------------------------------------------------------------------
  983. // Purpose: Input handler that stops the train.
  984. //-----------------------------------------------------------------------------
  985. void CFuncTrackTrain::InputStop( inputdata_t &inputdata )
  986. {
  987. Stop();
  988. }
  989. //------------------------------------------------------------------------------
  990. // Purpose: Input handler that starts the train moving.
  991. //------------------------------------------------------------------------------
  992. void CFuncTrackTrain::InputResume( inputdata_t &inputdata )
  993. {
  994. m_flSpeed = m_oldSpeed;
  995. Start();
  996. }
  997. //------------------------------------------------------------------------------
  998. // Purpose: Input handler that reverses the trains current direction of motion.
  999. //------------------------------------------------------------------------------
  1000. void CFuncTrackTrain::InputReverse( inputdata_t &inputdata )
  1001. {
  1002. SetDirForward( !IsDirForward() );
  1003. SetSpeed( m_flSpeed );
  1004. }
  1005. //-----------------------------------------------------------------------------
  1006. // Purpose: Returns whether we are travelling forward along our path.
  1007. //-----------------------------------------------------------------------------
  1008. bool CFuncTrackTrain::IsDirForward()
  1009. {
  1010. return ( m_dir == 1 );
  1011. }
  1012. //-----------------------------------------------------------------------------
  1013. // Purpose: Sets whether we go forward or backward along our path.
  1014. //-----------------------------------------------------------------------------
  1015. void CFuncTrackTrain::SetDirForward( bool bForward )
  1016. {
  1017. if ( bForward && ( m_dir != 1 ) )
  1018. {
  1019. // Reverse direction.
  1020. if ( m_ppath && m_ppath->GetPrevious() )
  1021. {
  1022. m_ppath = m_ppath->GetPrevious();
  1023. }
  1024. m_dir = 1;
  1025. }
  1026. else if ( !bForward && ( m_dir != -1 ) )
  1027. {
  1028. // Reverse direction.
  1029. if ( m_ppath && m_ppath->GetNext() )
  1030. {
  1031. m_ppath = m_ppath->GetNext();
  1032. }
  1033. m_dir = -1;
  1034. }
  1035. }
  1036. //------------------------------------------------------------------------------
  1037. // Purpose: Input handler that starts the train moving.
  1038. //------------------------------------------------------------------------------
  1039. void CFuncTrackTrain::InputStartForward( inputdata_t &inputdata )
  1040. {
  1041. SetDirForward( true );
  1042. SetSpeed( m_maxSpeed );
  1043. }
  1044. //------------------------------------------------------------------------------
  1045. // Purpose: Input handler that starts the train moving.
  1046. //------------------------------------------------------------------------------
  1047. void CFuncTrackTrain::InputStartBackward( inputdata_t &inputdata )
  1048. {
  1049. SetDirForward( false );
  1050. SetSpeed( m_maxSpeed );
  1051. }
  1052. //------------------------------------------------------------------------------
  1053. // Purpose: Starts the train moving.
  1054. //------------------------------------------------------------------------------
  1055. void CFuncTrackTrain::Start( void )
  1056. {
  1057. m_OnStart.FireOutput(this,this);
  1058. Next();
  1059. }
  1060. //-----------------------------------------------------------------------------
  1061. // Purpose: Toggles the train between moving and not moving.
  1062. //-----------------------------------------------------------------------------
  1063. void CFuncTrackTrain::InputToggle( inputdata_t &inputdata )
  1064. {
  1065. if ( m_flSpeed == 0 )
  1066. {
  1067. SetSpeed( m_maxSpeed );
  1068. }
  1069. else
  1070. {
  1071. SetSpeed( 0 );
  1072. }
  1073. }
  1074. //-----------------------------------------------------------------------------
  1075. // Purpose: Handles player use so players can control the speed of the train.
  1076. //-----------------------------------------------------------------------------
  1077. void CFuncTrackTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  1078. {
  1079. // player +USE
  1080. if ( useType == USE_SET )
  1081. {
  1082. float delta = value;
  1083. delta = ((int)(m_flSpeed * 4) / (int)m_maxSpeed)*0.25 + 0.25 * delta;
  1084. if ( delta > 1 )
  1085. delta = 1;
  1086. else if ( delta < -0.25 )
  1087. delta = -0.25;
  1088. if ( m_spawnflags & SF_TRACKTRAIN_FORWARDONLY )
  1089. {
  1090. if ( delta < 0 )
  1091. delta = 0;
  1092. }
  1093. SetDirForward( delta >= 0 );
  1094. delta = fabs(delta);
  1095. SetSpeed( m_maxSpeed * delta );
  1096. }
  1097. }
  1098. //-----------------------------------------------------------------------------
  1099. // Purpose: Input handler that sets the speed of the train.
  1100. // Input : Float speed from 0 to max speed, in units per second.
  1101. //-----------------------------------------------------------------------------
  1102. void CFuncTrackTrain::InputSetSpeedReal( inputdata_t &inputdata )
  1103. {
  1104. SetSpeed( clamp( inputdata.value.Float(), 0, m_maxSpeed ) );
  1105. }
  1106. //-----------------------------------------------------------------------------
  1107. // Purpose: Input handler that sets the speed of the train.
  1108. // Input : Float speed scale from 0 to 1.
  1109. //-----------------------------------------------------------------------------
  1110. void CFuncTrackTrain::InputSetSpeed( inputdata_t &inputdata )
  1111. {
  1112. float flScale = clamp( inputdata.value.Float(), 0, 1 );
  1113. SetSpeed( m_maxSpeed * flScale );
  1114. }
  1115. //-----------------------------------------------------------------------------
  1116. // Purpose: Input handler that sets the max speed of the train.
  1117. // Input : Float speed
  1118. //-----------------------------------------------------------------------------
  1119. void CFuncTrackTrain::InputSetMaxSpeed( inputdata_t &inputdata )
  1120. {
  1121. m_maxSpeed = inputdata.value.Float();
  1122. }
  1123. //-----------------------------------------------------------------------------
  1124. // Purpose: Input handler that sets the speed of the train and the direction
  1125. // based on the sign of the speed.
  1126. // Input : Float speed scale from -1 to 1. Negatives values indicate a reversed
  1127. // direction.
  1128. //-----------------------------------------------------------------------------
  1129. void CFuncTrackTrain::InputSetSpeedDir( inputdata_t &inputdata )
  1130. {
  1131. float newSpeed = inputdata.value.Float();
  1132. SetDirForward( newSpeed >= 0 );
  1133. newSpeed = fabs(newSpeed);
  1134. float flScale = clamp( newSpeed, 0, 1 );
  1135. SetSpeed( m_maxSpeed * flScale );
  1136. }
  1137. //-----------------------------------------------------------------------------
  1138. // Purpose: Input handler that sets the speed of the train and the direction
  1139. // based on the sign of the speed, and accels/decels to that speed
  1140. // Input : Float speed scale from -1 to 1. Negatives values indicate a reversed
  1141. // direction.
  1142. //-----------------------------------------------------------------------------
  1143. void CFuncTrackTrain::InputSetSpeedDirAccel( inputdata_t &inputdata )
  1144. {
  1145. float newSpeed = inputdata.value.Float();
  1146. SetDirForward( newSpeed >= 0 );
  1147. newSpeed = fabs(newSpeed);
  1148. float flScale = clamp( newSpeed, 0, 1 );
  1149. SetSpeed( m_maxSpeed * flScale, true );
  1150. }
  1151. //-----------------------------------------------------------------------------
  1152. // Purpose: Input handler that sets a target path node to move to.
  1153. // Input : String name of the destination node
  1154. //-----------------------------------------------------------------------------
  1155. void CFuncTrackTrain::InputMoveToPathNode( inputdata_t &inputdata )
  1156. {
  1157. m_strPathTarget = MAKE_STRING( inputdata.value.String() );
  1158. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
  1159. CPathTrack *pTrack, *pNext;
  1160. pTrack = m_ppath;
  1161. const int MAX_SEARCH_LENGTH = 1000;
  1162. int searchesLeft = MAX_SEARCH_LENGTH;
  1163. if (pTrack && pEntity)
  1164. {
  1165. float flDesiredSpeed = pTrack->m_flSpeed;
  1166. if( pTrack->m_flSpeed == 0 )
  1167. {
  1168. flDesiredSpeed = m_maxSpeed;
  1169. }
  1170. // if our current path is what we want then we can short circut. Still move forward - we will stop when we pass the track.
  1171. if (pEntity == pTrack)
  1172. {
  1173. if (IsDirForward())
  1174. {
  1175. if (pTrack->GetNext())
  1176. {
  1177. SetDirForward( false );
  1178. SetSpeed( flDesiredSpeed );
  1179. return;
  1180. }
  1181. }
  1182. else
  1183. {
  1184. if (pTrack->GetPrevious())
  1185. {
  1186. SetDirForward( true );
  1187. SetSpeed( flDesiredSpeed );
  1188. return;
  1189. }
  1190. }
  1191. Stop();
  1192. return;
  1193. }
  1194. do // check forward first
  1195. {
  1196. searchesLeft--;
  1197. pNext = pTrack->GetNext();
  1198. if ( pNext )
  1199. pTrack = pNext;
  1200. } while ( pNext && pEntity != pNext && searchesLeft); // quit if the next node is invalid - or our target
  1201. if( pNext == pEntity )
  1202. {
  1203. SetDirForward( true );
  1204. SetSpeed( flDesiredSpeed );
  1205. return;
  1206. }
  1207. searchesLeft = MAX_SEARCH_LENGTH;
  1208. pTrack = m_ppath;
  1209. // now reverse
  1210. do
  1211. {
  1212. searchesLeft--;
  1213. pNext = pTrack->GetPrevious();
  1214. if ( pNext )
  1215. pTrack = pNext;
  1216. } while ( pNext && pEntity != pNext && searchesLeft); // quit if the next node is invalid - or our target
  1217. if( pNext == pEntity )
  1218. {
  1219. SetDirForward( false );
  1220. SetSpeed( flDesiredSpeed );
  1221. return;
  1222. }
  1223. }
  1224. }
  1225. //-----------------------------------------------------------------------------
  1226. // Purpose: Input handler that teleports the train to the given node
  1227. // Input : String name of the destination node
  1228. //-----------------------------------------------------------------------------
  1229. void CFuncTrackTrain::InputTeleportToPathNode( inputdata_t &inputdata )
  1230. {
  1231. m_strPathTarget = MAKE_STRING( inputdata.value.String() );
  1232. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
  1233. if (pEntity)
  1234. {
  1235. m_ppath = (CPathTrack *)pEntity;
  1236. ArriveAtNode( m_ppath );
  1237. TeleportToPathTrack( m_ppath );
  1238. }
  1239. }
  1240. //-----------------------------------------------------------------------------
  1241. // Purpose: Lock the orientation of the train
  1242. //-----------------------------------------------------------------------------
  1243. void CFuncTrackTrain::InputLockOrientation( inputdata_t &inputdata )
  1244. {
  1245. AddSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION );
  1246. SetLocalAngularVelocity( vec3_angle );
  1247. }
  1248. //-----------------------------------------------------------------------------
  1249. // Purpose: Lock the orientation of the train
  1250. //-----------------------------------------------------------------------------
  1251. void CFuncTrackTrain::InputUnlockOrientation( inputdata_t &inputdata )
  1252. {
  1253. RemoveSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION );
  1254. }
  1255. //-----------------------------------------------------------------------------
  1256. // Purpose: Sets the speed of the train to the given value in units per second.
  1257. //-----------------------------------------------------------------------------
  1258. void CFuncTrackTrain::SetSpeed( float flSpeed, bool bAccel /*= false */ )
  1259. {
  1260. m_bAccelToSpeed = bAccel;
  1261. float flOldSpeed = m_flSpeed;
  1262. if ( m_bAccelToSpeed )
  1263. {
  1264. m_flDesiredSpeed = fabs( flSpeed ) * m_dir;
  1265. m_flSpeedChangeTime = gpGlobals->curtime;
  1266. if ( m_flSpeed == 0 && abs(m_flDesiredSpeed) > 0 )
  1267. {
  1268. m_flSpeed = 0.1; // little push to get us going
  1269. }
  1270. Start();
  1271. return;
  1272. }
  1273. m_flSpeed = fabs( flSpeed ) * m_dir;
  1274. if ( m_flSpeed != flOldSpeed)
  1275. {
  1276. // Changing speed.
  1277. if ( m_flSpeed != 0 )
  1278. {
  1279. if ( flOldSpeed == 0 )
  1280. {
  1281. // Starting to move.
  1282. Start();
  1283. }
  1284. else
  1285. {
  1286. // Continuing to move.
  1287. Next();
  1288. }
  1289. }
  1290. else
  1291. {
  1292. // Stopping.
  1293. Stop();
  1294. }
  1295. }
  1296. DevMsg( 2, "TRAIN(%s), speed to %.2f\n", GetDebugName(), m_flSpeed );
  1297. }
  1298. //-----------------------------------------------------------------------------
  1299. // Purpose: Stops the train.
  1300. //-----------------------------------------------------------------------------
  1301. void CFuncTrackTrain::Stop( void )
  1302. {
  1303. SetLocalVelocity( vec3_origin );
  1304. SetLocalAngularVelocity( vec3_angle );
  1305. m_oldSpeed = m_flSpeed;
  1306. m_flSpeed = 0;
  1307. SoundStop();
  1308. SetThink(NULL);
  1309. }
  1310. static CBaseEntity *FindPhysicsBlockerForHierarchy( CBaseEntity *pParentEntity )
  1311. {
  1312. CUtlVector<CBaseEntity *> list;
  1313. GetAllInHierarchy( pParentEntity, list );
  1314. CBaseEntity *pPhysicsBlocker = NULL;
  1315. float maxForce = 0;
  1316. for ( int i = 0; i < list.Count(); i++ )
  1317. {
  1318. IPhysicsObject *pPhysics = list[i]->VPhysicsGetObject();
  1319. if ( pPhysics )
  1320. {
  1321. IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
  1322. while ( pSnapshot->IsValid() )
  1323. {
  1324. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  1325. CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
  1326. if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  1327. {
  1328. Vector normal;
  1329. pSnapshot->GetSurfaceNormal(normal);
  1330. float dot = DotProduct( pParentEntity->GetAbsVelocity(), pSnapshot->GetNormalForce() * normal );
  1331. if ( !pPhysicsBlocker || dot > maxForce )
  1332. {
  1333. pPhysicsBlocker = pOtherEntity;
  1334. maxForce = dot;
  1335. }
  1336. }
  1337. pSnapshot->NextFrictionData();
  1338. }
  1339. pPhysics->DestroyFrictionSnapshot( pSnapshot );
  1340. }
  1341. }
  1342. return pPhysicsBlocker;
  1343. }
  1344. //-----------------------------------------------------------------------------
  1345. // Purpose: Called when we are blocked by another entity.
  1346. // Input : pOther -
  1347. //-----------------------------------------------------------------------------
  1348. void CFuncTrackTrain::Blocked( CBaseEntity *pOther )
  1349. {
  1350. // Blocker is on-ground on the train
  1351. if ( ( pOther->GetFlags() & FL_ONGROUND ) && pOther->GetGroundEntity() == this )
  1352. {
  1353. DevMsg( 1, "TRAIN(%s): Blocked by %s\n", GetDebugName(), pOther->GetClassname() );
  1354. float deltaSpeed = fabs(m_flSpeed);
  1355. if ( deltaSpeed > 50 )
  1356. deltaSpeed = 50;
  1357. Vector vecNewVelocity;
  1358. pOther->GetVelocity( &vecNewVelocity );
  1359. if ( !vecNewVelocity.z )
  1360. {
  1361. pOther->ApplyAbsVelocityImpulse( Vector(0,0,deltaSpeed) );
  1362. }
  1363. return;
  1364. }
  1365. else
  1366. {
  1367. Vector vecNewVelocity;
  1368. vecNewVelocity = pOther->GetAbsOrigin() - GetAbsOrigin();
  1369. VectorNormalize(vecNewVelocity);
  1370. vecNewVelocity *= m_flBlockDamage;
  1371. pOther->SetAbsVelocity( vecNewVelocity );
  1372. }
  1373. if ( HasSpawnFlags(SF_TRACKTRAIN_UNBLOCKABLE_BY_PLAYER) )
  1374. {
  1375. CBaseEntity *pPhysicsBlocker = FindPhysicsBlockerForHierarchy(this);
  1376. if ( pPhysicsBlocker )
  1377. {
  1378. // This code keeps track of how long this train has been blocked
  1379. // The heuristic here is to keep instantaneous blocks from invoking the somewhat
  1380. // heavy-handed solver (which will disable collisions until we're clear) in cases
  1381. // where physics can solve it easily enough.
  1382. int ticksBlocked = gpGlobals->tickcount - m_lastBlockTick;
  1383. float dist = 0.0f;
  1384. // wait at least 10 ticks and make sure the train isn't actually moving before really blocking
  1385. const int MIN_BLOCKED_TICKS = 10;
  1386. if ( ticksBlocked > MIN_BLOCKED_TICKS )
  1387. {
  1388. dist = (GetAbsOrigin() - m_lastBlockPos).Length();
  1389. // must have moved at least 10% of normal velocity over the blocking interval, or we're being blocked
  1390. float minLength = GetAbsVelocity().Length() * TICK_INTERVAL * MIN_BLOCKED_TICKS * 0.10f;
  1391. if ( dist < minLength )
  1392. {
  1393. // been stuck for more than one tick without moving much?
  1394. // yes, disable collisions with the physics object most likely to be blocking us
  1395. EntityPhysics_CreateSolver( this, pPhysicsBlocker, true, 4.0f );
  1396. }
  1397. }
  1398. // first time blocking or moved too far since last block, reset
  1399. if ( dist > 1.0f || m_lastBlockTick < 0 )
  1400. {
  1401. m_lastBlockPos = GetAbsOrigin();
  1402. m_lastBlockTick = gpGlobals->tickcount;
  1403. }
  1404. }
  1405. // unblockable shouldn't damage the player in this case
  1406. if ( pOther->IsPlayer() )
  1407. return;
  1408. }
  1409. DevWarning( 2, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", GetDebugName(), pOther->GetClassname(), m_flBlockDamage );
  1410. if ( m_flBlockDamage <= 0 )
  1411. return;
  1412. // we can't hurt this thing, so we're not concerned with it
  1413. pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
  1414. }
  1415. extern void FixupAngles( QAngle &v );
  1416. #define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation
  1417. //-----------------------------------------------------------------------------
  1418. // Purpose:
  1419. //-----------------------------------------------------------------------------
  1420. void CFuncTrackTrain::SoundStop( void )
  1421. {
  1422. // if sound playing, stop it
  1423. if ( m_bSoundPlaying )
  1424. {
  1425. if ( m_iszSoundMove != NULL_STRING )
  1426. {
  1427. StopSound( entindex(), CHAN_STATIC, STRING( m_iszSoundMove ) );
  1428. }
  1429. if ( m_iszSoundStart != NULL_STRING )
  1430. {
  1431. StopSound( entindex(), CHAN_STATIC, STRING( m_iszSoundStart ) );
  1432. }
  1433. if ( m_iszSoundStop != NULL_STRING )
  1434. {
  1435. CPASAttenuationFilter filter( this );
  1436. EmitSound_t ep;
  1437. ep.m_nChannel = CHAN_ITEM;
  1438. ep.m_pSoundName = STRING(m_iszSoundStop);
  1439. ep.m_flVolume = m_flVolume;
  1440. ep.m_SoundLevel = SNDLVL_NORM;
  1441. EmitSound( filter, entindex(), ep );
  1442. }
  1443. }
  1444. m_bSoundPlaying = false;
  1445. }
  1446. //-----------------------------------------------------------------------------
  1447. // Purpose: Update pitch based on speed, start sound if not playing.
  1448. // NOTE: when train goes through transition, m_bSoundPlaying should become
  1449. // false, which will cause the looped sound to restart.
  1450. //-----------------------------------------------------------------------------
  1451. void CFuncTrackTrain::SoundUpdate( void )
  1452. {
  1453. if ( ( !m_iszSoundMove ) && ( !m_iszSoundStart ) && ( !m_iszSoundMovePing ))
  1454. {
  1455. return;
  1456. }
  1457. // In multiplayer, only update the sound once a second
  1458. if ( g_pGameRules->IsMultiplayer() && m_bSoundPlaying )
  1459. {
  1460. if ( m_flNextMPSoundTime > gpGlobals->curtime )
  1461. return;
  1462. m_flNextMPSoundTime = gpGlobals->curtime + 1.0;
  1463. }
  1464. float flSpeedRatio = 0;
  1465. if ( HasSpawnFlags( SF_TRACKTRAIN_USE_MAXSPEED_FOR_PITCH ) )
  1466. {
  1467. flSpeedRatio = clamp( fabs( m_flSpeed ) / m_maxSpeed, 0, 1 );
  1468. }
  1469. else
  1470. {
  1471. flSpeedRatio = clamp( fabs( m_flSpeed ) / TRAIN_MAXSPEED, 0, 1 );
  1472. }
  1473. float flpitch = RemapVal( flSpeedRatio, 0, 1, m_nMoveSoundMinPitch, m_nMoveSoundMaxPitch );
  1474. CPASAttenuationFilter filter( this );
  1475. CPASAttenuationFilter filterReliable( this );
  1476. filterReliable.MakeReliable();
  1477. Vector vecWorldSpaceCenter = WorldSpaceCenter();
  1478. if (!m_bSoundPlaying)
  1479. {
  1480. if ( m_iszSoundStart != NULL_STRING )
  1481. {
  1482. EmitSound_t ep;
  1483. ep.m_nChannel = CHAN_ITEM;
  1484. ep.m_pSoundName = STRING(m_iszSoundStart);
  1485. ep.m_flVolume = m_flVolume;
  1486. ep.m_SoundLevel = SNDLVL_NORM;
  1487. ep.m_pOrigin = &vecWorldSpaceCenter;
  1488. EmitSound( filter, entindex(), ep );
  1489. }
  1490. if ( m_iszSoundMove != NULL_STRING )
  1491. {
  1492. EmitSound_t ep;
  1493. ep.m_nChannel = CHAN_STATIC;
  1494. ep.m_pSoundName = STRING(m_iszSoundMove);
  1495. ep.m_flVolume = m_flVolume;
  1496. ep.m_SoundLevel = SNDLVL_NORM;
  1497. ep.m_nPitch = (int)flpitch;
  1498. ep.m_pOrigin = &vecWorldSpaceCenter;
  1499. EmitSound( filterReliable, entindex(), ep );
  1500. }
  1501. // We've just started moving. Delay the next move ping sound.
  1502. m_flNextMoveSoundTime = gpGlobals->curtime + RemapVal( flSpeedRatio, 0, 1, m_flMoveSoundMaxTime, m_flMoveSoundMinTime );
  1503. m_bSoundPlaying = true;
  1504. }
  1505. else
  1506. {
  1507. if ( m_iszSoundMove != NULL_STRING )
  1508. {
  1509. // update pitch
  1510. EmitSound_t ep;
  1511. ep.m_nChannel = CHAN_STATIC;
  1512. ep.m_pSoundName = STRING(m_iszSoundMove);
  1513. ep.m_flVolume = m_flVolume;
  1514. ep.m_SoundLevel = SNDLVL_NORM;
  1515. ep.m_nPitch = (int)flpitch;
  1516. ep.m_nFlags = SND_CHANGE_PITCH;
  1517. ep.m_pOrigin = &vecWorldSpaceCenter;
  1518. // In multiplayer, don't make this reliable
  1519. if ( g_pGameRules->IsMultiplayer() )
  1520. {
  1521. EmitSound( filter, entindex(), ep );
  1522. }
  1523. else
  1524. {
  1525. EmitSound( filterReliable, entindex(), ep );
  1526. }
  1527. }
  1528. if ( ( m_iszSoundMovePing != NULL_STRING ) && ( gpGlobals->curtime > m_flNextMoveSoundTime ) )
  1529. {
  1530. EmitSound(STRING(m_iszSoundMovePing));
  1531. m_flNextMoveSoundTime = gpGlobals->curtime + RemapVal( flSpeedRatio, 0, 1, m_flMoveSoundMaxTime, m_flMoveSoundMinTime );
  1532. }
  1533. }
  1534. }
  1535. //-----------------------------------------------------------------------------
  1536. // Purpose:
  1537. // Input : *pNode -
  1538. //-----------------------------------------------------------------------------
  1539. void CFuncTrackTrain::ArriveAtNode( CPathTrack *pNode )
  1540. {
  1541. // BUGBUG: This is wrong. We need to fire all targets between the one we've passed and the one
  1542. // we've switched to.
  1543. FirePassInputs( pNode, pNode->GetNext(), true );
  1544. //
  1545. // Disable train controls if this path track says to do so.
  1546. //
  1547. if ( pNode->HasSpawnFlags( SF_PATH_DISABLE_TRAIN ) )
  1548. {
  1549. m_spawnflags |= SF_TRACKTRAIN_NOCONTROL;
  1550. }
  1551. // Do we have a node move target?
  1552. if (m_strPathTarget != NULL_STRING)
  1553. {
  1554. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, STRING(m_strPathTarget) );
  1555. if (pEntity && pNode == pEntity)
  1556. {
  1557. m_OnArrivedAtDestinationNode.FireOutput(pNode, this);
  1558. m_strPathTarget = NULL_STRING;
  1559. m_oldSpeed = m_flSpeed;
  1560. m_flSpeed = 0;
  1561. return;
  1562. }
  1563. }
  1564. //
  1565. // Don't override the train speed if it's under user control.
  1566. //
  1567. if ( m_spawnflags & SF_TRACKTRAIN_NOCONTROL )
  1568. {
  1569. //
  1570. // Don't copy speed from path track if it is 0 (uninitialized).
  1571. //
  1572. if ( pNode->m_flSpeed != 0 )
  1573. {
  1574. SetSpeed( pNode->m_flSpeed );
  1575. DevMsg( 2, "TrackTrain %s arrived at %s, speed to %4.2f\n", GetDebugName(), pNode->GetDebugName(), pNode->m_flSpeed );
  1576. }
  1577. }
  1578. }
  1579. //-----------------------------------------------------------------------------
  1580. // Purpose: Controls how the train accelerates as it moves along the path.
  1581. //-----------------------------------------------------------------------------
  1582. TrainVelocityType_t CFuncTrackTrain::GetTrainVelocityType()
  1583. {
  1584. return m_eVelocityType;
  1585. }
  1586. //-----------------------------------------------------------------------------
  1587. // Purpose:
  1588. // Input : *pnext -
  1589. //-----------------------------------------------------------------------------
  1590. void CFuncTrackTrain::UpdateTrainVelocity( CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
  1591. {
  1592. switch ( GetTrainVelocityType() )
  1593. {
  1594. case TrainVelocity_Instantaneous:
  1595. {
  1596. Vector velDesired = nextPos - GetLocalOrigin();
  1597. VectorNormalize( velDesired );
  1598. velDesired *= fabs( m_flSpeed );
  1599. if ( CBaseEntity::IsSimulatingOnAlternateTicks() && flInterval < .017 )
  1600. {
  1601. // https://bugbait.valvesoftware.com/show_bug.cgi?id=70371
  1602. // Local band-aid: when a portal is on the train, the train simulates with the player some ticks but still only
  1603. // every other tick, so the interval on game physics is one tick, but only run every other, so
  1604. // you get a halting half speed effect. This isn't a great fix, as the movement of the panel is
  1605. // not as smooth as w/o alternate ticks [11/15/2010 tom]
  1606. velDesired *= 2.0f;
  1607. }
  1608. SetLocalVelocity( velDesired );
  1609. break;
  1610. }
  1611. case TrainVelocity_LinearBlend:
  1612. case TrainVelocity_EaseInEaseOut:
  1613. {
  1614. if ( m_bAccelToSpeed )
  1615. {
  1616. float flPrevSpeed = m_flSpeed;
  1617. float flNextSpeed = m_flDesiredSpeed;
  1618. if ( flPrevSpeed != flNextSpeed )
  1619. {
  1620. float flSpeedChangeTime = ( abs(flNextSpeed) > abs(flPrevSpeed) ) ? m_flAccelSpeed : m_flDecelSpeed;
  1621. m_flSpeed = UTIL_Approach( m_flDesiredSpeed, m_flSpeed, flSpeedChangeTime * gpGlobals->frametime );
  1622. }
  1623. }
  1624. else if ( pPrev && pNext )
  1625. {
  1626. // Get the speed to blend from.
  1627. float flPrevSpeed = m_flSpeed;
  1628. if ( pPrev->m_flSpeed != 0 )
  1629. {
  1630. flPrevSpeed = pPrev->m_flSpeed;
  1631. }
  1632. // Get the speed to blend to.
  1633. float flNextSpeed = flPrevSpeed;
  1634. if ( pNext->m_flSpeed != 0 )
  1635. {
  1636. flNextSpeed = pNext->m_flSpeed;
  1637. }
  1638. // If they're different, do the blend.
  1639. if ( flPrevSpeed != flNextSpeed )
  1640. {
  1641. Vector vecSegment = pNext->GetLocalOrigin() - pPrev->GetLocalOrigin();
  1642. float flSegmentLen = vecSegment.Length();
  1643. if ( flSegmentLen )
  1644. {
  1645. Vector vecCurOffset = GetLocalOrigin() - pPrev->GetLocalOrigin();
  1646. float p = vecCurOffset.Length() / flSegmentLen;
  1647. if ( GetTrainVelocityType() == TrainVelocity_EaseInEaseOut )
  1648. {
  1649. p = SimpleSplineRemapVal( p, 0.0f, 1.0f, 0.0f, 1.0f );
  1650. }
  1651. m_flSpeed = m_dir * ( flPrevSpeed * ( 1 - p ) + flNextSpeed * p );
  1652. }
  1653. }
  1654. else
  1655. {
  1656. m_flSpeed = m_dir * flPrevSpeed;
  1657. }
  1658. }
  1659. Vector velDesired = nextPos - GetLocalOrigin();
  1660. VectorNormalize( velDesired );
  1661. velDesired *= fabs( m_flSpeed );
  1662. SetLocalVelocity( velDesired );
  1663. break;
  1664. }
  1665. }
  1666. }
  1667. //-----------------------------------------------------------------------------
  1668. // Purpose: Controls how the train blends angles as it moves along the path.
  1669. //-----------------------------------------------------------------------------
  1670. TrainOrientationType_t CFuncTrackTrain::GetTrainOrientationType()
  1671. {
  1672. return m_eOrientationType;
  1673. }
  1674. //-----------------------------------------------------------------------------
  1675. // Purpose:
  1676. // Input : pnext -
  1677. //-----------------------------------------------------------------------------
  1678. void CFuncTrackTrain::UpdateTrainOrientation( CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
  1679. {
  1680. // FIXME: old way of doing fixed orienation trains, remove!
  1681. if ( HasSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION ) )
  1682. return;
  1683. // Trains *can* work in local space, but only if all elements of the track share
  1684. // the same move parent as the train.
  1685. Assert( !pPrev || (pPrev->GetMoveParent() == GetMoveParent()) );
  1686. switch ( GetTrainOrientationType() )
  1687. {
  1688. case TrainOrientation_Fixed:
  1689. {
  1690. // Fixed orientation. Do nothing.
  1691. break;
  1692. }
  1693. case TrainOrientation_AtPathTracks:
  1694. {
  1695. UpdateOrientationAtPathTracks( pPrev, pNext, nextPos, flInterval );
  1696. break;
  1697. }
  1698. case TrainOrientation_EaseInEaseOut:
  1699. case TrainOrientation_LinearBlend:
  1700. {
  1701. UpdateOrientationBlend( GetTrainOrientationType(), pPrev, pNext, nextPos, flInterval );
  1702. break;
  1703. }
  1704. }
  1705. }
  1706. //-----------------------------------------------------------------------------
  1707. // Purpose: Adjusts our angles as we hit each path track. This is for support of
  1708. // trains with wheels that round corners a la HL1 trains.
  1709. // FIXME: move into path_track, have the angles come back from LookAhead
  1710. //-----------------------------------------------------------------------------
  1711. void CFuncTrackTrain::UpdateOrientationAtPathTracks( CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
  1712. {
  1713. if ( !m_ppath )
  1714. return;
  1715. Vector nextFront = GetLocalOrigin();
  1716. CPathTrack *pNextNode = NULL;
  1717. nextFront.z -= m_height;
  1718. if ( m_length > 0 )
  1719. {
  1720. m_ppath->LookAhead( nextFront, IsDirForward() ? m_length : -m_length, 0, &pNextNode );
  1721. }
  1722. else
  1723. {
  1724. m_ppath->LookAhead( nextFront, IsDirForward() ? 100 : -100, 0, &pNextNode );
  1725. }
  1726. nextFront.z += m_height;
  1727. Vector vecFaceDir = nextFront - GetLocalOrigin();
  1728. if ( !IsDirForward() )
  1729. {
  1730. vecFaceDir *= -1;
  1731. }
  1732. QAngle angles;
  1733. VectorAngles( vecFaceDir, angles );
  1734. // !!! All of this crap has to be done to make the angles not wrap around, revisit this.
  1735. FixupAngles( angles );
  1736. // Wrapped with this bool so we don't affect old trains
  1737. if ( m_bManualSpeedChanges )
  1738. {
  1739. if ( pNextNode && pNextNode->GetOrientationType() == TrackOrientation_FacePathAngles )
  1740. {
  1741. angles = pNextNode->GetOrientation( IsDirForward() );
  1742. }
  1743. }
  1744. QAngle curAngles = GetLocalAngles();
  1745. FixupAngles( curAngles );
  1746. if ( !pPrev || (vecFaceDir.x == 0 && vecFaceDir.y == 0) )
  1747. angles = curAngles;
  1748. DoUpdateOrientation( curAngles, angles, flInterval );
  1749. }
  1750. //-----------------------------------------------------------------------------
  1751. // Purpose: Blends our angles using one of two orientation blending types.
  1752. // ASSUMES that eOrientationType is either LinearBlend or EaseInEaseOut.
  1753. // FIXME: move into path_track, have the angles come back from LookAhead
  1754. //-----------------------------------------------------------------------------
  1755. void CFuncTrackTrain::UpdateOrientationBlend( TrainOrientationType_t eOrientationType, CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
  1756. {
  1757. // Get the angles to blend from.
  1758. QAngle angPrev = pPrev->GetOrientation( IsDirForward() );
  1759. FixupAngles( angPrev );
  1760. // Get the angles to blend to.
  1761. QAngle angNext;
  1762. if ( pNext )
  1763. {
  1764. angNext = pNext->GetOrientation( IsDirForward() );
  1765. FixupAngles( angNext );
  1766. }
  1767. else
  1768. {
  1769. // At a dead end, just use the last path track's angles.
  1770. angNext = angPrev;
  1771. }
  1772. if ( m_spawnflags & SF_TRACKTRAIN_NOPITCH )
  1773. {
  1774. angNext[PITCH] = angPrev[PITCH];
  1775. }
  1776. // Calculate our parametric distance along the path segment from 0 to 1.
  1777. float p = 0;
  1778. if ( pPrev && ( angPrev != angNext ) )
  1779. {
  1780. Vector vecSegment = pNext->GetLocalOrigin() - pPrev->GetLocalOrigin();
  1781. float flSegmentLen = vecSegment.Length();
  1782. if ( flSegmentLen )
  1783. {
  1784. Vector vecCurOffset = GetLocalOrigin() - pPrev->GetLocalOrigin();
  1785. p = vecCurOffset.Length() / flSegmentLen;
  1786. }
  1787. }
  1788. if ( eOrientationType == TrainOrientation_EaseInEaseOut )
  1789. {
  1790. p = SimpleSplineRemapVal( p, 0.0f, 1.0f, 0.0f, 1.0f );
  1791. }
  1792. //Msg( "UpdateOrientationFacePathAngles: %s->%s, p=%f, ", pPrev->GetDebugName(), pNext->GetDebugName(), p );
  1793. Quaternion qtPrev;
  1794. Quaternion qtNext;
  1795. // hack to avoid gimble lock
  1796. if( angPrev[PITCH] == 90 )
  1797. angPrev[PITCH] = 89;
  1798. if( angPrev[PITCH] == -90 )
  1799. angPrev[PITCH] = -89;
  1800. if( angNext[PITCH] == 90 )
  1801. angNext[PITCH] = 89;
  1802. if( angNext[PITCH] == -90 )
  1803. angNext[PITCH] = 89;
  1804. AngleQuaternion( angPrev, qtPrev );
  1805. AngleQuaternion( angNext, qtNext );
  1806. QAngle angNew = angNext;
  1807. float flAngleDiff = QuaternionAngleDiff( qtPrev, qtNext );
  1808. if ( flAngleDiff )
  1809. {
  1810. Quaternion qtNew;
  1811. QuaternionSlerp( qtPrev, qtNext, p, qtNew );
  1812. QuaternionAngles( qtNew, angNew );
  1813. }
  1814. if ( m_spawnflags & SF_TRACKTRAIN_NOPITCH )
  1815. {
  1816. angNew[PITCH] = angPrev[PITCH];
  1817. }
  1818. DoUpdateOrientation( GetLocalAngles(), angNew, flInterval );
  1819. }
  1820. //-----------------------------------------------------------------------------
  1821. // Purpose: Sets our angular velocity to approach the target angles over the given interval.
  1822. //-----------------------------------------------------------------------------
  1823. void CFuncTrackTrain::DoUpdateOrientation( const QAngle &curAngles, const QAngle &angles, float flInterval )
  1824. {
  1825. float vy, vx, vz;
  1826. if ( !(m_spawnflags & SF_TRACKTRAIN_NOPITCH) )
  1827. {
  1828. vx = UTIL_AngleDistance( angles.x, curAngles.x );
  1829. }
  1830. else
  1831. {
  1832. vx = 0;
  1833. }
  1834. vy = UTIL_AngleDistance( angles.y, curAngles.y );
  1835. if ( (m_spawnflags & SF_TRACKTRAIN_ALLOWROLL ) )
  1836. {
  1837. vz = UTIL_AngleDistance( angles.z, curAngles.z );
  1838. }
  1839. else
  1840. {
  1841. vz = 0;
  1842. }
  1843. // HACKHACK: Clamp really small angular deltas to avoid rotating movement on things
  1844. // that are close enough
  1845. if ( fabs(vx) < 0.1 )
  1846. {
  1847. vx = 0;
  1848. }
  1849. if ( fabs(vy) < 0.1 )
  1850. {
  1851. vy = 0;
  1852. }
  1853. if ( fabs(vz) < 0.1 )
  1854. {
  1855. vz = 0;
  1856. }
  1857. if ( flInterval == 0 )
  1858. {
  1859. // Avoid dividing by zero
  1860. flInterval = 0.1;
  1861. }
  1862. QAngle vecAngVel( vx / flInterval, vy / flInterval, vz / flInterval );
  1863. if ( m_flBank != 0 )
  1864. {
  1865. if ( vecAngVel.y < -5 )
  1866. {
  1867. vecAngVel.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, curAngles.z, m_flBank*2 ), curAngles.z);
  1868. }
  1869. else if ( vecAngVel.y > 5 )
  1870. {
  1871. vecAngVel.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, curAngles.z, m_flBank*2 ), curAngles.z);
  1872. }
  1873. else
  1874. {
  1875. vecAngVel.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, curAngles.z, m_flBank*4 ), curAngles.z) * 4;
  1876. }
  1877. }
  1878. SetLocalAngularVelocity( vecAngVel );
  1879. }
  1880. //-----------------------------------------------------------------------------
  1881. // Purpose:
  1882. // Input : pTeleport -
  1883. //-----------------------------------------------------------------------------
  1884. void CFuncTrackTrain::TeleportToPathTrack( CPathTrack *pTeleport )
  1885. {
  1886. QAngle angCur = GetLocalAngles();
  1887. Vector nextPos = pTeleport->GetLocalOrigin();
  1888. Vector look = nextPos;
  1889. pTeleport->LookAhead( look, m_length, 0 );
  1890. QAngle nextAngles;
  1891. if ( HasSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION ) || ( look == nextPos ) )
  1892. {
  1893. nextAngles = GetLocalAngles();
  1894. }
  1895. else
  1896. {
  1897. nextAngles = pTeleport->GetOrientation( IsDirForward() );
  1898. if ( HasSpawnFlags( SF_TRACKTRAIN_NOPITCH ) )
  1899. {
  1900. nextAngles[PITCH] = angCur[PITCH];
  1901. }
  1902. }
  1903. Teleport( &pTeleport->GetLocalOrigin(), &nextAngles, NULL );
  1904. SetLocalAngularVelocity( vec3_angle );
  1905. }
  1906. //-----------------------------------------------------------------------------
  1907. // Purpose: Advances the train to the next path corner on the path.
  1908. //-----------------------------------------------------------------------------
  1909. void CFuncTrackTrain::Next( void )
  1910. {
  1911. if ( !m_flSpeed )
  1912. {
  1913. DevMsg( 2, "TRAIN(%s): Speed is 0\n", GetDebugName() );
  1914. SoundStop();
  1915. return;
  1916. }
  1917. if ( !m_ppath )
  1918. {
  1919. DevMsg( 2, "TRAIN(%s): Lost path\n", GetDebugName() );
  1920. SoundStop();
  1921. m_flSpeed = 0;
  1922. return;
  1923. }
  1924. SoundUpdate();
  1925. //
  1926. // Based on our current position and speed, look ahead along our path and see
  1927. // where we should be in 0.1 seconds.
  1928. //
  1929. Vector nextPos = GetLocalOrigin();
  1930. float flSpeed = m_flSpeed;
  1931. nextPos.z -= m_height;
  1932. CPathTrack *pNextNext = NULL;
  1933. CPathTrack *pNext = m_ppath->LookAhead( nextPos, flSpeed * 0.1, 1, &pNextNext );
  1934. //Assert( pNext != NULL );
  1935. // If we're moving towards a dead end, but our desired speed goes in the opposite direction
  1936. // this fixes us from stalling
  1937. if ( m_bManualSpeedChanges && ( ( flSpeed < 0 ) != ( m_flDesiredSpeed < 0 ) ) )
  1938. {
  1939. if ( !pNext )
  1940. pNext = m_ppath;
  1941. }
  1942. if (m_debugOverlays & OVERLAY_BBOX_BIT)
  1943. {
  1944. if ( pNext != NULL )
  1945. {
  1946. NDebugOverlay::Line( GetAbsOrigin(), pNext->GetAbsOrigin(), 255, 0, 0, true, 0.1 );
  1947. NDebugOverlay::Line( pNext->GetAbsOrigin(), pNext->GetAbsOrigin() + Vector( 0,0,32), 255, 0, 0, true, 0.1 );
  1948. NDebugOverlay::Box( pNext->GetAbsOrigin(), Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 255, 0, 0, 0, 0.1 );
  1949. }
  1950. if ( pNextNext != NULL )
  1951. {
  1952. NDebugOverlay::Line( GetAbsOrigin(), pNextNext->GetAbsOrigin(), 0, 255, 0, true, 0.1 );
  1953. NDebugOverlay::Line( pNextNext->GetAbsOrigin(), pNextNext->GetAbsOrigin() + Vector( 0,0,32), 0, 255, 0, true, 0.1 );
  1954. NDebugOverlay::Box( pNextNext->GetAbsOrigin(), Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 0, 255, 0, 0, 0.1 );
  1955. }
  1956. }
  1957. nextPos.z += m_height;
  1958. // Trains *can* work in local space, but only if all elements of the track share
  1959. // the same move parent as the train.
  1960. Assert( !pNext || (pNext->GetMoveParent() == GetMoveParent()) );
  1961. if ( pNext )
  1962. {
  1963. UpdateTrainVelocity( pNext, pNextNext, nextPos, gpGlobals->frametime );
  1964. UpdateTrainOrientation( pNext, pNextNext, nextPos, gpGlobals->frametime );
  1965. if ( pNext != m_ppath )
  1966. {
  1967. //
  1968. // We have reached a new path track. Fire its OnPass output.
  1969. //
  1970. m_ppath = pNext;
  1971. ArriveAtNode( pNext );
  1972. //
  1973. // See if we should teleport to the next path track.
  1974. //
  1975. CPathTrack *pTeleport = pNext->GetNext();
  1976. if ( ( pTeleport != NULL ) && pTeleport->HasSpawnFlags( SF_PATH_TELEPORT ) )
  1977. {
  1978. TeleportToPathTrack( pTeleport );
  1979. }
  1980. }
  1981. m_OnNext.FireOutput( pNext, this );
  1982. SetThink( &CFuncTrackTrain::Next );
  1983. SetMoveDoneTime( 0.1 );
  1984. SetNextThink( gpGlobals->curtime );
  1985. SetMoveDone( NULL );
  1986. }
  1987. else
  1988. {
  1989. //
  1990. // We've reached the end of the path, stop.
  1991. //
  1992. SoundStop();
  1993. SetLocalVelocity(nextPos - GetLocalOrigin());
  1994. SetLocalAngularVelocity( vec3_angle );
  1995. float distance = GetLocalVelocity().Length();
  1996. m_oldSpeed = m_flSpeed;
  1997. m_flSpeed = 0;
  1998. // Move to the dead end
  1999. // Are we there yet?
  2000. if ( distance > 0 )
  2001. {
  2002. // no, how long to get there?
  2003. float flTime = distance / fabs( m_oldSpeed );
  2004. SetLocalVelocity( GetLocalVelocity() * (fabs(m_oldSpeed) / distance) );
  2005. SetMoveDone( &CFuncTrackTrain::DeadEnd );
  2006. SetNextThink( TICK_NEVER_THINK );
  2007. SetMoveDoneTime( flTime );
  2008. }
  2009. else
  2010. {
  2011. DeadEnd();
  2012. }
  2013. }
  2014. }
  2015. Vector CFuncTrackTrain::ScriptGetFuturePosition( float flSeconds, float flMinSpeed )
  2016. {
  2017. //
  2018. // Based on our current position and speed, look ahead along our path and see
  2019. // where we should be in flSeconds seconds.
  2020. //
  2021. Vector nextPos = GetLocalOrigin();
  2022. float flSpeed = flMinSpeed;
  2023. nextPos.z -= m_height;
  2024. CPathTrack *pNextNext = NULL;
  2025. CPathTrack *pNext = m_ppath->LookAhead( nextPos, flSpeed * flSeconds, 1, &pNextNext );
  2026. // If we're moving towards a dead end, but our desired speed goes in the opposite direction
  2027. // this fixes us from stalling
  2028. if ( m_bManualSpeedChanges && ( ( flSpeed < 0 ) != ( m_flDesiredSpeed < 0 ) ) )
  2029. {
  2030. if ( !pNext )
  2031. pNext = m_ppath;
  2032. }
  2033. // NDebugOverlay::Box( nextPos, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 0, 255, 0, 0, 0.1 );
  2034. return nextPos;
  2035. }
  2036. void CFuncTrackTrain::FirePassInputs( CPathTrack *pStart, CPathTrack *pEnd, bool forward )
  2037. {
  2038. CPathTrack *pCurrent = pStart;
  2039. // swap if going backward
  2040. if ( !forward )
  2041. {
  2042. pCurrent = pEnd;
  2043. pEnd = pStart;
  2044. }
  2045. variant_t emptyVariant;
  2046. while ( pCurrent && pCurrent != pEnd )
  2047. {
  2048. //Msg("Fired pass on %s\n", STRING(pCurrent->GetEntityName()) );
  2049. pCurrent->AcceptInput( "InPass", this, this, emptyVariant, 0 );
  2050. pCurrent = forward ? pCurrent->GetNext() : pCurrent->GetPrevious();
  2051. }
  2052. }
  2053. void CFuncTrackTrain::DeadEnd( void )
  2054. {
  2055. // Fire the dead-end target if there is one
  2056. CPathTrack *pTrack, *pNext;
  2057. pTrack = m_ppath;
  2058. DevMsg( 2, "TRAIN(%s): Dead end ", GetDebugName() );
  2059. // Find the dead end path node
  2060. // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed
  2061. // so we have to traverse the list to it's end.
  2062. if ( pTrack )
  2063. {
  2064. if ( m_oldSpeed < 0 )
  2065. {
  2066. do
  2067. {
  2068. pNext = pTrack->ValidPath( pTrack->GetPrevious(), true );
  2069. if ( pNext )
  2070. pTrack = pNext;
  2071. } while ( pNext );
  2072. }
  2073. else
  2074. {
  2075. do
  2076. {
  2077. pNext = pTrack->ValidPath( pTrack->GetNext(), true );
  2078. if ( pNext )
  2079. pTrack = pNext;
  2080. } while ( pNext );
  2081. }
  2082. }
  2083. SetLocalVelocity( vec3_origin );
  2084. SetLocalAngularVelocity( vec3_angle );
  2085. if ( pTrack )
  2086. {
  2087. m_ppath = pTrack;
  2088. DevMsg( 2, "at %s\n", pTrack->GetDebugName() );
  2089. variant_t emptyVariant;
  2090. pTrack->AcceptInput( "InPass", this, this, emptyVariant, 0 );
  2091. // also check to see if we were assigned to move here.
  2092. if (m_strPathTarget != NULL_STRING )
  2093. {
  2094. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, STRING(m_strPathTarget) );
  2095. if (pEntity && pTrack == pEntity)
  2096. {
  2097. m_OnArrivedAtDestinationNode.FireOutput(pTrack, this);
  2098. m_strPathTarget = NULL_STRING;
  2099. }
  2100. }
  2101. }
  2102. else
  2103. {
  2104. DevMsg( 2, "\n" );
  2105. }
  2106. }
  2107. void CFuncTrackTrain::SetControls( CBaseEntity *pControls )
  2108. {
  2109. Vector offset = pControls->GetLocalOrigin();
  2110. m_controlMins = pControls->WorldAlignMins() + offset;
  2111. m_controlMaxs = pControls->WorldAlignMaxs() + offset;
  2112. }
  2113. //-----------------------------------------------------------------------------
  2114. // Purpose: Returns true if the entity's origin is within the controls region.
  2115. //-----------------------------------------------------------------------------
  2116. bool CFuncTrackTrain::OnControls( CBaseEntity *pTest )
  2117. {
  2118. Vector offset = pTest->GetLocalOrigin() - GetLocalOrigin();
  2119. if ( m_spawnflags & SF_TRACKTRAIN_NOCONTROL )
  2120. return false;
  2121. // Transform offset into local coordinates
  2122. VMatrix tmp = SetupMatrixAngles( GetLocalAngles() );
  2123. // rotate into local space
  2124. Vector local = tmp.VMul3x3Transpose( offset );
  2125. /*
  2126. NDebugOverlay::Box( GetLocalOrigin(), m_controlMins, m_controlMaxs,
  2127. 255, 0, 0, 100, 5.0 );
  2128. NDebugOverlay::Box( GetLocalOrigin() + local, Vector(-5,-5,-5), Vector(5,5,5),
  2129. 0, 0, 255, 100, 5.0 );
  2130. */
  2131. if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z &&
  2132. local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z )
  2133. return true;
  2134. return false;
  2135. }
  2136. void CFuncTrackTrain::Find( void )
  2137. {
  2138. m_ppath = (CPathTrack *)gEntList.FindEntityByName( NULL, m_target );
  2139. if ( !m_ppath )
  2140. return;
  2141. if ( !FClassnameIs( m_ppath, "path_track" )
  2142. #ifndef PORTAL //env_portal_path_track is a child of path_track and would like to get found
  2143. && !FClassnameIs( m_ppath, "env_portal_path_track" )
  2144. #endif //#ifndef PORTAL
  2145. )
  2146. {
  2147. Warning( "func_track_train must be on a path of path_track\n" );
  2148. Assert(0);
  2149. m_ppath = NULL;
  2150. return;
  2151. }
  2152. Vector nextPos = m_ppath->GetLocalOrigin();
  2153. Vector look = nextPos;
  2154. m_ppath->LookAhead( look, m_length, 0 );
  2155. nextPos.z += m_height;
  2156. look.z += m_height;
  2157. QAngle nextAngles;
  2158. if ( HasSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION ) )
  2159. {
  2160. nextAngles = GetLocalAngles();
  2161. }
  2162. else
  2163. {
  2164. VectorAngles( look - nextPos, nextAngles );
  2165. if ( HasSpawnFlags( SF_TRACKTRAIN_NOPITCH ) )
  2166. {
  2167. nextAngles.x = 0;
  2168. }
  2169. }
  2170. Teleport( &nextPos, &nextAngles, NULL );
  2171. ArriveAtNode( m_ppath );
  2172. if ( m_flSpeed != 0 )
  2173. {
  2174. SetNextThink( gpGlobals->curtime + 0.1f );
  2175. SetThink( &CFuncTrackTrain::Next );
  2176. SoundUpdate();
  2177. }
  2178. }
  2179. void CFuncTrackTrain::NearestPath( void )
  2180. {
  2181. CBaseEntity *pTrack = NULL;
  2182. CBaseEntity *pNearest = NULL;
  2183. float dist, closest;
  2184. closest = 1024;
  2185. for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024 ); ( pTrack = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
  2186. {
  2187. // filter out non-tracks
  2188. if ( !(pTrack->GetFlags() & (FL_CLIENT|FL_NPC)) && FClassnameIs( pTrack, "path_track" ) )
  2189. {
  2190. dist = (GetAbsOrigin() - pTrack->GetAbsOrigin()).Length();
  2191. if ( dist < closest )
  2192. {
  2193. closest = dist;
  2194. pNearest = pTrack;
  2195. }
  2196. }
  2197. }
  2198. if ( !pNearest )
  2199. {
  2200. Msg( "Can't find a nearby track !!!\n" );
  2201. SetThink(NULL);
  2202. return;
  2203. }
  2204. DevMsg( 2, "TRAIN: %s, Nearest track is %s\n", GetDebugName(), pNearest->GetDebugName() );
  2205. // If I'm closer to the next path_track on this path, then it's my real path
  2206. pTrack = ((CPathTrack *)pNearest)->GetNext();
  2207. if ( pTrack )
  2208. {
  2209. if ( (GetLocalOrigin() - pTrack->GetLocalOrigin()).Length() < (GetLocalOrigin() - pNearest->GetLocalOrigin()).Length() )
  2210. pNearest = pTrack;
  2211. }
  2212. m_ppath = (CPathTrack *)pNearest;
  2213. if ( m_flSpeed != 0 )
  2214. {
  2215. SetMoveDoneTime( 0.1 );
  2216. SetMoveDone( &CFuncTrackTrain::Next );
  2217. }
  2218. }
  2219. void CFuncTrackTrain::OnRestore( void )
  2220. {
  2221. BaseClass::OnRestore();
  2222. if ( !m_ppath )
  2223. {
  2224. NearestPath();
  2225. SetThink( NULL );
  2226. }
  2227. }
  2228. CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent )
  2229. {
  2230. CBaseEntity *pEntity = CBaseEntity::Instance( pent );
  2231. if ( FClassnameIs( pEntity, "func_tracktrain" ) )
  2232. return (CFuncTrackTrain *)pEntity;
  2233. return NULL;
  2234. }
  2235. //-----------------------------------------------------------------------------
  2236. // Purpose:
  2237. //-----------------------------------------------------------------------------
  2238. void CFuncTrackTrain::Spawn( void )
  2239. {
  2240. if ( m_maxSpeed == 0 )
  2241. {
  2242. if ( m_flSpeed == 0 )
  2243. {
  2244. m_maxSpeed = 100;
  2245. }
  2246. else
  2247. {
  2248. m_maxSpeed = m_flSpeed;
  2249. }
  2250. }
  2251. if ( m_nMoveSoundMinPitch == 0 )
  2252. {
  2253. m_nMoveSoundMinPitch = 60;
  2254. }
  2255. if ( m_nMoveSoundMaxPitch == 0 )
  2256. {
  2257. m_nMoveSoundMaxPitch = 200;
  2258. }
  2259. SetLocalVelocity(vec3_origin);
  2260. SetLocalAngularVelocity( vec3_angle );
  2261. m_dir = 1;
  2262. if ( !m_target )
  2263. {
  2264. Msg("FuncTrackTrain '%s' has no target.\n", GetDebugName());
  2265. }
  2266. SetModel( STRING( GetModelName() ) );
  2267. SetMoveType( MOVETYPE_PUSH );
  2268. SetSolid( HasSpawnFlags( SF_TRACKTRAIN_HL1TRAIN ) ? SOLID_BSP : SOLID_VPHYSICS );
  2269. //SetSolid( SOLID_VPHYSICS );
  2270. if ( HasSpawnFlags( SF_TRACKTRAIN_UNBLOCKABLE_BY_PLAYER ) )
  2271. {
  2272. AddFlag( FL_UNBLOCKABLE_BY_PLAYER );
  2273. }
  2274. if ( m_spawnflags & SF_TRACKTRAIN_PASSABLE )
  2275. {
  2276. AddSolidFlags( FSOLID_NOT_SOLID );
  2277. }
  2278. m_controlMins = CollisionProp()->OBBMins();
  2279. m_controlMaxs = CollisionProp()->OBBMaxs();
  2280. m_controlMaxs.z += 72;
  2281. // start trains on the next frame, to make sure their targets have had
  2282. // a chance to spawn/activate
  2283. SetThink( &CFuncTrackTrain::Find );
  2284. SetNextThink( gpGlobals->curtime );
  2285. Precache();
  2286. CreateVPhysics();
  2287. }
  2288. bool CFuncTrackTrain::CreateVPhysics( void )
  2289. {
  2290. VPhysicsInitShadow( false, false );
  2291. return true;
  2292. }
  2293. //-----------------------------------------------------------------------------
  2294. // Purpose: Precaches the train sounds.
  2295. //-----------------------------------------------------------------------------
  2296. void CFuncTrackTrain::Precache( void )
  2297. {
  2298. if (m_flVolume == 0.0)
  2299. {
  2300. m_flVolume = 1.0;
  2301. }
  2302. if ( m_iszSoundMove != NULL_STRING )
  2303. {
  2304. PrecacheScriptSound( STRING( m_iszSoundMove ) );
  2305. }
  2306. if ( m_iszSoundMovePing != NULL_STRING )
  2307. {
  2308. PrecacheScriptSound( STRING( m_iszSoundMovePing ) );
  2309. }
  2310. if ( m_iszSoundStart != NULL_STRING )
  2311. {
  2312. PrecacheScriptSound( STRING( m_iszSoundStart ) );
  2313. }
  2314. if ( m_iszSoundStop != NULL_STRING )
  2315. {
  2316. PrecacheScriptSound( STRING( m_iszSoundStop ) );
  2317. }
  2318. }
  2319. //-----------------------------------------------------------------------------
  2320. // Purpose:
  2321. //-----------------------------------------------------------------------------
  2322. void CFuncTrackTrain::UpdateOnRemove()
  2323. {
  2324. SoundStop();
  2325. BaseClass::UpdateOnRemove();
  2326. }
  2327. void CFuncTrackTrain::MoveDone()
  2328. {
  2329. m_lastBlockPos.Init();
  2330. m_lastBlockTick = -1;
  2331. BaseClass::MoveDone();
  2332. }
  2333. //-----------------------------------------------------------------------------
  2334. // Purpose: Defines the volume of space that the player must stand in to
  2335. // control the train
  2336. //-----------------------------------------------------------------------------
  2337. class CFuncTrainControls : public CBaseEntity
  2338. {
  2339. DECLARE_CLASS( CFuncTrainControls, CBaseEntity );
  2340. public:
  2341. void Spawn( void );
  2342. void Find( void );
  2343. DECLARE_DATADESC();
  2344. };
  2345. BEGIN_DATADESC( CFuncTrainControls )
  2346. // Function Pointers
  2347. DEFINE_FUNCTION( Find ),
  2348. END_DATADESC()
  2349. LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls );
  2350. void CFuncTrainControls::Find( void )
  2351. {
  2352. CBaseEntity *pTarget = NULL;
  2353. do
  2354. {
  2355. pTarget = gEntList.FindEntityByName( pTarget, m_target );
  2356. } while ( pTarget && !FClassnameIs(pTarget, "func_tracktrain") );
  2357. if ( !pTarget )
  2358. {
  2359. Msg( "No train %s\n", STRING(m_target) );
  2360. return;
  2361. }
  2362. CFuncTrackTrain *ptrain = (CFuncTrackTrain*) pTarget;
  2363. ptrain->SetControls( this );
  2364. SetThink( NULL );
  2365. }
  2366. void CFuncTrainControls::Spawn( void )
  2367. {
  2368. SetSolid( SOLID_NONE );
  2369. SetMoveType( MOVETYPE_NONE );
  2370. SetModel( STRING( GetModelName() ) );
  2371. AddEffects( EF_NODRAW );
  2372. Assert( GetParent() && "func_traincontrols needs parent to properly align to train" );
  2373. SetThink( &CFuncTrainControls::Find );
  2374. SetNextThink( gpGlobals->curtime );
  2375. }
  2376. #define SF_TRACK_ACTIVATETRAIN 0x00000001
  2377. #define SF_TRACK_RELINK 0x00000002
  2378. #define SF_TRACK_ROTMOVE 0x00000004
  2379. #define SF_TRACK_STARTBOTTOM 0x00000008
  2380. #define SF_TRACK_DONT_MOVE 0x00000010
  2381. typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE;
  2382. //-----------------------------------------------------------------------------
  2383. // This entity is a rotating/moving platform that will carry a train to a new track.
  2384. // It must be larger in X-Y planar area than the train, since it must contain the
  2385. // train within these dimensions in order to operate when the train is near it.
  2386. //-----------------------------------------------------------------------------
  2387. class CFuncTrackChange : public CFuncPlatRot
  2388. {
  2389. DECLARE_CLASS( CFuncTrackChange, CFuncPlatRot );
  2390. public:
  2391. void Spawn( void );
  2392. void Precache( void );
  2393. // virtual void Blocked( void );
  2394. virtual void GoUp( void );
  2395. virtual void GoDown( void );
  2396. void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  2397. void Find( void );
  2398. TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent );
  2399. void UpdateTrain( QAngle &dest );
  2400. virtual void HitBottom( void );
  2401. virtual void HitTop( void );
  2402. void Touch( CBaseEntity *pOther );
  2403. virtual void UpdateAutoTargets( int toggleState );
  2404. virtual bool IsTogglePlat( void ) { return true; }
  2405. void DisableUse( void ) { m_use = 0; }
  2406. void EnableUse( void ) { m_use = 1; }
  2407. int UseEnabled( void ) { return m_use; }
  2408. DECLARE_DATADESC();
  2409. CPathTrack *m_trackTop;
  2410. CPathTrack *m_trackBottom;
  2411. CFuncTrackTrain *m_train;
  2412. string_t m_trackTopName;
  2413. string_t m_trackBottomName;
  2414. string_t m_trainName;
  2415. TRAIN_CODE m_code;
  2416. int m_targetState;
  2417. int m_use;
  2418. };
  2419. LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange );
  2420. BEGIN_DATADESC( CFuncTrackChange )
  2421. DEFINE_GLOBAL_FIELD( m_trackTop, FIELD_CLASSPTR ),
  2422. DEFINE_GLOBAL_FIELD( m_trackBottom, FIELD_CLASSPTR ),
  2423. DEFINE_GLOBAL_FIELD( m_train, FIELD_CLASSPTR ),
  2424. DEFINE_GLOBAL_KEYFIELD( m_trackTopName, FIELD_STRING, "toptrack" ),
  2425. DEFINE_GLOBAL_KEYFIELD( m_trackBottomName, FIELD_STRING, "bottomtrack" ),
  2426. DEFINE_GLOBAL_KEYFIELD( m_trainName, FIELD_STRING, "train" ),
  2427. DEFINE_FIELD( m_code, FIELD_INTEGER ),
  2428. DEFINE_FIELD( m_targetState, FIELD_INTEGER ),
  2429. DEFINE_FIELD( m_use, FIELD_INTEGER ),
  2430. // Function Pointers
  2431. DEFINE_FUNCTION( Find ),
  2432. END_DATADESC()
  2433. void CFuncTrackChange::Spawn( void )
  2434. {
  2435. Setup();
  2436. if ( FBitSet( m_spawnflags, SF_TRACK_DONT_MOVE ) )
  2437. m_vecPosition2.z = GetLocalOrigin().z;
  2438. SetupRotation();
  2439. if ( FBitSet( m_spawnflags, SF_TRACK_STARTBOTTOM ) )
  2440. {
  2441. UTIL_SetOrigin( this, m_vecPosition2);
  2442. m_toggle_state = TS_AT_BOTTOM;
  2443. SetLocalAngles( m_start );
  2444. m_targetState = TS_AT_TOP;
  2445. }
  2446. else
  2447. {
  2448. UTIL_SetOrigin( this, m_vecPosition1);
  2449. m_toggle_state = TS_AT_TOP;
  2450. SetLocalAngles( m_end );
  2451. m_targetState = TS_AT_BOTTOM;
  2452. }
  2453. EnableUse();
  2454. SetThink( &CFuncTrackChange::Find );
  2455. SetNextThink( gpGlobals->curtime + 2 );
  2456. Precache();
  2457. }
  2458. void CFuncTrackChange::Precache( void )
  2459. {
  2460. BaseClass::Precache();
  2461. PrecacheScriptSound( "FuncTrackChange.Blocking" );
  2462. }
  2463. // UNDONE: Filter touches before re-evaluating the train.
  2464. void CFuncTrackChange::Touch( CBaseEntity *pOther )
  2465. {
  2466. }
  2467. void CFuncTrackChange::Find( void )
  2468. {
  2469. // Find track entities
  2470. CBaseEntity *target;
  2471. target = gEntList.FindEntityByName( NULL, m_trackTopName );
  2472. if ( target )
  2473. {
  2474. m_trackTop = (CPathTrack*) target;
  2475. target = gEntList.FindEntityByName( NULL, m_trackBottomName );
  2476. if ( target )
  2477. {
  2478. m_trackBottom = (CPathTrack*) target;
  2479. target = gEntList.FindEntityByName( NULL, m_trainName );
  2480. if ( target )
  2481. {
  2482. m_train = (CFuncTrackTrain *)gEntList.FindEntityByName( NULL, m_trainName );
  2483. if ( !m_train )
  2484. {
  2485. Warning( "Can't find train for track change! %s\n", STRING(m_trainName) );
  2486. Assert(0);
  2487. return;
  2488. }
  2489. Vector center = WorldSpaceCenter();
  2490. m_trackBottom = m_trackBottom->Nearest( center );
  2491. m_trackTop = m_trackTop->Nearest( center );
  2492. UpdateAutoTargets( m_toggle_state );
  2493. SetThink( NULL );
  2494. return;
  2495. }
  2496. else
  2497. {
  2498. Warning( "Can't find train for track change! %s\n", STRING(m_trainName) );
  2499. Assert(0);
  2500. target = gEntList.FindEntityByName( NULL, m_trainName );
  2501. }
  2502. }
  2503. else
  2504. {
  2505. Warning( "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) );
  2506. Assert(0);
  2507. }
  2508. }
  2509. else
  2510. {
  2511. Warning( "Can't find top track for track change! %s\n", STRING(m_trackTopName) );
  2512. Assert(0);
  2513. }
  2514. }
  2515. TRAIN_CODE CFuncTrackChange::EvaluateTrain( CPathTrack *pcurrent )
  2516. {
  2517. // Go ahead and work, we don't have anything to switch, so just be an elevator
  2518. if ( !pcurrent || !m_train )
  2519. return TRAIN_SAFE;
  2520. if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) ||
  2521. (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) )
  2522. {
  2523. if ( m_train->m_flSpeed != 0 )
  2524. return TRAIN_BLOCKING;
  2525. Vector dist = GetLocalOrigin() - m_train->GetLocalOrigin();
  2526. float length = dist.Length2D();
  2527. if ( length < m_train->m_length ) // Empirically determined close distance
  2528. return TRAIN_FOLLOWING;
  2529. else if ( length > (150 + m_train->m_length) )
  2530. return TRAIN_SAFE;
  2531. return TRAIN_BLOCKING;
  2532. }
  2533. return TRAIN_SAFE;
  2534. }
  2535. void CFuncTrackChange::UpdateTrain( QAngle &dest )
  2536. {
  2537. float time = GetMoveDoneTime();
  2538. m_train->SetAbsVelocity( GetAbsVelocity() );
  2539. m_train->SetLocalAngularVelocity( GetLocalAngularVelocity() );
  2540. m_train->SetMoveDoneTime( time );
  2541. // Attempt at getting the train to rotate properly around the origin of the trackchange
  2542. if ( time <= 0 )
  2543. return;
  2544. Vector offset = m_train->GetLocalOrigin() - GetLocalOrigin();
  2545. QAngle delta = dest - GetLocalAngles();
  2546. // Transform offset into local coordinates
  2547. Vector forward, right, up;
  2548. AngleVectorsTranspose( delta, &forward, &right, &up );
  2549. Vector local;
  2550. local.x = DotProduct( offset, forward );
  2551. local.y = DotProduct( offset, right );
  2552. local.z = DotProduct( offset, up );
  2553. local = local - offset;
  2554. m_train->SetAbsVelocity( GetAbsVelocity() + (local * (1.0/time)) );
  2555. }
  2556. void CFuncTrackChange::GoDown( void )
  2557. {
  2558. if ( m_code == TRAIN_BLOCKING )
  2559. return;
  2560. // HitBottom may get called during CFuncPlat::GoDown(), so set up for that
  2561. // before you call GoDown()
  2562. UpdateAutoTargets( TS_GOING_DOWN );
  2563. // If ROTMOVE, move & rotate
  2564. if ( FBitSet( m_spawnflags, SF_TRACK_DONT_MOVE ) )
  2565. {
  2566. SetMoveDone( &CFuncTrackChange::CallHitBottom );
  2567. m_toggle_state = TS_GOING_DOWN;
  2568. AngularMove( m_start, m_flSpeed );
  2569. }
  2570. else
  2571. {
  2572. BaseClass::GoDown();
  2573. SetMoveDone( &CFuncTrackChange::CallHitBottom );
  2574. RotMove( m_start, GetMoveDoneTime() );
  2575. }
  2576. // Otherwise, rotate first, move second
  2577. // If the train is moving with the platform, update it
  2578. if ( m_code == TRAIN_FOLLOWING )
  2579. {
  2580. UpdateTrain( m_start );
  2581. m_train->m_ppath = NULL;
  2582. }
  2583. }
  2584. //
  2585. // Platform is at bottom, now starts moving up
  2586. //
  2587. void CFuncTrackChange::GoUp( void )
  2588. {
  2589. if ( m_code == TRAIN_BLOCKING )
  2590. return;
  2591. // HitTop may get called during CFuncPlat::GoUp(), so set up for that
  2592. // before you call GoUp();
  2593. UpdateAutoTargets( TS_GOING_UP );
  2594. if ( FBitSet( m_spawnflags, SF_TRACK_DONT_MOVE ) )
  2595. {
  2596. m_toggle_state = TS_GOING_UP;
  2597. SetMoveDone( &CFuncTrackChange::CallHitTop );
  2598. AngularMove( m_end, m_flSpeed );
  2599. }
  2600. else
  2601. {
  2602. // If ROTMOVE, move & rotate
  2603. BaseClass::GoUp();
  2604. SetMoveDone( &CFuncTrackChange::CallHitTop );
  2605. RotMove( m_end, GetMoveDoneTime() );
  2606. }
  2607. // Otherwise, move first, rotate second
  2608. // If the train is moving with the platform, update it
  2609. if ( m_code == TRAIN_FOLLOWING )
  2610. {
  2611. UpdateTrain( m_end );
  2612. m_train->m_ppath = NULL;
  2613. }
  2614. }
  2615. //-----------------------------------------------------------------------------
  2616. // Purpose: Normal track change
  2617. // Input : toggleState -
  2618. //-----------------------------------------------------------------------------
  2619. void CFuncTrackChange::UpdateAutoTargets( int toggleState )
  2620. {
  2621. if ( !m_trackTop || !m_trackBottom )
  2622. return;
  2623. if ( toggleState == TS_AT_TOP )
  2624. {
  2625. m_trackTop->RemoveSpawnFlags( SF_PATH_DISABLED );
  2626. }
  2627. else
  2628. {
  2629. m_trackTop->AddSpawnFlags( SF_PATH_DISABLED );
  2630. }
  2631. if ( toggleState == TS_AT_BOTTOM )
  2632. {
  2633. m_trackBottom->RemoveSpawnFlags( SF_PATH_DISABLED );
  2634. }
  2635. else
  2636. {
  2637. m_trackBottom->AddSpawnFlags( SF_PATH_DISABLED );
  2638. }
  2639. }
  2640. void CFuncTrackChange::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  2641. {
  2642. if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM )
  2643. return;
  2644. // If train is in "safe" area, but not on the elevator, play alarm sound
  2645. if ( m_toggle_state == TS_AT_TOP )
  2646. m_code = EvaluateTrain( m_trackTop );
  2647. else if ( m_toggle_state == TS_AT_BOTTOM )
  2648. m_code = EvaluateTrain( m_trackBottom );
  2649. else
  2650. m_code = TRAIN_BLOCKING;
  2651. if ( m_code == TRAIN_BLOCKING )
  2652. {
  2653. // Play alarm and return
  2654. EmitSound( "FuncTrackChange.Blocking" );
  2655. return;
  2656. }
  2657. // Otherwise, it's safe to move
  2658. // If at top, go down
  2659. // at bottom, go up
  2660. DisableUse();
  2661. if (m_toggle_state == TS_AT_TOP)
  2662. GoDown();
  2663. else
  2664. GoUp();
  2665. }
  2666. //
  2667. // Platform has hit bottom. Stops and waits forever.
  2668. //
  2669. void CFuncTrackChange::HitBottom( void )
  2670. {
  2671. BaseClass::HitBottom();
  2672. if ( m_code == TRAIN_FOLLOWING )
  2673. {
  2674. // UpdateTrain();
  2675. m_train->SetTrack( m_trackBottom );
  2676. }
  2677. SetMoveDone( NULL );
  2678. SetMoveDoneTime( -1 );
  2679. UpdateAutoTargets( m_toggle_state );
  2680. EnableUse();
  2681. }
  2682. //
  2683. // Platform has hit bottom. Stops and waits forever.
  2684. //
  2685. void CFuncTrackChange::HitTop( void )
  2686. {
  2687. BaseClass::HitTop();
  2688. if ( m_code == TRAIN_FOLLOWING )
  2689. {
  2690. // UpdateTrain();
  2691. m_train->SetTrack( m_trackTop );
  2692. }
  2693. // Don't let the plat go back down
  2694. SetMoveDone( NULL );
  2695. SetMoveDoneTime( -1 );
  2696. UpdateAutoTargets( m_toggle_state );
  2697. EnableUse();
  2698. }
  2699. class CFuncTrackAuto : public CFuncTrackChange
  2700. {
  2701. DECLARE_CLASS( CFuncTrackAuto, CFuncTrackChange );
  2702. public:
  2703. void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  2704. virtual void UpdateAutoTargets( int toggleState );
  2705. void TriggerTrackChange( inputdata_t &inputdata );
  2706. DECLARE_DATADESC();
  2707. };
  2708. BEGIN_DATADESC( CFuncTrackAuto )
  2709. DEFINE_INPUTFUNC( FIELD_VOID, "Trigger", TriggerTrackChange ),
  2710. END_DATADESC()
  2711. LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto );
  2712. // Auto track change
  2713. void CFuncTrackAuto::UpdateAutoTargets( int toggleState )
  2714. {
  2715. CPathTrack *pTarget, *pNextTarget;
  2716. if ( !m_trackTop || !m_trackBottom )
  2717. return;
  2718. if ( m_targetState == TS_AT_TOP )
  2719. {
  2720. pTarget = m_trackTop->GetNext();
  2721. pNextTarget = m_trackBottom->GetNext();
  2722. }
  2723. else
  2724. {
  2725. pTarget = m_trackBottom->GetNext();
  2726. pNextTarget = m_trackTop->GetNext();
  2727. }
  2728. if ( pTarget )
  2729. {
  2730. pTarget->RemoveSpawnFlags( SF_PATH_DISABLED );
  2731. if ( m_code == TRAIN_FOLLOWING && m_train && m_train->m_flSpeed == 0 )
  2732. {
  2733. m_train->SetSpeed( pTarget->m_flSpeed );
  2734. m_train->Use( this, this, USE_SET, 0 );
  2735. }
  2736. }
  2737. if ( pNextTarget )
  2738. {
  2739. pNextTarget->AddSpawnFlags( SF_PATH_DISABLED );
  2740. }
  2741. }
  2742. void CFuncTrackAuto::TriggerTrackChange ( inputdata_t &inputdata )
  2743. {
  2744. CPathTrack *pTarget;
  2745. if ( !UseEnabled() )
  2746. return;
  2747. if ( m_toggle_state == TS_AT_TOP )
  2748. pTarget = m_trackTop;
  2749. else if ( m_toggle_state == TS_AT_BOTTOM )
  2750. pTarget = m_trackBottom;
  2751. else
  2752. pTarget = NULL;
  2753. if ( inputdata.pActivator && FClassnameIs( inputdata.pActivator, "func_tracktrain" ) )
  2754. {
  2755. m_code = EvaluateTrain( pTarget );
  2756. // Safe to fire?
  2757. if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState )
  2758. {
  2759. DisableUse();
  2760. if (m_toggle_state == TS_AT_TOP)
  2761. GoDown();
  2762. else
  2763. GoUp();
  2764. }
  2765. }
  2766. else
  2767. {
  2768. if ( pTarget )
  2769. pTarget = pTarget->GetNext();
  2770. if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( USE_TOGGLE, m_targetState ) )
  2771. {
  2772. if ( m_targetState == TS_AT_TOP )
  2773. m_targetState = TS_AT_BOTTOM;
  2774. else
  2775. m_targetState = TS_AT_TOP;
  2776. }
  2777. UpdateAutoTargets( m_targetState );
  2778. }
  2779. }
  2780. void CFuncTrackAuto::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  2781. {
  2782. CPathTrack *pTarget;
  2783. if ( !UseEnabled() )
  2784. return;
  2785. if ( m_toggle_state == TS_AT_TOP )
  2786. pTarget = m_trackTop;
  2787. else if ( m_toggle_state == TS_AT_BOTTOM )
  2788. pTarget = m_trackBottom;
  2789. else
  2790. pTarget = NULL;
  2791. if ( FClassnameIs( pActivator, "func_tracktrain" ) )
  2792. {
  2793. m_code = EvaluateTrain( pTarget );
  2794. // Safe to fire?
  2795. if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState )
  2796. {
  2797. DisableUse();
  2798. if (m_toggle_state == TS_AT_TOP)
  2799. GoDown();
  2800. else
  2801. GoUp();
  2802. }
  2803. }
  2804. else
  2805. {
  2806. if ( pTarget )
  2807. pTarget = pTarget->GetNext();
  2808. if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) )
  2809. {
  2810. if ( m_targetState == TS_AT_TOP )
  2811. m_targetState = TS_AT_BOTTOM;
  2812. else
  2813. m_targetState = TS_AT_TOP;
  2814. }
  2815. UpdateAutoTargets( m_targetState );
  2816. }
  2817. }