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.

1295 lines
39 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "team_objectiveresource.h"
  9. #include "team_control_point_master.h"
  10. #include "teamplayroundbased_gamerules.h"
  11. // NOTE: This has to be the last file included!
  12. #include "tier0/memdbgon.h"
  13. BEGIN_DATADESC( CTeamControlPointMaster )
  14. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  15. DEFINE_KEYFIELD( m_iszCapLayoutInHUD, FIELD_STRING, "caplayout" ),
  16. DEFINE_KEYFIELD( m_iInvalidCapWinner, FIELD_INTEGER, "cpm_restrict_team_cap_win" ),
  17. DEFINE_KEYFIELD( m_bSwitchTeamsOnWin, FIELD_BOOLEAN, "switch_teams" ),
  18. DEFINE_KEYFIELD( m_bScorePerCapture, FIELD_BOOLEAN, "score_style" ),
  19. DEFINE_KEYFIELD( m_bPlayAllRounds, FIELD_BOOLEAN, "play_all_rounds" ),
  20. DEFINE_KEYFIELD( m_flPartialCapturePointsRate, FIELD_FLOAT, "partial_cap_points_rate" ),
  21. // DEFINE_FIELD( m_ControlPoints, CUtlMap < int , CTeamControlPoint * > ),
  22. // DEFINE_FIELD( m_bFoundPoints, FIELD_BOOLEAN ),
  23. // DEFINE_FIELD( m_ControlPointRounds, CUtlVector < CTeamControlPointRound * > ),
  24. // DEFINE_FIELD( m_iCurrentRoundIndex, FIELD_INTEGER ),
  25. // DEFINE_ARRAY( m_iszTeamBaseIcons, FIELD_STRING, MAX_TEAMS ),
  26. // DEFINE_ARRAY( m_iTeamBaseIcons, FIELD_INTEGER, MAX_TEAMS ),
  27. // DEFINE_FIELD( m_bFirstRoundAfterRestart, FIELD_BOOLEAN ),
  28. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  29. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  30. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetWinner", InputSetWinner ),
  31. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetWinnerAndForceCaps", InputSetWinnerAndForceCaps ),
  32. DEFINE_INPUTFUNC( FIELD_VOID, "RoundSpawn", InputRoundSpawn ),
  33. DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ),
  34. DEFINE_INPUTFUNC( FIELD_STRING, "SetCapLayout", InputSetCapLayout ),
  35. DEFINE_FUNCTION( CPMThink ),
  36. DEFINE_OUTPUT( m_OnWonByTeam1, "OnWonByTeam1" ),
  37. DEFINE_OUTPUT( m_OnWonByTeam2, "OnWonByTeam2" ),
  38. END_DATADESC()
  39. LINK_ENTITY_TO_CLASS( team_control_point_master, CTeamControlPointMaster );
  40. ConVar mp_time_between_capscoring( "mp_time_between_capscoring", "30", FCVAR_GAMEDLL, "Delay between scoring of owned capture points.", true, 1, false, 0 );
  41. // sort function for the list of control_point_rounds (we're sorting them by priority...highest first)
  42. int ControlPointRoundSort( CTeamControlPointRound* const *p1, CTeamControlPointRound* const *p2 )
  43. {
  44. // check the priority
  45. if ( (*p2)->GetPriorityValue() > (*p1)->GetPriorityValue() )
  46. {
  47. return 1;
  48. }
  49. return -1;
  50. }
  51. //-----------------------------------------------------------------------------
  52. // Purpose: init
  53. //-----------------------------------------------------------------------------
  54. CTeamControlPointMaster::CTeamControlPointMaster()
  55. {
  56. m_flPartialCapturePointsRate = 0.0f;
  57. }
  58. //-----------------------------------------------------------------------------
  59. // Purpose:
  60. //-----------------------------------------------------------------------------
  61. void CTeamControlPointMaster::Spawn( void )
  62. {
  63. Precache();
  64. SetTouch( NULL );
  65. m_bFoundPoints = false;
  66. SetDefLessFunc( m_ControlPoints );
  67. m_iCurrentRoundIndex = -1;
  68. m_bFirstRoundAfterRestart = true;
  69. BaseClass::Spawn();
  70. if ( g_hControlPointMasters.Find(this) == g_hControlPointMasters.InvalidIndex() )
  71. {
  72. g_hControlPointMasters.AddToTail( this );
  73. }
  74. }
  75. //-----------------------------------------------------------------------------
  76. // Purpose:
  77. //-----------------------------------------------------------------------------
  78. void CTeamControlPointMaster::UpdateOnRemove( void )
  79. {
  80. BaseClass::UpdateOnRemove();
  81. g_hControlPointMasters.FindAndRemove( this );
  82. }
  83. //-----------------------------------------------------------------------------
  84. // Purpose:
  85. //-----------------------------------------------------------------------------
  86. bool CTeamControlPointMaster::KeyValue( const char *szKeyName, const char *szValue )
  87. {
  88. if ( !Q_strncmp( szKeyName, "team_base_icon_", 15 ) )
  89. {
  90. int iTeam = atoi(szKeyName+15);
  91. Assert( iTeam >= 0 && iTeam < MAX_TEAMS );
  92. m_iszTeamBaseIcons[iTeam] = AllocPooledString(szValue);
  93. }
  94. else
  95. {
  96. return BaseClass::KeyValue( szKeyName, szValue );
  97. }
  98. return true;
  99. }
  100. //-----------------------------------------------------------------------------
  101. // Purpose:
  102. //-----------------------------------------------------------------------------
  103. void CTeamControlPointMaster::Precache( void )
  104. {
  105. for ( int i = 0; i < MAX_TEAMS; i++ )
  106. {
  107. if ( m_iszTeamBaseIcons[i] != NULL_STRING )
  108. {
  109. PrecacheMaterial( STRING( m_iszTeamBaseIcons[i] ) );
  110. m_iTeamBaseIcons[i] = GetMaterialIndex( STRING( m_iszTeamBaseIcons[i] ) );
  111. Assert( m_iTeamBaseIcons[i] != 0 );
  112. }
  113. }
  114. BaseClass::Precache();
  115. }
  116. //-----------------------------------------------------------------------------
  117. // Purpose:
  118. //-----------------------------------------------------------------------------
  119. void CTeamControlPointMaster::Activate( void )
  120. {
  121. BaseClass::Activate();
  122. // Find control points right away. This allows client hud elements to know the
  123. // number & starting state of control points before the game actually starts.
  124. FindControlPoints();
  125. FindControlPointRounds();
  126. SetBaseControlPoints();
  127. }
  128. //-----------------------------------------------------------------------------
  129. // Purpose:
  130. //-----------------------------------------------------------------------------
  131. void CTeamControlPointMaster::RoundRespawn( void )
  132. {
  133. }
  134. //-----------------------------------------------------------------------------
  135. // Purpose:
  136. //-----------------------------------------------------------------------------
  137. void CTeamControlPointMaster::Reset( void )
  138. {
  139. }
  140. //-----------------------------------------------------------------------------
  141. // Purpose:
  142. //-----------------------------------------------------------------------------
  143. bool CTeamControlPointMaster::FindControlPoints( void )
  144. {
  145. //go through all the points
  146. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, GetControlPointName() );
  147. int numFound = 0;
  148. while( pEnt )
  149. {
  150. CTeamControlPoint *pPoint = assert_cast<CTeamControlPoint *>(pEnt);
  151. if( pPoint->IsActive() )
  152. {
  153. int index = pPoint->GetPointIndex();
  154. Assert( index >= 0 );
  155. if( m_ControlPoints.Find( index ) == m_ControlPoints.InvalidIndex())
  156. {
  157. DevMsg( 2, "**** Adding control point %s with index %d to control point master\n", pPoint->GetName(), index );
  158. m_ControlPoints.Insert( index, pPoint );
  159. numFound++;
  160. }
  161. else
  162. {
  163. Warning( "!!!!\nMultiple control points with the same index, duplicates ignored\n!!!!\n" );
  164. UTIL_Remove( pPoint );
  165. }
  166. }
  167. pEnt = gEntList.FindEntityByClassname( pEnt, GetControlPointName() );
  168. }
  169. if( numFound > MAX_CONTROL_POINTS )
  170. {
  171. Warning( "Too many control points! Max is %d\n", MAX_CONTROL_POINTS );
  172. }
  173. //Remap the indeces of the control points so they are 0-based
  174. //======================
  175. unsigned int j;
  176. bool bHandled[MAX_CONTROL_POINTS];
  177. memset( bHandled, 0, sizeof(bHandled) );
  178. unsigned int numPoints = m_ControlPoints.Count();
  179. unsigned int newIndex = 0;
  180. while( newIndex < numPoints )
  181. {
  182. //Find the lowest numbered, unhandled point
  183. int lowestIndex = -1;
  184. int lowestValue = 999;
  185. //find the lowest unhandled index
  186. for( j=0; j<numPoints; j++ )
  187. {
  188. if( !bHandled[j] && m_ControlPoints[j]->GetPointIndex() < lowestValue )
  189. {
  190. lowestIndex = j;
  191. lowestValue = m_ControlPoints[j]->GetPointIndex();
  192. }
  193. }
  194. //Don't examine this point again
  195. bHandled[lowestIndex] = true;
  196. //Give it its new index
  197. m_ControlPoints[lowestIndex]->SetPointIndex( newIndex );
  198. newIndex++;
  199. }
  200. if( m_ControlPoints.Count() == 0 )
  201. {
  202. Warning( "Error! No control points found in map!\n");
  203. return false;
  204. }
  205. // Now setup the objective resource
  206. ObjectiveResource()->SetNumControlPoints( m_ControlPoints.Count() );
  207. for ( unsigned int i = 0; i < m_ControlPoints.Count(); i++ )
  208. {
  209. CTeamControlPoint *pPoint = m_ControlPoints[i];
  210. int iPointIndex = m_ControlPoints[i]->GetPointIndex();
  211. ObjectiveResource()->SetOwningTeam( iPointIndex, pPoint->GetOwner() );
  212. ObjectiveResource()->SetCPVisible( iPointIndex, pPoint->PointIsVisible() );
  213. ObjectiveResource()->SetCPPosition( iPointIndex, pPoint->GetAbsOrigin() );
  214. ObjectiveResource()->SetWarnOnCap( iPointIndex, pPoint->GetWarnOnCap() );
  215. ObjectiveResource()->SetWarnSound( iPointIndex, pPoint->GetWarnSound() );
  216. for ( int team = 0; team < GetNumberOfTeams(); team++ )
  217. {
  218. ObjectiveResource()->SetCPIcons( iPointIndex, team, pPoint->GetHudIconIndexForTeam(team) );
  219. ObjectiveResource()->SetCPOverlays( iPointIndex, team, pPoint->GetHudOverlayIndexForTeam(team) );
  220. for ( int prevpoint = 0; prevpoint < MAX_PREVIOUS_POINTS; prevpoint++ )
  221. {
  222. ObjectiveResource()->SetPreviousPoint( iPointIndex, team, prevpoint, pPoint->GetPreviousPointForTeam(team, prevpoint) );
  223. }
  224. }
  225. }
  226. return true;
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Purpose:
  230. //-----------------------------------------------------------------------------
  231. void CTeamControlPointMaster::SetBaseControlPoints( void )
  232. {
  233. for ( int team = 0; team < GetNumberOfTeams(); team++ )
  234. {
  235. ObjectiveResource()->SetTeamBaseIcons( team, m_iTeamBaseIcons[team] );
  236. ObjectiveResource()->SetBaseCP( GetBaseControlPoint(team), team );
  237. }
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Purpose:
  241. //-----------------------------------------------------------------------------
  242. bool CTeamControlPointMaster::FindControlPointRounds( void )
  243. {
  244. bool bFoundRounds = false;
  245. m_ControlPointRounds.RemoveAll();
  246. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, GetControlPointRoundName() );
  247. while( pEnt )
  248. {
  249. CTeamControlPointRound *pRound = assert_cast<CTeamControlPointRound *>( pEnt );
  250. if( pRound && ( m_ControlPointRounds.Find( pRound ) == m_ControlPointRounds.InvalidIndex() ) )
  251. {
  252. DevMsg( 2, "**** Adding control point round %s to control point master\n", pRound->GetEntityName().ToCStr() );
  253. m_ControlPointRounds.AddToHead( pRound );
  254. }
  255. pEnt = gEntList.FindEntityByClassname( pEnt, GetControlPointRoundName() );
  256. }
  257. if ( m_ControlPointRounds.Count() > 0 )
  258. {
  259. // sort them in our list by priority (highest priority first)
  260. m_ControlPointRounds.Sort( ControlPointRoundSort );
  261. bFoundRounds = true;
  262. }
  263. if ( g_pObjectiveResource )
  264. {
  265. g_pObjectiveResource->SetPlayingMiniRounds( bFoundRounds );
  266. g_pObjectiveResource->SetCapLayoutInHUD( STRING(m_iszCapLayoutInHUD) );
  267. }
  268. return bFoundRounds;
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Purpose:
  272. //-----------------------------------------------------------------------------
  273. bool CTeamControlPointMaster::PointCanBeCapped( CTeamControlPoint *pPoint )
  274. {
  275. // are we playing a round and is this point in the round?
  276. if ( m_ControlPointRounds.Count() > 0 && m_iCurrentRoundIndex != -1 )
  277. {
  278. return m_ControlPointRounds[m_iCurrentRoundIndex]->IsControlPointInRound( pPoint );
  279. }
  280. return true;
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose:
  284. //-----------------------------------------------------------------------------
  285. bool CTeamControlPointMaster::FindControlPointRoundToPlay( void )
  286. {
  287. for ( int i = 0 ; i < m_ControlPointRounds.Count() ; ++i )
  288. {
  289. CTeamControlPointRound *pRound = m_ControlPointRounds[i];
  290. if ( pRound )
  291. {
  292. if ( pRound->IsPlayable() )
  293. {
  294. // we found one that's playable
  295. return true;
  296. }
  297. }
  298. }
  299. return false;
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose:
  303. //-----------------------------------------------------------------------------
  304. bool CTeamControlPointMaster::SelectSpecificRound( void )
  305. {
  306. CTeamControlPointRound *pRound = NULL;
  307. CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
  308. if ( pRules )
  309. {
  310. if ( pRules->GetRoundToPlayNext() != NULL_STRING )
  311. {
  312. // do we have the name of a round?
  313. pRound = dynamic_cast<CTeamControlPointRound*>( gEntList.FindEntityByName( NULL, STRING( pRules->GetRoundToPlayNext() ) ) );
  314. if ( pRound )
  315. {
  316. if ( ( m_ControlPointRounds.Find( pRound )== m_ControlPointRounds.InvalidIndex() ) ||
  317. ( !pRound->IsPlayable() && !pRound->MakePlayable() ) )
  318. {
  319. pRound = NULL;
  320. }
  321. }
  322. pRules->SetRoundToPlayNext( NULL_STRING );
  323. }
  324. }
  325. // do we have a round to play?
  326. if ( pRound )
  327. {
  328. m_iCurrentRoundIndex = m_ControlPointRounds.Find( pRound );
  329. m_ControlPointRounds[m_iCurrentRoundIndex]->SelectedToPlay();
  330. if ( pRules )
  331. {
  332. pRules->SetRoundOverlayDetails();
  333. }
  334. FireRoundStartOutput();
  335. DevMsg( 2, "**** Selected round %s to play\n", m_ControlPointRounds[m_iCurrentRoundIndex]->GetEntityName().ToCStr() );
  336. if ( !pRules->IsInWaitingForPlayers() )
  337. {
  338. UTIL_LogPrintf( "World triggered \"Mini_Round_Selected\" (round \"%s\")\n", m_ControlPointRounds[m_iCurrentRoundIndex]->GetEntityName().ToCStr() );
  339. UTIL_LogPrintf( "World triggered \"Mini_Round_Start\"\n" );
  340. }
  341. return true;
  342. }
  343. return false;
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Purpose:
  347. //-----------------------------------------------------------------------------
  348. void CTeamControlPointMaster::RegisterRoundBeingPlayed( void )
  349. {
  350. // let the game rules know what round we're playing
  351. CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
  352. if ( pRules )
  353. {
  354. string_t iszEntityName = m_ControlPointRounds[m_iCurrentRoundIndex]->GetEntityName();
  355. pRules->AddPlayedRound( iszEntityName );
  356. if ( m_bFirstRoundAfterRestart )
  357. {
  358. pRules->SetFirstRoundPlayed( iszEntityName );
  359. m_bFirstRoundAfterRestart = false;
  360. }
  361. }
  362. }
  363. //-----------------------------------------------------------------------------
  364. // Purpose:
  365. //-----------------------------------------------------------------------------
  366. bool CTeamControlPointMaster::GetControlPointRoundToPlay( void )
  367. {
  368. int i = 0;
  369. // are we trying to pick a specific round?
  370. if ( SelectSpecificRound() )
  371. {
  372. SetBaseControlPoints();
  373. RegisterRoundBeingPlayed();
  374. return true;
  375. }
  376. // rounds are sorted with the higher priority rounds first
  377. for ( i = 0 ; i < m_ControlPointRounds.Count() ; ++i )
  378. {
  379. CTeamControlPointRound *pRound = m_ControlPointRounds[i];
  380. if ( pRound )
  381. {
  382. if ( pRound->IsPlayable() )
  383. {
  384. // we found one that's playable
  385. break;
  386. }
  387. }
  388. }
  389. if ( i >= m_ControlPointRounds.Count() || m_ControlPointRounds[i] == NULL )
  390. {
  391. // we didn't find one to play
  392. m_iCurrentRoundIndex = -1;
  393. return false;
  394. }
  395. // we have a priority value, now we need to randomly pick a round with this priority that's playable
  396. int nPriority = m_ControlPointRounds[i]->GetPriorityValue();
  397. CUtlVector<int> nRounds;
  398. CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
  399. string_t iszLastRoundPlayed = pRules ? pRules->GetLastPlayedRound() : NULL_STRING;
  400. int iLastRoundPlayed = -1;
  401. string_t iszFirstRoundPlayed = pRules ? pRules->GetFirstRoundPlayed() : NULL_STRING;
  402. int iFirstRoundPlayed = -1; // after a full restart
  403. // loop through and find the rounds with this priority value
  404. for ( i = 0 ; i < m_ControlPointRounds.Count() ; ++i )
  405. {
  406. CTeamControlPointRound *pRound = m_ControlPointRounds[i];
  407. if ( pRound )
  408. {
  409. string_t iszRoundName = pRound->GetEntityName();
  410. if ( pRound->IsPlayable() && pRound->GetPriorityValue() == nPriority )
  411. {
  412. if ( iszLastRoundPlayed == iszRoundName ) // is this the last round we played?
  413. {
  414. iLastRoundPlayed = i;
  415. }
  416. if ( m_bFirstRoundAfterRestart )
  417. {
  418. // is this the first round we played after the last full restart?
  419. if ( ( iszFirstRoundPlayed != NULL_STRING ) && ( iszFirstRoundPlayed == iszRoundName ) )
  420. {
  421. iFirstRoundPlayed = i;
  422. }
  423. }
  424. nRounds.AddToHead(i);
  425. }
  426. }
  427. }
  428. if ( nRounds.Count() <= 0 )
  429. {
  430. // we didn't find one to play
  431. m_iCurrentRoundIndex = -1;
  432. return false;
  433. }
  434. // if we have more than one and the last played round is in our list, remove it
  435. if ( nRounds.Count() > 1 )
  436. {
  437. if ( iLastRoundPlayed != -1 )
  438. {
  439. int elementIndex = nRounds.Find( iLastRoundPlayed );
  440. nRounds.Remove( elementIndex );
  441. }
  442. }
  443. // if this is the first round after a full restart, we still have more than one round in our list,
  444. // and the first played round (after the last full restart) is in our list, remove it
  445. if ( m_bFirstRoundAfterRestart )
  446. {
  447. if ( nRounds.Count() > 1 )
  448. {
  449. if ( iFirstRoundPlayed != -1 )
  450. {
  451. int elementIndex = nRounds.Find( iFirstRoundPlayed );
  452. nRounds.Remove( elementIndex );
  453. }
  454. }
  455. }
  456. // pick one to play but try to avoid picking one that we have recently played if there are other rounds to play
  457. int index = random->RandomInt( 0, nRounds.Count() - 1 );
  458. // only need to check this if we have more than one round with this priority value
  459. if ( pRules && nRounds.Count() > 1 )
  460. {
  461. // keep picking a round until we find one that's not a previously played round
  462. // or until we don't have any more rounds to choose from
  463. while ( pRules->IsPreviouslyPlayedRound( m_ControlPointRounds[ nRounds[ index ] ]->GetEntityName() ) &&
  464. nRounds.Count() > 1 )
  465. {
  466. nRounds.Remove( index ); // we have played this round recently so get it out of the list
  467. index = random->RandomInt( 0, nRounds.Count() - 1 );
  468. }
  469. }
  470. // pick one to play and fire its OnSelected output
  471. m_iCurrentRoundIndex = nRounds[ index ];
  472. m_ControlPointRounds[m_iCurrentRoundIndex]->SelectedToPlay();
  473. if ( pRules )
  474. {
  475. pRules->SetRoundOverlayDetails();
  476. }
  477. FireRoundStartOutput();
  478. DevMsg( 2, "**** Selected round %s to play\n", m_ControlPointRounds[m_iCurrentRoundIndex]->GetEntityName().ToCStr() );
  479. if ( !pRules->IsInWaitingForPlayers() )
  480. {
  481. UTIL_LogPrintf( "World triggered \"Mini_Round_Selected\" (round \"%s\")\n", m_ControlPointRounds[m_iCurrentRoundIndex]->GetEntityName().ToCStr() );
  482. UTIL_LogPrintf( "World triggered \"Mini_Round_Start\"\n" );
  483. }
  484. SetBaseControlPoints();
  485. RegisterRoundBeingPlayed();
  486. return true;
  487. }
  488. //-----------------------------------------------------------------------------
  489. // Purpose: Called every 0.1 seconds and checks the status of all the control points
  490. // if one team owns them all, it gives points and resets
  491. // Think also gives the time based points at the specified time intervals
  492. //-----------------------------------------------------------------------------
  493. void CTeamControlPointMaster::CPMThink( void )
  494. {
  495. if ( m_bDisabled || !TeamplayGameRules()->PointsMayBeCaptured() )
  496. {
  497. SetContextThink( &CTeamControlPointMaster::CPMThink, gpGlobals->curtime + 0.2, CPM_THINK );
  498. return;
  499. }
  500. // If we call this from team_control_point, this function should never
  501. // trigger a win. but we'll leave it here just in case.
  502. CheckWinConditions();
  503. // the next time we 'think'
  504. SetContextThink( &CTeamControlPointMaster::CPMThink, gpGlobals->curtime + 0.2, CPM_THINK );
  505. }
  506. //-----------------------------------------------------------------------------
  507. // Purpose:
  508. //-----------------------------------------------------------------------------
  509. void CTeamControlPointMaster::CheckWinConditions( void )
  510. {
  511. if ( m_bDisabled )
  512. return;
  513. if ( m_ControlPointRounds.Count() > 0 )
  514. {
  515. if ( m_iCurrentRoundIndex != -1 )
  516. {
  517. // Check the current round to see if one team is a winner yet
  518. int iWinners = m_ControlPointRounds[m_iCurrentRoundIndex]->CheckWinConditions();
  519. if ( iWinners != -1 && iWinners >= FIRST_GAME_TEAM )
  520. {
  521. bool bForceMapReset = ( FindControlPointRoundToPlay() == false ); // are there any more rounds to play?
  522. if ( !bForceMapReset )
  523. {
  524. // we have more rounds to play
  525. TeamplayGameRules()->SetWinningTeam( iWinners, WINREASON_ALL_POINTS_CAPTURED, bForceMapReset );
  526. }
  527. else
  528. {
  529. // we have played all of the available rounds
  530. TeamplayGameRules()->SetWinningTeam( iWinners, WINREASON_ALL_POINTS_CAPTURED, bForceMapReset, m_bSwitchTeamsOnWin );
  531. }
  532. FireTeamWinOutput( iWinners );
  533. }
  534. }
  535. }
  536. else
  537. {
  538. // Check that the points aren't all held by one team...if they are
  539. // this will reset the round and will reset all the points
  540. int iWinners = TeamOwnsAllPoints();
  541. if ( ( iWinners >= FIRST_GAME_TEAM ) &&
  542. ( iWinners != m_iInvalidCapWinner ) )
  543. {
  544. TeamplayGameRules()->SetWinningTeam( iWinners, WINREASON_ALL_POINTS_CAPTURED, true, m_bSwitchTeamsOnWin );
  545. FireTeamWinOutput( iWinners );
  546. }
  547. }
  548. }
  549. //-----------------------------------------------------------------------------
  550. // Purpose:
  551. //-----------------------------------------------------------------------------
  552. void CTeamControlPointMaster::InputSetWinner( inputdata_t &input )
  553. {
  554. int iTeam = input.value.Int();
  555. InternalSetWinner( iTeam );
  556. }
  557. //-----------------------------------------------------------------------------
  558. // Purpose:
  559. //-----------------------------------------------------------------------------
  560. void CTeamControlPointMaster::InputSetWinnerAndForceCaps( inputdata_t &input )
  561. {
  562. int iTeam = input.value.Int();
  563. // Set all cap points in the current round to be owned by the winning team
  564. for ( unsigned int i = 0; i < m_ControlPoints.Count(); i++ )
  565. {
  566. CTeamControlPoint *pPoint = m_ControlPoints[i];
  567. if ( pPoint && (!PlayingMiniRounds() || ObjectiveResource()->IsInMiniRound(pPoint->GetPointIndex()) ) )
  568. {
  569. pPoint->ForceOwner( iTeam );
  570. }
  571. }
  572. InternalSetWinner( iTeam );
  573. }
  574. //-----------------------------------------------------------------------------
  575. // Purpose:
  576. //-----------------------------------------------------------------------------
  577. void CTeamControlPointMaster::InternalSetWinner( int iTeam )
  578. {
  579. bool bForceMapReset = true;
  580. if ( m_ControlPointRounds.Count() > 0 )
  581. {
  582. // if we're playing rounds and there are more to play, don't do a full reset
  583. bForceMapReset = ( FindControlPointRoundToPlay() == false );
  584. }
  585. if ( iTeam == TEAM_UNASSIGNED )
  586. {
  587. TeamplayGameRules()->SetStalemate( STALEMATE_TIMER, bForceMapReset );
  588. }
  589. else
  590. {
  591. if ( !bForceMapReset )
  592. {
  593. TeamplayGameRules()->SetWinningTeam( iTeam, WINREASON_ALL_POINTS_CAPTURED, bForceMapReset );
  594. }
  595. else
  596. {
  597. TeamplayGameRules()->SetWinningTeam( iTeam, WINREASON_ALL_POINTS_CAPTURED, bForceMapReset, m_bSwitchTeamsOnWin );
  598. }
  599. FireTeamWinOutput( iTeam );
  600. }
  601. }
  602. //-----------------------------------------------------------------------------
  603. // Purpose:
  604. //-----------------------------------------------------------------------------
  605. void CTeamControlPointMaster::HandleRandomOwnerControlPoints( void )
  606. {
  607. CUtlVector<CTeamControlPoint*> vecPoints;
  608. CUtlVector<int> vecTeams;
  609. int i = 0;
  610. // loop through and find all of the points that want random owners after a full restart
  611. for ( i = 0 ; i < (int)m_ControlPoints.Count() ; i++ )
  612. {
  613. CTeamControlPoint *pPoint = m_ControlPoints[i];
  614. if ( pPoint && pPoint->RandomOwnerOnRestart() )
  615. {
  616. vecPoints.AddToHead( pPoint );
  617. vecTeams.AddToHead( pPoint->GetTeamNumber() );
  618. }
  619. }
  620. // now loop through and mix up the owners (if we found any points with this flag set)
  621. for ( i = 0 ; i < vecPoints.Count() ; i++ )
  622. {
  623. CTeamControlPoint *pPoint = vecPoints[i];
  624. if ( pPoint )
  625. {
  626. int index = random->RandomInt( 0, vecTeams.Count() - 1 );
  627. pPoint->ForceOwner( vecTeams[index] );
  628. vecTeams.Remove( index );
  629. }
  630. }
  631. vecPoints.RemoveAll();
  632. vecTeams.RemoveAll();
  633. }
  634. //-----------------------------------------------------------------------------
  635. // Purpose:
  636. //-----------------------------------------------------------------------------
  637. void CTeamControlPointMaster::InputRoundSpawn( inputdata_t &input )
  638. {
  639. //clear out old control points
  640. m_ControlPoints.RemoveAll();
  641. //find the control points, and if successful, do CPMThink
  642. if ( FindControlPoints() )
  643. {
  644. /* if ( m_bFirstRoundAfterRestart )
  645. {
  646. CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
  647. if ( pRules && ( pRules->GetRoundToPlayNext() == NULL_STRING ) )
  648. {
  649. // we only want to handle the random points if we don't have a specific round to play next
  650. // (prevents points being randomized again after "waiting for players" has finished and we're going to play the same round)
  651. HandleRandomOwnerControlPoints();
  652. }
  653. }
  654. */
  655. SetContextThink( &CTeamControlPointMaster::CPMThink, gpGlobals->curtime + 0.1, CPM_THINK );
  656. }
  657. // clear out the old rounds
  658. m_ControlPointRounds.RemoveAll();
  659. // find the rounds (if the map has any)
  660. FindControlPointRounds();
  661. SetBaseControlPoints();
  662. // init the ClientAreas
  663. int index = 0;
  664. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, GetTriggerAreaCaptureName() );
  665. while( pEnt )
  666. {
  667. CTriggerAreaCapture *pArea = (CTriggerAreaCapture *)pEnt;
  668. Assert( pArea );
  669. pArea->SetAreaIndex( index );
  670. index++;
  671. pEnt = gEntList.FindEntityByClassname( pEnt, GetTriggerAreaCaptureName() );
  672. }
  673. ObjectiveResource()->ResetControlPoints();
  674. }
  675. //-----------------------------------------------------------------------------
  676. // Purpose:
  677. //-----------------------------------------------------------------------------
  678. void CTeamControlPointMaster::InputRoundActivate( inputdata_t &input )
  679. {
  680. // if we're using mini-rounds and haven't picked one yet, find one to play
  681. if ( PlayingMiniRounds() && GetCurrentRound() == NULL )
  682. {
  683. GetControlPointRoundToPlay();
  684. }
  685. if ( PlayingMiniRounds() )
  686. {
  687. // Tell the objective resource what control points are in use in the selected mini-round
  688. CTeamControlPointRound *pRound = GetCurrentRound();
  689. if ( pRound )
  690. {
  691. for ( unsigned int i = 0; i < m_ControlPoints.Count(); i++ )
  692. {
  693. CTeamControlPoint *pPoint = m_ControlPoints[i];
  694. if ( pPoint )
  695. {
  696. ObjectiveResource()->SetInMiniRound( pPoint->GetPointIndex(), pRound->IsControlPointInRound( pPoint ) );
  697. }
  698. }
  699. }
  700. }
  701. }
  702. //-----------------------------------------------------------------------------
  703. // Purpose:
  704. //-----------------------------------------------------------------------------
  705. void CTeamControlPointMaster::InputSetCapLayout( inputdata_t &inputdata )
  706. {
  707. m_iszCapLayoutInHUD = inputdata.value.StringID();
  708. g_pObjectiveResource->SetCapLayoutInHUD( STRING(m_iszCapLayoutInHUD) );
  709. }
  710. //-----------------------------------------------------------------------------
  711. // Purpose:
  712. //-----------------------------------------------------------------------------
  713. void CTeamControlPointMaster::FireTeamWinOutput( int iWinningTeam )
  714. {
  715. // Remap team so that first game team = 1
  716. switch( iWinningTeam - FIRST_GAME_TEAM+1 )
  717. {
  718. case 1:
  719. m_OnWonByTeam1.FireOutput(this,this);
  720. break;
  721. case 2:
  722. m_OnWonByTeam2.FireOutput(this,this);
  723. break;
  724. default:
  725. Assert(0);
  726. break;
  727. }
  728. }
  729. //-----------------------------------------------------------------------------
  730. // Purpose:
  731. //-----------------------------------------------------------------------------
  732. void CTeamControlPointMaster::FireRoundStartOutput( void )
  733. {
  734. CTeamControlPointRound *pRound = GetCurrentRound();
  735. if ( pRound )
  736. {
  737. pRound->FireOnStartOutput();
  738. }
  739. }
  740. //-----------------------------------------------------------------------------
  741. // Purpose:
  742. //-----------------------------------------------------------------------------
  743. void CTeamControlPointMaster::FireRoundEndOutput( void )
  744. {
  745. CTeamControlPointRound *pRound = GetCurrentRound();
  746. if ( pRound )
  747. {
  748. pRound->FireOnEndOutput();
  749. m_iCurrentRoundIndex = -1;
  750. }
  751. }
  752. //-----------------------------------------------------------------------------
  753. // Purpose:
  754. //-----------------------------------------------------------------------------
  755. float CTeamControlPointMaster::PointLastContestedAt( int point )
  756. {
  757. CTeamControlPoint *pPoint = GetControlPoint(point);
  758. if ( pPoint )
  759. return pPoint->LastContestedAt();
  760. return -1;
  761. }
  762. //-----------------------------------------------------------------------------
  763. // Purpose: This function returns the team that owns all the cap points.
  764. // If its not the case that one team owns them all, it returns 0.
  765. // CPs are broken into groups. A team can win by owning all flags within a single group.
  766. //
  767. // Can be passed an overriding team. If this is not null, the passed team
  768. // number will be used for that cp. Used to predict if that CP changing would
  769. // win the game.
  770. //-----------------------------------------------------------------------------
  771. int CTeamControlPointMaster::TeamOwnsAllPoints( CTeamControlPoint *pOverridePoint /* = NULL */, int iOverrideNewTeam /* = TEAM_UNASSIGNED */ )
  772. {
  773. unsigned int i;
  774. int iWinningTeam[MAX_CONTROL_POINT_GROUPS];
  775. for( i=0;i<MAX_CONTROL_POINT_GROUPS;i++ )
  776. {
  777. iWinningTeam[i] = TEAM_INVALID;
  778. }
  779. // if TEAM_INVALID, haven't found a flag for this group yet
  780. // if TEAM_UNASSIGNED, the group is still being contested
  781. // for each control point
  782. for( i=0;i<m_ControlPoints.Count();i++ )
  783. {
  784. int group = m_ControlPoints[i]->GetCPGroup();
  785. int owner = m_ControlPoints[i]->GetOwner();
  786. if ( pOverridePoint == m_ControlPoints[i] )
  787. {
  788. owner = iOverrideNewTeam;
  789. }
  790. // the first one we find in this group, set the win to true
  791. if ( iWinningTeam[group] == TEAM_INVALID )
  792. {
  793. iWinningTeam[group] = owner;
  794. }
  795. // unassigned means this group is already contested, move on
  796. else if ( iWinningTeam[group] == TEAM_UNASSIGNED )
  797. {
  798. continue;
  799. }
  800. // if we find another one in the group that isn't the same owner, set the win to false
  801. else if ( owner != iWinningTeam[group] )
  802. {
  803. iWinningTeam[group] = TEAM_UNASSIGNED;
  804. }
  805. }
  806. // report the first win we find as the winner
  807. for ( i=0;i<MAX_CONTROL_POINT_GROUPS;i++ )
  808. {
  809. if ( iWinningTeam[i] >= FIRST_GAME_TEAM )
  810. return iWinningTeam[i];
  811. }
  812. // no wins yet
  813. return TEAM_UNASSIGNED;
  814. }
  815. //-----------------------------------------------------------------------------
  816. // Purpose:
  817. //-----------------------------------------------------------------------------
  818. bool CTeamControlPointMaster::WouldNewCPOwnerWinGame( CTeamControlPoint *pPoint, int iNewOwner )
  819. {
  820. return ( TeamOwnsAllPoints( pPoint, iNewOwner ) == iNewOwner );
  821. }
  822. //-----------------------------------------------------------------------------
  823. // Purpose:
  824. //-----------------------------------------------------------------------------
  825. bool CTeamControlPointMaster::IsBaseControlPoint( int iPointIndex )
  826. {
  827. bool retVal = false;
  828. for ( int iTeam = LAST_SHARED_TEAM+1; iTeam < GetNumberOfTeams(); iTeam++ )
  829. {
  830. if ( GetBaseControlPoint( iTeam ) == iPointIndex )
  831. {
  832. retVal = true;
  833. break;
  834. }
  835. }
  836. return retVal;
  837. }
  838. //-----------------------------------------------------------------------------
  839. // Purpose: Get the control point for the specified team that's at their end of
  840. // the control point chain.
  841. //-----------------------------------------------------------------------------
  842. int CTeamControlPointMaster::GetBaseControlPoint( int iTeam )
  843. {
  844. int iRetVal = -1;
  845. int nLowestValue = 999, nHighestValue = -1;
  846. int iLowestIndex = 0, iHighestIndex = 0;
  847. for( int i = 0 ; i < (int)m_ControlPoints.Count() ; i++ )
  848. {
  849. CTeamControlPoint *pPoint = m_ControlPoints[i];
  850. int iPointIndex = m_ControlPoints[i]->GetPointIndex();
  851. if ( PlayingMiniRounds() && iTeam > LAST_SHARED_TEAM )
  852. {
  853. if ( PointCanBeCapped( pPoint ) ) // is this point in the current round?
  854. {
  855. if ( iPointIndex > nHighestValue )
  856. {
  857. nHighestValue = iPointIndex;
  858. iHighestIndex = i;
  859. }
  860. if ( iPointIndex < nLowestValue )
  861. {
  862. nLowestValue = iPointIndex;
  863. iLowestIndex = i;
  864. }
  865. }
  866. }
  867. else
  868. {
  869. if ( pPoint->GetDefaultOwner() != iTeam )
  870. {
  871. continue;
  872. }
  873. // If it's the first or the last point, it's their base
  874. if ( iPointIndex == 0 || iPointIndex == (((int)m_ControlPoints.Count())-1) )
  875. {
  876. iRetVal = iPointIndex;
  877. break;
  878. }
  879. }
  880. }
  881. if ( PlayingMiniRounds() && iTeam > LAST_SHARED_TEAM )
  882. {
  883. if ( nLowestValue != 999 && nHighestValue != -1 )
  884. {
  885. CTeamControlPoint *pLowestPoint = m_ControlPoints[iLowestIndex];
  886. CTeamControlPoint *pHighestPoint = m_ControlPoints[iHighestIndex];
  887. // which point is owned by this team?
  888. if ( ( pLowestPoint->GetDefaultOwner() == iTeam && pHighestPoint->GetDefaultOwner() == iTeam ) || // if the same team owns both, take the highest value to be the last point
  889. ( pHighestPoint->GetDefaultOwner() == iTeam ) )
  890. {
  891. iRetVal = nHighestValue;
  892. }
  893. else if ( pLowestPoint->GetDefaultOwner() == iTeam )
  894. {
  895. iRetVal = nLowestValue;
  896. }
  897. }
  898. }
  899. return iRetVal;
  900. }
  901. //-----------------------------------------------------------------------------
  902. // Purpose:
  903. //-----------------------------------------------------------------------------
  904. void CTeamControlPointMaster::InputEnable( inputdata_t &input )
  905. {
  906. m_bDisabled = false;
  907. }
  908. //-----------------------------------------------------------------------------
  909. // Purpose:
  910. //-----------------------------------------------------------------------------
  911. void CTeamControlPointMaster::InputDisable( inputdata_t &input )
  912. {
  913. m_bDisabled = true;
  914. }
  915. //-----------------------------------------------------------------------------
  916. // Purpose:
  917. //-----------------------------------------------------------------------------
  918. int CTeamControlPointMaster::GetNumPointsOwnedByTeam( int iTeam )
  919. {
  920. int nCount = 0;
  921. for( int i = 0 ; i < (int)m_ControlPoints.Count() ; i++ )
  922. {
  923. CTeamControlPoint *pPoint = m_ControlPoints[i];
  924. if ( pPoint && ( pPoint->GetTeamNumber() == iTeam ) )
  925. {
  926. nCount++;
  927. }
  928. }
  929. return nCount;
  930. }
  931. //-----------------------------------------------------------------------------
  932. // Purpose: returns how many more mini-rounds it will take for specified team
  933. // to win, if they keep winning every mini-round
  934. //-----------------------------------------------------------------------------
  935. int CTeamControlPointMaster::CalcNumRoundsRemaining( int iTeam )
  936. {
  937. // To determine how many rounds remain for a given team if it consistently wins mini-rounds, we have to
  938. // simulate forward each mini-round and track the control point ownership that would result
  939. // vector of control points the team owns in our forward-simulation
  940. CUtlVector<CTeamControlPoint *> vecControlPointsOwned;
  941. // start with all the control points the team currently owns
  942. FOR_EACH_MAP_FAST( m_ControlPoints, iControlPoint )
  943. {
  944. if ( m_ControlPoints[iControlPoint]->GetOwner() == iTeam )
  945. {
  946. vecControlPointsOwned.AddToTail( m_ControlPoints[iControlPoint] );
  947. }
  948. }
  949. int iRoundsRemaining = 0;
  950. // keep simulating what will happen next if this team keeps winning, until
  951. // it owns all the control points in the map
  952. while ( vecControlPointsOwned.Count() < (int) m_ControlPoints.Count() )
  953. {
  954. iRoundsRemaining++;
  955. // choose the next highest-priority round that is playable
  956. for ( int i = 0 ; i < m_ControlPointRounds.Count() ; ++i )
  957. {
  958. CTeamControlPointRound *pRound = m_ControlPointRounds[i];
  959. if ( !pRound )
  960. continue;
  961. // see if one team owns all control points in this round
  962. int iRoundOwningTeam = TEAM_INVALID;
  963. int iControlPoint;
  964. for ( iControlPoint = 0; iControlPoint < pRound->m_ControlPoints.Count(); iControlPoint++ )
  965. {
  966. CTeamControlPoint *pControlPoint = pRound->m_ControlPoints[iControlPoint];
  967. int iControlPointOwningTeam = TEAM_INVALID;
  968. // determine who owns this control point.
  969. // First, check our simulated ownership
  970. if ( vecControlPointsOwned.InvalidIndex() != vecControlPointsOwned.Find( pControlPoint ) )
  971. {
  972. // This team has won this control point in forward simulation
  973. iControlPointOwningTeam = iTeam;
  974. }
  975. else
  976. {
  977. // use actual control point ownership
  978. iControlPointOwningTeam = pControlPoint->GetOwner();
  979. }
  980. if ( 0 == iControlPoint )
  981. {
  982. // if this is the first control point, assign ownership to the team that owns this control point
  983. iRoundOwningTeam = iControlPointOwningTeam;
  984. }
  985. else
  986. {
  987. // for all other control points, if the control point ownership does not match other control points, reset
  988. // round ownership to no team
  989. if ( iRoundOwningTeam != iControlPointOwningTeam )
  990. {
  991. iRoundOwningTeam = TEAM_INVALID;
  992. }
  993. }
  994. }
  995. // this round is playable if all control points are not owned by one team (or owned by a team that can't win by capping them)
  996. bool bPlayable = ( ( iRoundOwningTeam < FIRST_GAME_TEAM ) || ( iRoundOwningTeam == pRound->GetInvalidCapWinner() ) );
  997. if ( !bPlayable )
  998. continue;
  999. // Pretend this team played and won this round. It now owns all control points from this round. Add all the
  1000. // control points from this round that are not already own the owned list to the owned list
  1001. int iNewControlPointsOwned = 0;
  1002. FOR_EACH_VEC( pRound->m_ControlPoints, iControlPoint )
  1003. {
  1004. CTeamControlPoint *pControlPoint = pRound->m_ControlPoints[iControlPoint];
  1005. if ( vecControlPointsOwned.InvalidIndex() == vecControlPointsOwned.Find( pControlPoint ) )
  1006. {
  1007. vecControlPointsOwned.AddToTail( pControlPoint );
  1008. iNewControlPointsOwned++;
  1009. }
  1010. }
  1011. // sanity check: team being simulated should be owning at least one more new control point per round, or they're not making progress
  1012. Assert( iNewControlPointsOwned > 0 );
  1013. // now go back and pick the next playable round (if any) given the control points this team now owns,
  1014. // repeat until all control points are owned. The number of iterations it takes is the # of rounds remaining
  1015. // for this team to win.
  1016. break;
  1017. }
  1018. }
  1019. return iRoundsRemaining;
  1020. }
  1021. //-----------------------------------------------------------------------------
  1022. // Purpose:
  1023. //-----------------------------------------------------------------------------
  1024. float CTeamControlPointMaster::GetPartialCapturePointRate( void )
  1025. {
  1026. return m_flPartialCapturePointsRate;
  1027. }
  1028. /*
  1029. //-----------------------------------------------------------------------------
  1030. // Purpose:
  1031. //-----------------------------------------------------------------------------
  1032. void CTeamControlPointMaster::ListRounds( void )
  1033. {
  1034. if ( PlayingMiniRounds() )
  1035. {
  1036. ConMsg( "Rounds in this map:\n\n" );
  1037. for ( int i = 0; i < m_ControlPointRounds.Count() ; ++i )
  1038. {
  1039. CTeamControlPointRound* pRound = m_ControlPointRounds[i];
  1040. if ( pRound )
  1041. {
  1042. const char *pszName = STRING( pRound->GetEntityName() );
  1043. ConMsg( "%s\n", pszName );
  1044. }
  1045. }
  1046. }
  1047. else
  1048. {
  1049. ConMsg( "* No rounds in this map *\n" );
  1050. }
  1051. }
  1052. //-----------------------------------------------------------------------------
  1053. // Purpose:
  1054. //-----------------------------------------------------------------------------
  1055. void cc_ListRounds( void )
  1056. {
  1057. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  1058. if ( pMaster )
  1059. {
  1060. pMaster->ListRounds();
  1061. }
  1062. }
  1063. static ConCommand listrounds( "listrounds", cc_ListRounds, "List the rounds for the current map", FCVAR_CHEAT );
  1064. //-----------------------------------------------------------------------------
  1065. // Purpose:
  1066. //-----------------------------------------------------------------------------
  1067. void cc_PlayRound( const CCommand& args )
  1068. {
  1069. if ( args.ArgC() > 1 )
  1070. {
  1071. CTeamplayRoundBasedRules *pRules = dynamic_cast<CTeamplayRoundBasedRules*>( GameRules() );
  1072. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  1073. if ( pRules && pMaster )
  1074. {
  1075. if ( pMaster->PlayingMiniRounds() )
  1076. {
  1077. // did we get the name of a round?
  1078. CTeamControlPointRound *pRound = dynamic_cast<CTeamControlPointRound*>( gEntList.FindEntityByName( NULL, args[1] ) );
  1079. if ( pRound )
  1080. {
  1081. pRules->SetRoundToPlayNext( pRound->GetEntityName() );
  1082. mp_restartgame.SetValue( 5 );
  1083. }
  1084. else
  1085. {
  1086. ConMsg( "* Round \"%s\" not found in this map *\n", args[1] );
  1087. }
  1088. }
  1089. }
  1090. }
  1091. else
  1092. {
  1093. ConMsg( "Usage: playround < round name >\n" );
  1094. }
  1095. }
  1096. static ConCommand playround( "playround", cc_PlayRound, "Play the selected round\n\tArgument: {round name given by \"listrounds\" command}", FCVAR_CHEAT );
  1097. */