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.

559 lines
15 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "cbase.h"
  7. #include "team_train_watcher.h"
  8. #include "team_control_point.h"
  9. #include "trains.h"
  10. #include "team_objectiveresource.h"
  11. #include "teamplayroundbased_gamerules.h"
  12. #include "team_control_point.h"
  13. #include "team_control_point_master.h"
  14. #include "engine/IEngineSound.h"
  15. #include "soundenvelope.h"
  16. #include "mp_shareddefs.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. static CSoundPatch *g_pAlarm = NULL;
  20. BEGIN_DATADESC( CTeamTrainWatcher )
  21. // Inputs.
  22. DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ),
  23. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetNumTrainCappers", InputSetNumTrainCappers ),
  24. DEFINE_INPUTFUNC( FIELD_VOID, "OnStartOvertime", InputOnStartOvertime ),
  25. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  26. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  27. // Outputs
  28. DEFINE_OUTPUT( m_OnTrainStartRecede, "OnTrainStartRecede" ),
  29. // key
  30. DEFINE_KEYFIELD( m_iszTrain, FIELD_STRING, "train" ),
  31. DEFINE_KEYFIELD( m_iszStartNode, FIELD_STRING, "start_node" ),
  32. DEFINE_KEYFIELD( m_iszGoalNode, FIELD_STRING, "goal_node" ),
  33. DEFINE_KEYFIELD( m_iszLinkedPathTracks[0], FIELD_STRING, "linked_pathtrack_1" ),
  34. DEFINE_KEYFIELD( m_iszLinkedCPs[0], FIELD_STRING, "linked_cp_1" ),
  35. DEFINE_KEYFIELD( m_iszLinkedPathTracks[1], FIELD_STRING, "linked_pathtrack_2" ),
  36. DEFINE_KEYFIELD( m_iszLinkedCPs[1], FIELD_STRING, "linked_cp_2" ),
  37. DEFINE_KEYFIELD( m_iszLinkedPathTracks[2], FIELD_STRING, "linked_pathtrack_3" ),
  38. DEFINE_KEYFIELD( m_iszLinkedCPs[2], FIELD_STRING, "linked_cp_3" ),
  39. DEFINE_KEYFIELD( m_iszLinkedPathTracks[3], FIELD_STRING, "linked_pathtrack_4" ),
  40. DEFINE_KEYFIELD( m_iszLinkedCPs[3], FIELD_STRING, "linked_cp_4" ),
  41. // can be up to 8 links
  42. // min speed for train hud speed levels
  43. DEFINE_KEYFIELD( m_flSpeedLevels[0], FIELD_FLOAT, "hud_min_speed_level_1" ),
  44. DEFINE_KEYFIELD( m_flSpeedLevels[1], FIELD_FLOAT, "hud_min_speed_level_2" ),
  45. DEFINE_KEYFIELD( m_flSpeedLevels[2], FIELD_FLOAT, "hud_min_speed_level_3" ),
  46. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  47. END_DATADESC()
  48. IMPLEMENT_SERVERCLASS_ST_NOBASE(CTeamTrainWatcher, DT_TeamTrainWatcher)
  49. SendPropFloat( SENDINFO( m_flTotalProgress ), 11, 0, 0.0f, 1.0f ),
  50. SendPropInt( SENDINFO( m_iTrainSpeedLevel ), 4 ),
  51. SendPropTime( SENDINFO( m_flRecedeTime ) ),
  52. SendPropInt( SENDINFO( m_nNumCappers ), 5, SPROP_UNSIGNED ),
  53. END_SEND_TABLE()
  54. LINK_ENTITY_TO_CLASS( team_train_watcher, CTeamTrainWatcher );
  55. CTeamTrainWatcher::CTeamTrainWatcher()
  56. {
  57. m_bDisabled = false;
  58. m_flRecedeTime = 0;
  59. m_bWaitingToRecede = false;
  60. m_bCapBlocked = false;
  61. m_flNextSpeakForwardConceptTime = 0;
  62. m_hAreaCap = NULL;
  63. }
  64. int CTeamTrainWatcher::UpdateTransmitState()
  65. {
  66. if ( m_bDisabled )
  67. {
  68. return SetTransmitState( FL_EDICT_DONTSEND );
  69. }
  70. return SetTransmitState( FL_EDICT_ALWAYS );
  71. }
  72. void CTeamTrainWatcher::InputRoundActivate( inputdata_t &inputdata )
  73. {
  74. StopCaptureAlarm();
  75. if ( !m_bDisabled )
  76. {
  77. WatcherActivate();
  78. }
  79. }
  80. void CTeamTrainWatcher::InputEnable( inputdata_t &inputdata )
  81. {
  82. StopCaptureAlarm();
  83. m_bDisabled = false;
  84. WatcherActivate();
  85. UpdateTransmitState();
  86. }
  87. void CTeamTrainWatcher::InputDisable( inputdata_t &inputdata )
  88. {
  89. StopCaptureAlarm();
  90. m_bDisabled = true;
  91. SetThink( NULL );
  92. m_bWaitingToRecede = false;
  93. UpdateTransmitState();
  94. }
  95. ConVar tf_escort_recede_time( "tf_escort_recede_time", "30", 0, "", true, 0, false, 0 );
  96. ConVar tf_escort_recede_time_overtime( "tf_escort_recede_time_overtime", "5", 0, "", true, 0, false, 0 );
  97. void CTeamTrainWatcher::InputSetNumTrainCappers( inputdata_t &inputdata )
  98. {
  99. if ( IsDisabled() )
  100. {
  101. return;
  102. }
  103. int iNumCappers = inputdata.value.Int();
  104. m_nNumCappers = iNumCappers;
  105. // inputdata.pCaller is hopefully an area capture
  106. // lets see if its blocked, and not start receding if it is
  107. CTriggerAreaCapture *pAreaCap = dynamic_cast<CTriggerAreaCapture *>(inputdata.pCaller);
  108. if ( pAreaCap )
  109. {
  110. m_bCapBlocked = pAreaCap->IsBlocked();
  111. m_hAreaCap = pAreaCap;
  112. }
  113. if ( iNumCappers <= 0 && !m_bCapBlocked )
  114. {
  115. if ( !m_bWaitingToRecede )
  116. {
  117. // start receding in [tf_escort_cart_recede_time] seconds
  118. m_bWaitingToRecede = true;
  119. if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->InOvertime() )
  120. {
  121. m_flRecedeTotalTime = tf_escort_recede_time_overtime.GetFloat();
  122. }
  123. else
  124. {
  125. m_flRecedeTotalTime = tf_escort_recede_time.GetFloat();
  126. }
  127. m_flRecedeStartTime = gpGlobals->curtime;
  128. m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime;
  129. }
  130. }
  131. else
  132. {
  133. // cancel receding
  134. m_bWaitingToRecede = false;
  135. m_flRecedeTime = 0;
  136. }
  137. }
  138. void CTeamTrainWatcher::InputOnStartOvertime( inputdata_t &inputdata )
  139. {
  140. // recalculate the recede time
  141. if ( m_bWaitingToRecede )
  142. {
  143. float flRecedeTimeRemaining = m_flRecedeTime - gpGlobals->curtime;
  144. float flOvertimeRecedeLen = tf_escort_recede_time_overtime.GetFloat();
  145. // drop to overtime recede time if it's more than that
  146. if ( flRecedeTimeRemaining > flOvertimeRecedeLen )
  147. {
  148. m_flRecedeTotalTime = flOvertimeRecedeLen;
  149. m_flRecedeStartTime = gpGlobals->curtime;
  150. m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime;
  151. }
  152. }
  153. }
  154. // ==========================================================
  155. // given a start node and a list of goal nodes
  156. // calculate the distance between each
  157. // ==========================================================
  158. void CTeamTrainWatcher::WatcherActivate( void )
  159. {
  160. m_flRecedeTime = 0;
  161. m_bWaitingToRecede = false;
  162. m_bCapBlocked = false;
  163. m_flNextSpeakForwardConceptTime = 0;
  164. m_hAreaCap = NULL;
  165. StopCaptureAlarm();
  166. // init our train
  167. m_hTrain = dynamic_cast<CFuncTrackTrain*>( gEntList.FindEntityByName( NULL, m_iszTrain ) );
  168. if ( !m_hTrain )
  169. {
  170. Warning("%s failed to find train named '%s'\n", GetClassname(), STRING(m_iszTrain) );
  171. }
  172. // init our array of path_tracks linked to control points
  173. m_iNumCPLinks = 0;
  174. int i;
  175. for ( i=0;i<MAX_CONTROL_POINTS;i++ )
  176. {
  177. CPathTrack *pPathTrack = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_iszLinkedPathTracks[i] ) );
  178. CTeamControlPoint *pCP = dynamic_cast<CTeamControlPoint*>( gEntList.FindEntityByName( NULL, m_iszLinkedCPs[i] ) );
  179. if ( pPathTrack && pCP )
  180. {
  181. m_CPLinks[m_iNumCPLinks].hPathTrack = pPathTrack;
  182. m_CPLinks[m_iNumCPLinks].hCP = pCP;
  183. m_CPLinks[m_iNumCPLinks].flDistanceFromStart = 0; // filled in when we parse the nodes
  184. m_CPLinks[m_iNumCPLinks].bAlertPlayed = false;
  185. m_iNumCPLinks++;
  186. }
  187. }
  188. // init our start and goal nodes
  189. m_hStartNode = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_iszStartNode ) );
  190. if ( !m_hStartNode )
  191. {
  192. Warning("%s failed to find path_track named '%s'\n", GetClassname(), STRING(m_iszStartNode) );
  193. }
  194. m_hGoalNode = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_iszGoalNode ) );
  195. if ( !m_hGoalNode )
  196. {
  197. Warning("%s failed to find path_track named '%s'\n", GetClassname(), STRING(m_iszGoalNode) );
  198. }
  199. m_flTotalPathDistance = 0.0f;
  200. if( m_hStartNode.Get() && m_hGoalNode.Get() )
  201. {
  202. CPathTrack *pNode = m_hStartNode;
  203. CPathTrack *pPrev = pNode;
  204. pNode = pNode->GetNext();
  205. // don't check the start node for links. If it's linked, it will have 0 distance anyway
  206. while ( pNode )
  207. {
  208. Vector dir = pNode->GetLocalOrigin() - pPrev->GetLocalOrigin();
  209. float length = dir.Length();
  210. m_flTotalPathDistance += length;
  211. // if pNode is one of our cp nodes, store its distance from m_hStartNode
  212. for ( i=0;i<m_iNumCPLinks;i++ )
  213. {
  214. if ( m_CPLinks[i].hPathTrack == pNode )
  215. {
  216. m_CPLinks[i].flDistanceFromStart = m_flTotalPathDistance;
  217. break;
  218. }
  219. }
  220. if ( pNode == m_hGoalNode )
  221. break;
  222. pPrev = pNode;
  223. pNode = pNode->GetNext();
  224. }
  225. }
  226. // We have total distance and increments in our links array
  227. for ( i=0;i<m_iNumCPLinks;i++ )
  228. {
  229. int iCPIndex = m_CPLinks[i].hCP.Get()->GetPointIndex();
  230. ObjectiveResource()->SetTrainPathDistance( iCPIndex, m_CPLinks[i].flDistanceFromStart / m_flTotalPathDistance );
  231. DevMsg( "link %d = %.2f\n", iCPIndex, m_CPLinks[i].flDistanceFromStart / m_flTotalPathDistance );
  232. }
  233. SetThink( &CTeamTrainWatcher::WatcherThink );
  234. SetNextThink( gpGlobals->curtime + 0.1f );
  235. }
  236. void CTeamTrainWatcher::StopCaptureAlarm( void )
  237. {
  238. if ( g_pAlarm )
  239. {
  240. CSoundEnvelopeController::GetController().SoundDestroy( g_pAlarm );
  241. g_pAlarm = NULL;
  242. }
  243. }
  244. void CTeamTrainWatcher::StartCaptureAlarm( CTeamControlPoint *pPoint )
  245. {
  246. StopCaptureAlarm();
  247. if ( pPoint )
  248. {
  249. CReliableBroadcastRecipientFilter filter;
  250. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  251. g_pAlarm = controller.SoundCreate( filter, pPoint->entindex(), CHAN_STATIC, TF_TRAIN_ALARM, ATTN_NORM );
  252. controller.Play( g_pAlarm, 1.0, PITCH_NORM );
  253. }
  254. }
  255. void CTeamTrainWatcher::PlayCaptureAlert( CTeamControlPoint *pPoint, bool bFinalPointInMap )
  256. {
  257. if ( !pPoint )
  258. return;
  259. if ( TeamplayRoundBasedRules() )
  260. {
  261. TeamplayRoundBasedRules()->PlayTrainCaptureAlert( pPoint, bFinalPointInMap );
  262. }
  263. }
  264. void CTeamTrainWatcher::WatcherThink( void )
  265. {
  266. if ( m_bWaitingToRecede )
  267. {
  268. if ( m_flRecedeTime < gpGlobals->curtime )
  269. {
  270. m_bWaitingToRecede = false;
  271. // don't actually recede in overtime
  272. if ( TeamplayRoundBasedRules() && !TeamplayRoundBasedRules()->InOvertime() )
  273. {
  274. // fire recede output
  275. m_OnTrainStartRecede.FireOutput( this, this );
  276. }
  277. }
  278. }
  279. if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->State_Get() == GR_STATE_TEAM_WIN )
  280. {
  281. StopCaptureAlarm();
  282. }
  283. // given its next node, we can walk the nodes and find the linear
  284. // distance to the next cp node, or to the goal node
  285. CFuncTrackTrain *pTrain = m_hTrain;
  286. if ( pTrain )
  287. {
  288. int iOldTrainSpeedLevel = m_iTrainSpeedLevel;
  289. // how fast is the train moving?
  290. float flSpeed = pTrain->GetDesiredSpeed();
  291. // divide speed into regions
  292. // anything negative is -1
  293. if ( flSpeed < 0 )
  294. {
  295. m_iTrainSpeedLevel = -1;
  296. // even though our desired speed might be negative,
  297. // our actual speed might be zero if we're at a dead end...
  298. // this will turn off the < image when the train is done moving backwards
  299. if ( pTrain->GetCurrentSpeed() == 0 )
  300. {
  301. m_iTrainSpeedLevel = 0;
  302. }
  303. }
  304. else if ( flSpeed > m_flSpeedLevels[2] )
  305. {
  306. m_iTrainSpeedLevel = 3;
  307. }
  308. else if ( flSpeed > m_flSpeedLevels[1] )
  309. {
  310. m_iTrainSpeedLevel = 2;
  311. }
  312. else if ( flSpeed > m_flSpeedLevels[0] )
  313. {
  314. m_iTrainSpeedLevel = 1;
  315. }
  316. else
  317. {
  318. m_iTrainSpeedLevel = 0;
  319. }
  320. // play any concepts that we might need to play
  321. if ( m_iTrainSpeedLevel != iOldTrainSpeedLevel )
  322. {
  323. if ( TeamplayRoundBasedRules() )
  324. {
  325. if ( m_iTrainSpeedLevel == 0 && iOldTrainSpeedLevel != 0 )
  326. {
  327. TeamplayRoundBasedRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_CART_STOP );
  328. m_flNextSpeakForwardConceptTime = 0;
  329. }
  330. else if ( m_iTrainSpeedLevel < 0 && iOldTrainSpeedLevel == 0 )
  331. {
  332. TeamplayRoundBasedRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_CART_MOVING_BACKWARD );
  333. m_flNextSpeakForwardConceptTime = 0;
  334. }
  335. }
  336. }
  337. if ( m_iTrainSpeedLevel > 0 && m_flNextSpeakForwardConceptTime < gpGlobals->curtime )
  338. {
  339. if ( m_hAreaCap.Get() )
  340. {
  341. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  342. {
  343. CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) );
  344. if ( pPlayer )
  345. {
  346. if ( m_hAreaCap->IsTouching( pPlayer ) )
  347. {
  348. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_CART_MOVING_FORWARD );
  349. }
  350. }
  351. }
  352. }
  353. m_flNextSpeakForwardConceptTime = gpGlobals->curtime + 3.0;
  354. }
  355. // what percent progress are we at?
  356. CPathTrack *pNode = ( pTrain->m_ppath ) ? pTrain->m_ppath->GetNext() : NULL;
  357. // if we're moving backwards, GetNext is going to be wrong
  358. if ( flSpeed < 0 )
  359. {
  360. pNode = pTrain->m_ppath;
  361. }
  362. if ( pNode )
  363. {
  364. float flDistanceToGoal = 0;
  365. // distance to next node
  366. Vector vecDir = pNode->GetLocalOrigin() - pTrain->GetLocalOrigin();
  367. flDistanceToGoal = vecDir.Length();
  368. // distance of next node to goal node
  369. if ( pNode && pNode != m_hGoalNode )
  370. {
  371. // walk this until we get to goal node, or a dead end
  372. CPathTrack *pPrev = pNode;
  373. pNode = pNode->GetNext();
  374. while ( pNode )
  375. {
  376. vecDir = pNode->GetLocalOrigin() - pPrev->GetLocalOrigin();
  377. flDistanceToGoal += vecDir.Length();
  378. if ( pNode == m_hGoalNode )
  379. break;
  380. pPrev = pNode;
  381. pNode = pNode->GetNext();
  382. }
  383. }
  384. if ( m_flTotalPathDistance <= 0 )
  385. {
  386. Assert( !"No path distance in team_train_watcher\n" );
  387. m_flTotalPathDistance = 1;
  388. }
  389. m_flTotalProgress = clamp( 1.0 - ( flDistanceToGoal / m_flTotalPathDistance ), 0.0, 1.0 );
  390. float flTrainDistanceFromStart = m_flTotalPathDistance - flDistanceToGoal;
  391. // play alert sounds if necessary
  392. for ( int iCount = 0 ; iCount < m_iNumCPLinks ; iCount++ )
  393. {
  394. if ( flTrainDistanceFromStart < m_CPLinks[iCount].flDistanceFromStart - TF_TRAIN_ALERT_DISTANCE )
  395. {
  396. // back up twice the alert distance before resetting our flag to play the warning again
  397. if ( flTrainDistanceFromStart < m_CPLinks[iCount].flDistanceFromStart - ( TF_TRAIN_ALERT_DISTANCE * 2 ) )
  398. {
  399. // reset our alert flag
  400. m_CPLinks[iCount].bAlertPlayed = false;
  401. }
  402. }
  403. else
  404. {
  405. if ( flTrainDistanceFromStart < m_CPLinks[iCount].flDistanceFromStart && !m_CPLinks[iCount].bAlertPlayed )
  406. {
  407. m_CPLinks[iCount].bAlertPlayed = true;
  408. CTeamControlPoint *pLastPoint = NULL;
  409. CTeamControlPoint *pCurrentPoint = m_CPLinks[iCount].hCP.Get();
  410. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  411. if ( pMaster )
  412. {
  413. pLastPoint = pMaster->GetControlPoint( pMaster->GetNumPoints() - 1 );
  414. }
  415. bool bFinalPointInMap = pLastPoint && ( pLastPoint == pCurrentPoint );
  416. PlayCaptureAlert( pCurrentPoint, bFinalPointInMap );
  417. }
  418. }
  419. }
  420. // check to see if we need to start or stop the alarm
  421. if ( flDistanceToGoal <= TF_TRAIN_ALARM_DISTANCE )
  422. {
  423. if ( !g_pAlarm && m_iNumCPLinks > 0 )
  424. {
  425. // start the alarm at the final point
  426. StartCaptureAlarm( m_CPLinks[m_iNumCPLinks-1].hCP.Get() );
  427. }
  428. }
  429. else
  430. {
  431. if ( g_pAlarm )
  432. {
  433. StopCaptureAlarm();
  434. }
  435. }
  436. }
  437. }
  438. SetNextThink( gpGlobals->curtime + 0.1 );
  439. }
  440. CBaseEntity *CTeamTrainWatcher::GetTrainEntity( void )
  441. {
  442. return m_hTrain.Get();
  443. }
  444. bool CTeamTrainWatcher::TimerMayExpire( void )
  445. {
  446. if ( IsDisabled() )
  447. {
  448. return true;
  449. }
  450. // Still in overtime if we're waiting to recede
  451. if ( m_bWaitingToRecede )
  452. return false;
  453. // capture blocked so we're not receding, but game shouldn't end
  454. if ( m_bCapBlocked )
  455. return false;
  456. // not waiting, so we're capping, in which case the area capture
  457. // will not let us expire
  458. return true;
  459. }