Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1342 lines
41 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Base VoteController. Handles holding and voting on issues.
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "vote_controller.h"
  9. #include "shareddefs.h"
  10. #include "eiface.h"
  11. #include "team.h"
  12. #include "gameinterface.h"
  13. #include "fmtstr.h"
  14. #ifdef TF_DLL
  15. #include "tf/tf_gamerules.h"
  16. #include "tf/tf_voteissues.h"
  17. #endif
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. #define MAX_VOTER_HISTORY 64
  21. // Datatable
  22. IMPLEMENT_SERVERCLASS_ST( CVoteController, DT_VoteController )
  23. SendPropInt( SENDINFO( m_iActiveIssueIndex ) ),
  24. SendPropInt( SENDINFO( m_iOnlyTeamToVote ) ),
  25. SendPropArray3( SENDINFO_ARRAY3( m_nVoteOptionCount ), SendPropInt( SENDINFO_ARRAY( m_nVoteOptionCount ), 8, SPROP_UNSIGNED ) ),
  26. SendPropInt( SENDINFO( m_nPotentialVotes ) ),
  27. SendPropBool( SENDINFO( m_bIsYesNoVote ) )
  28. END_SEND_TABLE()
  29. BEGIN_DATADESC( CVoteController )
  30. DEFINE_THINKFUNC( VoteControllerThink ),
  31. END_DATADESC()
  32. LINK_ENTITY_TO_CLASS( vote_controller, CVoteController );
  33. CVoteController *g_voteController = NULL;
  34. ConVar sv_vote_timer_duration( "sv_vote_timer_duration", "15", FCVAR_DEVELOPMENTONLY, "How long to allow voting on an issue" );
  35. ConVar sv_vote_command_delay( "sv_vote_command_delay", "2", FCVAR_DEVELOPMENTONLY, "How long after a vote passes until the action happens", false, 0.f, true, 4.5f );
  36. ConVar sv_allow_votes( "sv_allow_votes", "1", FCVAR_NONE, "Allow voting?" );
  37. ConVar sv_vote_failure_timer( "sv_vote_failure_timer", "300", FCVAR_NONE, "A vote that fails cannot be re-submitted for this long" );
  38. #ifdef TF_DLL
  39. ConVar sv_vote_failure_timer_mvm( "sv_vote_failure_timer_mvm", "120", FCVAR_NONE, "A vote that fails in MvM cannot be re-submitted for this long" );
  40. #endif // TF_DLL
  41. ConVar sv_vote_creation_timer( "sv_vote_creation_timer", "150", FCVAR_NONE, "How long before a player can attempt to call another vote (in seconds)." );
  42. ConVar sv_vote_quorum_ratio( "sv_vote_quorum_ratio", "0.6", FCVAR_NOTIFY, "The minimum ratio of eligible players needed to pass a vote. Min 0.5, Max 1.0.", true, 0.1f, true, 1.0f );
  43. ConVar sv_vote_allow_spectators( "sv_vote_allow_spectators", "0", FCVAR_NONE, "Allow spectators to vote?" );
  44. ConVar sv_vote_ui_hide_disabled_issues( "sv_vote_ui_hide_disabled_issues", "1", FCVAR_NONE, "Suppress listing of disabled issues in the vote setup screen." );
  45. ConVar sv_vote_holder_may_vote_no( "sv_vote_holder_may_vote_no", "0", FCVAR_NONE, "1 = Vote caller is not forced to vote yes on yes/no votes." );
  46. static const int k_nKickWatchListMaxDuration = 300;
  47. //-----------------------------------------------------------------------------
  48. // Purpose:
  49. //-----------------------------------------------------------------------------
  50. class CVoteControllerSystem : public CAutoGameSystemPerFrame
  51. {
  52. public:
  53. CVoteControllerSystem( char const *name ) : CAutoGameSystemPerFrame( name )
  54. {
  55. SetDefLessFunc( m_mapKickWatchList );
  56. SetDefLessFunc( m_mapNameLockedList );
  57. m_flNextKickCheckTime = 0.f;
  58. m_flNextNameLockCheckTime = 0.f;
  59. }
  60. virtual void LevelInitPreEntity()
  61. {
  62. m_flNextNameLockCheckTime = 0.f;
  63. m_flNextKickCheckTime = 0.f;
  64. }
  65. virtual void FrameUpdatePostEntityThink( void )
  66. {
  67. // Executing the vote controller command needs to happen in the PostEntityThink as it can restart levels and
  68. // blast entities, etc. If you're doing this during a regular think, this can cause entities thinking after
  69. // you in Physics_RunThinkFunctions() to get grumpy and crash.
  70. if ( g_voteController )
  71. {
  72. // Vote passed - execute the command
  73. if ( g_voteController->m_executeCommandTimer.HasStarted() && g_voteController->m_executeCommandTimer.IsElapsed() )
  74. {
  75. g_voteController->m_executeCommandTimer.Invalidate();
  76. g_voteController->m_potentialIssues[g_voteController->m_iActiveIssueIndex]->ExecuteCommand();
  77. }
  78. // Kick watch
  79. if ( m_flNextKickCheckTime < gpGlobals->curtime )
  80. {
  81. FOR_EACH_MAP( m_mapKickWatchList, i )
  82. {
  83. if ( gpGlobals->curtime > m_mapKickWatchList[i] )
  84. {
  85. m_mapKickWatchList.RemoveAt( i );
  86. break; // Constantly called code - resume on next pass
  87. }
  88. CBasePlayer *pTarget = UTIL_PlayerBySteamID( m_mapKickWatchList.Key( i ) );
  89. if ( pTarget )
  90. {
  91. // Welcome back
  92. engine->ServerCommand( CFmtStr( "kickid %d %s;", pTarget->GetUserID(), "Kicked by server." ) );
  93. }
  94. }
  95. m_flNextKickCheckTime = gpGlobals->curtime + 0.2f;
  96. }
  97. // Name lock management
  98. if ( m_flNextNameLockCheckTime < gpGlobals->curtime )
  99. {
  100. FOR_EACH_MAP( m_mapNameLockedList, i )
  101. {
  102. CBasePlayer *pPlayer = UTIL_PlayerBySteamID( m_mapNameLockedList.Key( i ) );
  103. // Time up?
  104. if ( gpGlobals->curtime > m_mapNameLockedList[i] )
  105. {
  106. // Disable the lock if they're still here
  107. if ( pPlayer )
  108. {
  109. engine->ServerCommand( UTIL_VarArgs( "namelockid %d %d\n", pPlayer->GetUserID(), 0 ) );
  110. }
  111. // Remove and break - this will re-run in 1 second
  112. m_mapNameLockedList.RemoveAt( i );
  113. break;
  114. }
  115. // See if they reconnected
  116. else if ( pPlayer && !engine->IsPlayerNameLocked( pPlayer->edict() ) )
  117. {
  118. engine->ServerCommand( UTIL_VarArgs( "namelockid %d %d\n", pPlayer->GetUserID(), 1 ) );
  119. }
  120. }
  121. m_flNextNameLockCheckTime = gpGlobals->curtime + 1.f;
  122. }
  123. }
  124. }
  125. void AddPlayerToKickWatchList( CSteamID steamID, float flDuration )
  126. {
  127. if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
  128. return;
  129. flDuration = clamp( flDuration, 1.f, (float)k_nKickWatchListMaxDuration );
  130. if ( m_mapKickWatchList.Find( steamID ) == m_mapKickWatchList.InvalidIndex() )
  131. {
  132. m_mapKickWatchList.Insert( steamID, ( gpGlobals->curtime + flDuration ) );
  133. }
  134. }
  135. void AddPlayerToNameLockedList( CSteamID steamID, float flDuration )
  136. {
  137. if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
  138. return;
  139. flDuration = clamp( flDuration, 1.f, (float)k_nKickWatchListMaxDuration );
  140. if ( m_mapNameLockedList.Find( steamID ) == m_mapNameLockedList.InvalidIndex() )
  141. {
  142. m_mapNameLockedList.Insert( steamID, ( gpGlobals->curtime + flDuration ) );
  143. }
  144. }
  145. private:
  146. CUtlMap< CSteamID, float > m_mapKickWatchList;
  147. CUtlMap< CSteamID, float > m_mapNameLockedList;
  148. float m_flNextKickCheckTime;
  149. float m_flNextNameLockCheckTime;
  150. };
  151. CVoteControllerSystem VoteControllerSystem( "CVoteControllerSystem" );
  152. //-----------------------------------------------------------------------------
  153. // Purpose:
  154. //-----------------------------------------------------------------------------
  155. void CommandListIssues( void )
  156. {
  157. CBasePlayer *commandIssuer = UTIL_GetCommandClient();
  158. if ( g_voteController && commandIssuer )
  159. {
  160. g_voteController->ListIssues(commandIssuer);
  161. }
  162. }
  163. //-----------------------------------------------------------------------------
  164. // Purpose:
  165. //-----------------------------------------------------------------------------
  166. ConCommand ListIssues("listissues", CommandListIssues, "List all the issues that can be voted on.", 0);
  167. //-----------------------------------------------------------------------------
  168. // Purpose: This should eventually ask the player what team they are voting on
  169. // to take into account different idle / spectator rules.
  170. //-----------------------------------------------------------------------------
  171. int GetVoterTeam( CBaseEntity *pEntity )
  172. {
  173. if ( !pEntity )
  174. return TEAM_UNASSIGNED;
  175. int iTeam = pEntity->GetTeamNumber();
  176. return iTeam;
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose:
  180. //-----------------------------------------------------------------------------
  181. CON_COMMAND( callvote, "Start a vote on an issue." )
  182. {
  183. if ( !g_voteController )
  184. {
  185. DevMsg( "Vote Controller Not Found!\n" );
  186. return;
  187. }
  188. CBasePlayer *pVoteCaller = UTIL_GetCommandClient();
  189. if ( !pVoteCaller )
  190. return;
  191. if ( !sv_vote_allow_spectators.GetBool() )
  192. {
  193. if ( pVoteCaller->GetTeamNumber() == TEAM_SPECTATOR )
  194. {
  195. g_voteController->SendVoteCreationFailedMessage( VOTE_FAILED_SPECTATOR, pVoteCaller );
  196. return;
  197. }
  198. }
  199. if ( g_voteController->IsVoteActive() )
  200. {
  201. ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#GameUI_vote_failed_vote_in_progress" );
  202. return;
  203. }
  204. // Ask the controller if this is allowed
  205. int nCooldown = 0;
  206. vote_create_failed_t nError = VOTE_FAILED_GENERIC;
  207. if ( !g_voteController->CanEntityCallVote( pVoteCaller, nCooldown, nError ) )
  208. {
  209. g_voteController->SendVoteCreationFailedMessage( nError, pVoteCaller, nCooldown );
  210. return;
  211. }
  212. // Parameters
  213. char szEmptyDetails[MAX_VOTE_DETAILS_LENGTH];
  214. szEmptyDetails[0] = '\0';
  215. const char *arg2 = args[1];
  216. const char *arg3 = args.ArgC() >= 3 ? args[2] : szEmptyDetails;
  217. // If we don't have any arguments, invoke VoteSetup UI
  218. if ( args.ArgC() < 2 )
  219. {
  220. g_voteController->SetupVote( pVoteCaller->entindex() );
  221. return;
  222. }
  223. g_voteController->CreateVote( pVoteCaller->entindex(), arg2, arg3 );
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Purpose:
  227. //-----------------------------------------------------------------------------
  228. CVoteController::~CVoteController()
  229. {
  230. g_voteController = NULL;
  231. for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
  232. {
  233. delete m_potentialIssues[issueIndex];
  234. }
  235. }
  236. //-----------------------------------------------------------------------------
  237. // Purpose:
  238. //-----------------------------------------------------------------------------
  239. void CVoteController::ResetData( void )
  240. {
  241. m_iActiveIssueIndex = INVALID_ISSUE;
  242. for ( int index = 0; index < m_nVoteOptionCount.Count(); index++ )
  243. {
  244. m_nVoteOptionCount.Set( index, 0 );
  245. }
  246. m_nPotentialVotes = 0;
  247. m_acceptingVotesTimer.Invalidate();
  248. m_executeCommandTimer.Invalidate();
  249. m_waitingForGCResponseTimer.Invalidate();
  250. m_iEntityHoldingVote = -1;
  251. m_iOnlyTeamToVote = TEAM_UNASSIGNED;
  252. m_bIsYesNoVote = true;
  253. for( int voteIndex = 0; voteIndex < ARRAYSIZE( m_nVotesCast ); ++voteIndex )
  254. {
  255. m_nVotesCast[voteIndex] = VOTE_UNCAST;
  256. }
  257. m_pendingVoteParams.Reset();
  258. }
  259. //-----------------------------------------------------------------------------
  260. // Purpose:
  261. //-----------------------------------------------------------------------------
  262. void CVoteController::Spawn( void )
  263. {
  264. ResetData();
  265. BaseClass::Spawn();
  266. SetThink( &CVoteController::VoteControllerThink );
  267. SetNextThink( gpGlobals->curtime );
  268. SetDefLessFunc( m_VoteCallers );
  269. g_voteController = this;
  270. }
  271. //-----------------------------------------------------------------------------
  272. // Purpose:
  273. //-----------------------------------------------------------------------------
  274. int CVoteController::UpdateTransmitState( void )
  275. {
  276. // ALWAYS transmit to all clients.
  277. return SetTransmitState( FL_EDICT_ALWAYS );
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Purpose:
  281. //-----------------------------------------------------------------------------
  282. bool CVoteController::IsVoteSystemEnabled( void )
  283. {
  284. return sv_allow_votes.GetBool();
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Purpose:
  288. //-----------------------------------------------------------------------------
  289. bool CVoteController::CanTeamCastVote( int iTeam ) const
  290. {
  291. if ( m_iOnlyTeamToVote == TEAM_UNASSIGNED )
  292. return true;
  293. return iTeam == m_iOnlyTeamToVote;
  294. }
  295. //-----------------------------------------------------------------------------
  296. // Purpose: Handles menu-driven setup of Voting
  297. //-----------------------------------------------------------------------------
  298. bool CVoteController::SetupVote( int iEntIndex )
  299. {
  300. CBasePlayer *pVoteCaller = UTIL_PlayerByIndex( iEntIndex );
  301. if( !pVoteCaller )
  302. return false;
  303. int nIssueCount = 0;
  304. // Passing an nIssueCount of 0 triggers a "Voting disabled on server" message in the setup UI
  305. if ( IsVoteSystemEnabled() )
  306. {
  307. for( int iIndex = 0; iIndex < m_potentialIssues.Count(); ++iIndex )
  308. {
  309. // Hide disabled issues?
  310. CBaseIssue *pCurrentIssue = m_potentialIssues[iIndex];
  311. if ( pCurrentIssue )
  312. {
  313. if ( !pCurrentIssue->IsEnabled() && sv_vote_ui_hide_disabled_issues.GetBool() )
  314. continue;
  315. nIssueCount++;
  316. }
  317. }
  318. }
  319. CSingleUserRecipientFilter filter( pVoteCaller );
  320. filter.MakeReliable();
  321. UserMessageBegin( filter, "VoteSetup" );
  322. WRITE_BYTE( nIssueCount );
  323. int nMsgSize = 0;
  324. for( int iIndex = 0; iIndex < m_potentialIssues.Count(); ++iIndex )
  325. {
  326. CBaseIssue *pCurrentIssue = m_potentialIssues[iIndex];
  327. if ( pCurrentIssue )
  328. {
  329. // Don't send/display disabled issues when set
  330. if ( !pCurrentIssue->IsEnabled() && sv_vote_ui_hide_disabled_issues.GetBool() )
  331. continue;
  332. // Don't exceed MAX_USER_MSG_DATA (hack)
  333. nMsgSize += ( V_strlen( pCurrentIssue->GetTypeString() ) + 1 );
  334. nMsgSize += ( V_strlen( pCurrentIssue->GetTypeStringLocalized() ) + 1 );
  335. ++nMsgSize;
  336. Assert( nMsgSize <= MAX_USER_MSG_DATA );
  337. if ( nMsgSize > MAX_USER_MSG_DATA )
  338. continue;
  339. WRITE_STRING( pCurrentIssue->GetTypeString() );
  340. WRITE_STRING( pCurrentIssue->GetTypeStringLocalized() );
  341. WRITE_BYTE( pCurrentIssue->IsEnabled() );
  342. }
  343. }
  344. MessageEnd();
  345. return true;
  346. }
  347. //-----------------------------------------------------------------------------
  348. // Purpose: The purpose of this is to call again the same vote after waiting for the GC's response
  349. //-----------------------------------------------------------------------------
  350. bool CVoteController::SubmitPendingVote( VoteParams_t params )
  351. {
  352. return CreateVote( params.m_iEntIndex, params.m_szTypeString, params.m_szDetailString );
  353. }
  354. //-----------------------------------------------------------------------------
  355. // Purpose: Handles console-driven setup of Voting
  356. //-----------------------------------------------------------------------------
  357. bool CVoteController::CreateVote( int iEntIndex, const char *pszTypeString, const char *pszDetailString )
  358. {
  359. // Terrible Hack: Dedicated servers pass 99 as the EntIndex
  360. bool bDedicatedServer = ( iEntIndex == DEDICATED_SERVER ) ? true : false;
  361. if ( !IsVoteSystemEnabled() )
  362. return false;
  363. // Already running a vote?
  364. if ( IsVoteActive() )
  365. return false;
  366. CBasePlayer *pVoteCaller = UTIL_PlayerByIndex( iEntIndex );
  367. if ( !pVoteCaller && !bDedicatedServer )
  368. return false;
  369. // Find the issue the user is asking for
  370. for ( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
  371. {
  372. CBaseIssue *pCurrentIssue = m_potentialIssues[issueIndex];
  373. if ( !pCurrentIssue )
  374. return false;
  375. if ( FStrEq( pszTypeString, pCurrentIssue->GetTypeString() ) )
  376. {
  377. vote_create_failed_t nErrorCode = VOTE_FAILED_GENERIC;
  378. int nTime = 0;
  379. if ( pCurrentIssue->CanCallVote( iEntIndex, pszDetailString, nErrorCode, nTime ) )
  380. {
  381. // Does the GC need to approve now? If so, this function will send the message.
  382. if ( pCurrentIssue->NeedsPermissionFromGC() )
  383. {
  384. m_pendingVoteParams.m_iIssueIndex = issueIndex;
  385. m_pendingVoteParams.m_iEntIndex = iEntIndex;
  386. V_strcpy_safe( m_pendingVoteParams.m_szTypeString, pszTypeString );
  387. V_strcpy_safe( m_pendingVoteParams.m_szDetailString, pszDetailString );
  388. // Put the vote in limbo and wait for a time-out, or answer.
  389. m_waitingForGCResponseTimer.Start( 3.f );
  390. return false;
  391. }
  392. // Establish a bunch of data on this particular issue
  393. pCurrentIssue->SetIssueDetails( pszDetailString );
  394. m_bIsYesNoVote = pCurrentIssue->IsYesNoVote();
  395. m_iActiveIssueIndex = issueIndex;
  396. m_iEntityHoldingVote = iEntIndex;
  397. if ( !bDedicatedServer )
  398. {
  399. m_iOnlyTeamToVote = ( pCurrentIssue->IsTeamRestrictedVote() ) ? GetVoterTeam( pVoteCaller ) : TEAM_UNASSIGNED;
  400. }
  401. // Now get our choices
  402. m_VoteOptions.RemoveAll();
  403. pCurrentIssue->GetVoteOptions( m_VoteOptions );
  404. int nNumVoteOptions = m_VoteOptions.Count();
  405. if ( nNumVoteOptions >= 2 )
  406. {
  407. IGameEvent *event = gameeventmanager->CreateEvent( "vote_options" );
  408. if ( event )
  409. {
  410. event->SetInt( "count", nNumVoteOptions );
  411. for ( int iIndex = 0; iIndex < nNumVoteOptions; iIndex++ )
  412. {
  413. char szNumber[2];
  414. Q_snprintf( szNumber, sizeof( szNumber ), "%i", iIndex + 1 );
  415. char szOptionName[8] = "option";
  416. Q_strncat( szOptionName, szNumber, sizeof( szOptionName ), COPY_ALL_CHARACTERS );
  417. event->SetString( szOptionName, m_VoteOptions[iIndex] );
  418. }
  419. gameeventmanager->FireEvent( event );
  420. }
  421. }
  422. else
  423. {
  424. Assert( nNumVoteOptions >= 2 );
  425. }
  426. // Have the issue start working on it
  427. pCurrentIssue->OnVoteStarted();
  428. // Now the vote handling and UI
  429. m_nPotentialVotes = pCurrentIssue->CountPotentialVoters();
  430. m_acceptingVotesTimer.Start( sv_vote_timer_duration.GetFloat() + random->RandomFloat( -1.f, 1.f ) );
  431. #ifndef _DEBUG
  432. // Force the vote holder to agree with a Yes/No vote
  433. if ( pCurrentIssue->IsYesNoVote() && !bDedicatedServer && !sv_vote_holder_may_vote_no.GetBool() )
  434. {
  435. TryCastVote( iEntIndex, "Option1" );
  436. }
  437. #endif
  438. // Get the data out to the client
  439. CBroadcastRecipientFilter filter;
  440. filter.MakeReliable();
  441. UserMessageBegin( filter, "VoteStart" );
  442. WRITE_BYTE( m_iOnlyTeamToVote ); // move into the filter
  443. WRITE_BYTE( m_iEntityHoldingVote );
  444. WRITE_STRING( pCurrentIssue->GetDisplayString() );
  445. WRITE_STRING( pCurrentIssue->GetDetailsString() );
  446. WRITE_BOOL( pCurrentIssue->IsYesNoVote() );
  447. WRITE_BYTE( ( pCurrentIssue->m_hPlayerTarget ) ? pCurrentIssue->m_hPlayerTarget->entindex() : 0 );
  448. MessageEnd();
  449. if ( !bDedicatedServer )
  450. {
  451. TrackVoteCaller( pVoteCaller );
  452. }
  453. m_pendingVoteParams.Reset();
  454. return true;
  455. }
  456. else
  457. {
  458. if ( !bDedicatedServer )
  459. {
  460. SendVoteCreationFailedMessage( nErrorCode, pVoteCaller, nTime );
  461. }
  462. m_pendingVoteParams.Reset();
  463. }
  464. }
  465. }
  466. return false;
  467. }
  468. //-----------------------------------------------------------------------------
  469. // Purpose: The vote failed to start - let the caller know why
  470. //-----------------------------------------------------------------------------
  471. void CVoteController::SendVoteCreationFailedMessage( vote_create_failed_t nReason, CBasePlayer *pVoteCaller, int nTime /*= -1*/ )
  472. {
  473. Assert( pVoteCaller );
  474. if ( !pVoteCaller )
  475. return;
  476. CSingleUserRecipientFilter user( pVoteCaller );
  477. user.MakeReliable();
  478. UserMessageBegin( user, "CallVoteFailed" );
  479. WRITE_BYTE( nReason );
  480. WRITE_SHORT( nTime );
  481. MessageEnd();
  482. }
  483. //-----------------------------------------------------------------------------
  484. // Purpose: The vote was called, but failed to pass - let everyone know why
  485. //-----------------------------------------------------------------------------
  486. void CVoteController::SendVoteFailedToPassMessage( vote_create_failed_t nReason )
  487. {
  488. Assert( m_potentialIssues[m_iActiveIssueIndex] );
  489. UTIL_LogPrintf( "Vote failed \"%s %s\" with code %i\n", m_potentialIssues[m_iActiveIssueIndex]->GetTypeString(), m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString(), (int)nReason );
  490. CBroadcastRecipientFilter filter;
  491. filter.MakeReliable();
  492. UserMessageBegin( filter, "VoteFailed" );
  493. WRITE_BYTE( m_iOnlyTeamToVote );
  494. WRITE_BYTE( nReason );
  495. MessageEnd();
  496. }
  497. //-----------------------------------------------------------------------------
  498. // Purpose: Player generated a vote command. i.e. /vote option1
  499. //-----------------------------------------------------------------------------
  500. CVoteController::TryCastVoteResult CVoteController::TryCastVote( int iEntIndex, const char *pszVoteString )
  501. {
  502. if ( !IsVoteSystemEnabled() )
  503. return CAST_FAIL_SERVER_DISABLE;
  504. if ( iEntIndex >= ARRAYSIZE( m_nVotesCast ) )
  505. return CAST_FAIL_SYSTEM_ERROR;
  506. if ( !IsVoteActive() )
  507. return CAST_FAIL_NO_ACTIVE_ISSUE;
  508. if ( m_executeCommandTimer.HasStarted() )
  509. return CAST_FAIL_VOTE_CLOSED;
  510. if ( m_potentialIssues[m_iActiveIssueIndex] && m_potentialIssues[m_iActiveIssueIndex]->IsTeamRestrictedVote() )
  511. {
  512. CBaseEntity *pVoteHolder = UTIL_EntityByIndex( m_iEntityHoldingVote );
  513. CBaseEntity *pVoter = UTIL_EntityByIndex( iEntIndex );
  514. if ( ( pVoteHolder == NULL ) || ( pVoter == NULL ) || ( GetVoterTeam( pVoteHolder ) != GetVoterTeam( pVoter ) ) )
  515. {
  516. return CAST_FAIL_TEAM_RESTRICTED;
  517. }
  518. }
  519. // Look for a previous vote
  520. int nOldVote = m_nVotesCast[iEntIndex];
  521. #ifndef DEBUG
  522. if ( nOldVote != VOTE_UNCAST )
  523. {
  524. return CAST_FAIL_NO_CHANGES;
  525. }
  526. #endif // !DEBUG
  527. // Which option are they voting for?
  528. int nCurrentVote = VOTE_UNCAST;
  529. if ( Q_strnicmp( pszVoteString, "Option", 6 ) != 0 )
  530. return CAST_FAIL_SYSTEM_ERROR;
  531. nCurrentVote = (CastVote)( atoi( pszVoteString + 6 ) - 1 );
  532. if ( nCurrentVote < VOTE_OPTION1 || nCurrentVote > VOTE_OPTION5 )
  533. return CAST_FAIL_SYSTEM_ERROR;
  534. // They're changing their vote
  535. #ifdef DEBUG
  536. if ( nOldVote != VOTE_UNCAST )
  537. {
  538. if( nOldVote == nCurrentVote )
  539. {
  540. return CAST_FAIL_DUPLICATE;
  541. }
  542. VoteChoice_Decrement( nOldVote );
  543. }
  544. #endif // DEBUG
  545. // With a Yes/No vote, slam anything past "No" to No
  546. if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
  547. {
  548. if ( nCurrentVote > VOTE_OPTION2 )
  549. nCurrentVote = VOTE_OPTION2;
  550. }
  551. // Register and track this vote
  552. VoteChoice_Increment( nCurrentVote );
  553. m_nVotesCast[iEntIndex] = nCurrentVote;
  554. // Tell the client-side UI
  555. IGameEvent *event = gameeventmanager->CreateEvent( "vote_cast" );
  556. if ( event )
  557. {
  558. event->SetInt( "vote_option", nCurrentVote );
  559. event->SetInt( "team", m_iOnlyTeamToVote );
  560. event->SetInt( "entityid", iEntIndex );
  561. gameeventmanager->FireEvent( event );
  562. }
  563. CheckForEarlyVoteClose();
  564. return CAST_OK;
  565. }
  566. //-----------------------------------------------------------------------------
  567. // Purpose: Increments the vote count for a particular vote option
  568. // i.e. nVoteChoice = 0 might mean a Yes vote
  569. //-----------------------------------------------------------------------------
  570. void CVoteController::VoteChoice_Increment( int nVoteChoice )
  571. {
  572. if ( nVoteChoice < VOTE_OPTION1 || nVoteChoice > VOTE_OPTION5 )
  573. return;
  574. int nValue = m_nVoteOptionCount.Get( nVoteChoice );
  575. m_nVoteOptionCount.Set( nVoteChoice, ++nValue );
  576. }
  577. //-----------------------------------------------------------------------------
  578. // Purpose:
  579. //-----------------------------------------------------------------------------
  580. void CVoteController::VoteChoice_Decrement( int nVoteChoice )
  581. {
  582. if ( nVoteChoice < VOTE_OPTION1 || nVoteChoice > VOTE_OPTION5 )
  583. return;
  584. int nValue = m_nVoteOptionCount.Get( nVoteChoice );
  585. m_nVoteOptionCount.Set( nVoteChoice, --nValue );
  586. }
  587. //-----------------------------------------------------------------------------
  588. // Purpose:
  589. //-----------------------------------------------------------------------------
  590. void CVoteController::VoteControllerThink( void )
  591. {
  592. // This will stall all voting until the GC answers, or we time-out. Only Kick does this (sometimes).
  593. if ( m_waitingForGCResponseTimer.HasStarted() )
  594. {
  595. if ( m_waitingForGCResponseTimer.IsElapsed() )
  596. {
  597. m_waitingForGCResponseTimer.Invalidate();
  598. // Retry the vote
  599. SubmitPendingVote( m_pendingVoteParams );
  600. }
  601. SetNextThink( gpGlobals->curtime + 0.1f );
  602. return;
  603. }
  604. if ( !m_potentialIssues.IsValidIndex( m_iActiveIssueIndex ) )
  605. {
  606. SetNextThink( gpGlobals->curtime + 0.5f );
  607. return;
  608. }
  609. // Vote time is up - process the result
  610. if ( m_acceptingVotesTimer.HasStarted() && m_acceptingVotesTimer.IsElapsed() )
  611. {
  612. m_acceptingVotesTimer.Invalidate();
  613. // For GC record-keeping
  614. if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
  615. {
  616. m_potentialIssues[m_iActiveIssueIndex]->SetYesNoVoteCount( m_nVoteOptionCount[VOTE_OPTION1], m_nVoteOptionCount[VOTE_OPTION2], m_nPotentialVotes );
  617. }
  618. bool bVotePassed = false;
  619. if ( GetNumVotesCast() >= ( m_nPotentialVotes * m_potentialIssues[m_iActiveIssueIndex]->GetQuorumRatio() ) )
  620. {
  621. int nPassingVoteOptionIndex = GetVoteIssueIndexWithHighestCount();
  622. if ( nPassingVoteOptionIndex >= 0 && nPassingVoteOptionIndex < MAX_VOTE_OPTIONS )
  623. {
  624. // YES/NO VOTES - hard-wired to VOTE_OPTION1 (Yes)
  625. if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
  626. {
  627. if ( nPassingVoteOptionIndex == VOTE_OPTION1 )
  628. {
  629. bVotePassed = true;
  630. }
  631. }
  632. // GENERAL VOTES - as long as there's a quorum, go with the most popular choice
  633. else
  634. {
  635. bVotePassed = true;
  636. // We set the details string after the vote, since that's when
  637. // we finally have a parameter to pass along and execute
  638. m_potentialIssues[m_iActiveIssueIndex]->SetIssueDetails( m_VoteOptions[nPassingVoteOptionIndex] );
  639. }
  640. }
  641. }
  642. if ( bVotePassed )
  643. {
  644. float flDelay = sv_vote_command_delay.GetFloat();
  645. #ifdef TF_DLL
  646. if ( dynamic_cast< CKickIssue* >( m_potentialIssues[m_iActiveIssueIndex] ) )
  647. {
  648. // Don't delay successful kick votes
  649. flDelay = 0.f;
  650. }
  651. #endif
  652. m_executeCommandTimer.Start( flDelay );
  653. m_resetVoteTimer.Start( 5.f );
  654. UTIL_LogPrintf( "Vote succeeded \"%s %s\"\n", m_potentialIssues[m_iActiveIssueIndex]->GetTypeString(), m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString() );
  655. CBroadcastRecipientFilter filter;
  656. filter.MakeReliable();
  657. UserMessageBegin( filter, "VotePass" );
  658. WRITE_BYTE( m_iOnlyTeamToVote );
  659. WRITE_STRING( m_potentialIssues[m_iActiveIssueIndex]->GetVotePassedString() );
  660. WRITE_STRING( m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString() );
  661. MessageEnd();
  662. }
  663. else
  664. {
  665. vote_create_failed_t nReason = m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() ? VOTE_FAILED_YES_MUST_EXCEED_NO : VOTE_FAILED_QUORUM_FAILURE;
  666. SendVoteFailedToPassMessage( nReason );
  667. m_potentialIssues[m_iActiveIssueIndex]->OnVoteFailed( m_iEntityHoldingVote );
  668. m_resetVoteTimer.Start( 5.f );
  669. }
  670. }
  671. // Vote passed check moved down to FrameUpdatePostEntityThink at bottom of this file...
  672. if ( m_resetVoteTimer.HasStarted() && m_resetVoteTimer.IsElapsed() )
  673. {
  674. ResetData();
  675. m_resetVoteTimer.Invalidate();
  676. }
  677. // Size maintenance on m_VoteCallers
  678. if ( m_VoteCallers.Count() >= MAX_VOTER_HISTORY )
  679. {
  680. // Remove older entries
  681. for ( int iIdx = m_VoteCallers.FirstInorder(); iIdx != m_VoteCallers.InvalidIndex(); iIdx = m_VoteCallers.NextInorder( iIdx ) )
  682. {
  683. if ( m_VoteCallers[ iIdx ] - gpGlobals->curtime <= 0 )
  684. {
  685. m_VoteCallers.Remove( iIdx );
  686. }
  687. }
  688. }
  689. SetNextThink( gpGlobals->curtime + 0.5f );
  690. }
  691. //-----------------------------------------------------------------------------
  692. // Purpose: End the vote early if everyone's voted
  693. //-----------------------------------------------------------------------------
  694. void CVoteController::CheckForEarlyVoteClose( void )
  695. {
  696. int nVoteTally = 0;
  697. for ( int index = 0; index < MAX_VOTE_OPTIONS; index++ )
  698. {
  699. nVoteTally += m_nVoteOptionCount.Get( index );
  700. }
  701. if( nVoteTally >= m_nPotentialVotes )
  702. {
  703. m_acceptingVotesTimer.Start( 0 ); // Run the timer out right now
  704. }
  705. }
  706. //-----------------------------------------------------------------------------
  707. // Purpose:
  708. //-----------------------------------------------------------------------------
  709. bool CVoteController::IsValidVoter( CBasePlayer *pWhom )
  710. {
  711. if ( !pWhom )
  712. return false;
  713. if ( !pWhom->IsConnected() )
  714. return false;
  715. if ( pWhom->GetTeamNumber() == TEAM_UNASSIGNED )
  716. return false;
  717. if ( !sv_vote_allow_spectators.GetBool() )
  718. {
  719. if ( pWhom->GetTeamNumber() == TEAM_SPECTATOR )
  720. return false;
  721. }
  722. #ifndef DEBUG // Don't want to do this check for debug builds (so we can test with bots)
  723. if ( pWhom->IsBot() )
  724. return false;
  725. if ( pWhom->IsFakeClient() )
  726. return false;
  727. #endif // DEBUG
  728. if ( pWhom->IsHLTV() )
  729. return false;
  730. if ( pWhom->IsReplay() )
  731. return false;
  732. #ifdef TF_DLL
  733. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  734. {
  735. if ( pWhom->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS )
  736. return false;
  737. }
  738. #endif // TF_DLL
  739. return true;
  740. }
  741. //-----------------------------------------------------------------------------
  742. // Purpose:
  743. //-----------------------------------------------------------------------------
  744. void CVoteController::RegisterIssue( CBaseIssue *pszNewIssue )
  745. {
  746. m_potentialIssues.AddToTail( pszNewIssue );
  747. }
  748. //-----------------------------------------------------------------------------
  749. // Purpose:
  750. //-----------------------------------------------------------------------------
  751. void CVoteController::ListIssues( CBasePlayer *pForWhom )
  752. {
  753. if ( !IsVoteSystemEnabled() )
  754. return;
  755. ClientPrint( pForWhom, HUD_PRINTCONSOLE, "---Vote commands---\n" );
  756. for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
  757. {
  758. CBaseIssue *pCurrentIssue = m_potentialIssues[issueIndex];
  759. pCurrentIssue->ListIssueDetails( pForWhom );
  760. }
  761. ClientPrint( pForWhom, HUD_PRINTCONSOLE, "--- End Vote commands---\n" );
  762. }
  763. //-----------------------------------------------------------------------------
  764. // Purpose: -1 when invalid
  765. //-----------------------------------------------------------------------------
  766. int CVoteController::GetVoteIssueIndexWithHighestCount( void )
  767. {
  768. int nMaxIndex = -1;
  769. // Legacy Yes/No system
  770. if ( m_iActiveIssueIndex != INVALID_ISSUE && m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
  771. {
  772. return ( m_nVoteOptionCount[VOTE_OPTION1] > m_nVoteOptionCount[VOTE_OPTION2] ) ? VOTE_OPTION1 : VOTE_OPTION2;
  773. }
  774. // Which option had the most votes?
  775. else
  776. {
  777. int nMaxCount = 0;
  778. // TODO: Handle ties
  779. for ( int iIndex = 0; iIndex < m_nVoteOptionCount.Count(); iIndex ++ )
  780. {
  781. if ( m_nVoteOptionCount[iIndex] && m_nVoteOptionCount[iIndex] > nMaxCount )
  782. {
  783. nMaxCount = m_nVoteOptionCount[iIndex];
  784. nMaxIndex = iIndex;
  785. }
  786. }
  787. }
  788. return nMaxIndex;
  789. }
  790. //-----------------------------------------------------------------------------
  791. // Purpose: Store steamIDs for every player that calls a vote
  792. //-----------------------------------------------------------------------------
  793. void CVoteController::TrackVoteCaller( CBasePlayer *pPlayer )
  794. {
  795. if ( !pPlayer )
  796. return;
  797. CSteamID steamID;
  798. pPlayer->GetSteamID( &steamID );
  799. int iIdx = m_VoteCallers.Find( steamID.ConvertToUint64() );
  800. if ( iIdx != m_VoteCallers.InvalidIndex() )
  801. {
  802. // Already being tracked - update timer
  803. m_VoteCallers[ iIdx ] = gpGlobals->curtime + sv_vote_creation_timer.GetInt();
  804. return;
  805. }
  806. m_VoteCallers.Insert( steamID.ConvertToUint64(), gpGlobals->curtime + sv_vote_creation_timer.GetInt() );
  807. };
  808. //-----------------------------------------------------------------------------
  809. // Purpose: Check the history of steamIDs that called votes and test against a timer
  810. //-----------------------------------------------------------------------------
  811. bool CVoteController::CanEntityCallVote( CBasePlayer *pPlayer, int &nCooldown, vote_create_failed_t &nErrorCode )
  812. {
  813. if ( !pPlayer )
  814. return false;
  815. #ifndef _DEBUG
  816. CSteamID steamID;
  817. pPlayer->GetSteamID( &steamID );
  818. // Has this SteamID tried to call a vote recently?
  819. int iIdx = m_VoteCallers.Find( steamID.ConvertToUint64() );
  820. if ( iIdx != m_VoteCallers.InvalidIndex() )
  821. {
  822. // Timer elapsed?
  823. nCooldown = (int)( m_VoteCallers[ iIdx ] - gpGlobals->curtime );
  824. if ( nCooldown > 0 )
  825. {
  826. nErrorCode = VOTE_FAILED_RATE_EXCEEDED;
  827. return false;
  828. }
  829. // Expired
  830. m_VoteCallers.Remove( iIdx );
  831. }
  832. #endif
  833. return true;
  834. };
  835. //-----------------------------------------------------------------------------
  836. // Purpose:
  837. //-----------------------------------------------------------------------------
  838. int CVoteController::GetNumVotesCast( void )
  839. {
  840. int nVoteTally = 0;
  841. for ( int index = 0; index < MAX_VOTE_OPTIONS; index++ )
  842. {
  843. nVoteTally += m_nVoteOptionCount.Get( index );
  844. }
  845. return nVoteTally;
  846. }
  847. //-----------------------------------------------------------------------------
  848. // Purpose:
  849. //-----------------------------------------------------------------------------
  850. void CVoteController::AddPlayerToKickWatchList( CSteamID steamID, float flDuration )
  851. {
  852. VoteControllerSystem.AddPlayerToKickWatchList( steamID, flDuration );
  853. }
  854. //-----------------------------------------------------------------------------
  855. // Purpose:
  856. //-----------------------------------------------------------------------------
  857. void CVoteController::AddPlayerToNameLockedList( CSteamID steamID, float flDuration, int nUserID )
  858. {
  859. engine->ServerCommand( UTIL_VarArgs( "namelockid %d %d\n", nUserID, 1 ) );
  860. VoteControllerSystem.AddPlayerToNameLockedList( steamID, flDuration );
  861. }
  862. //-----------------------------------------------------------------------------
  863. // Purpose:
  864. //-----------------------------------------------------------------------------
  865. bool CVoteController::IsPlayerBeingKicked( CBasePlayer *pPlayer )
  866. {
  867. #ifdef TF_DLL
  868. if ( pPlayer && m_iActiveIssueIndex != INVALID_ISSUE )
  869. {
  870. CKickIssue *pKickIssue = dynamic_cast< CKickIssue* >( m_potentialIssues[m_iActiveIssueIndex] );
  871. if ( pKickIssue )
  872. {
  873. return pKickIssue->m_hPlayerTarget == pPlayer;
  874. }
  875. }
  876. #endif // TF_DLL
  877. return false;
  878. }
  879. //-----------------------------------------------------------------------------
  880. // Purpose:
  881. //-----------------------------------------------------------------------------
  882. void CVoteController::GCResponseReceived( bool bVerdict )
  883. {
  884. m_waitingForGCResponseTimer.Invalidate();
  885. if ( m_pendingVoteParams.m_iIssueIndex == INVALID_ISSUE )
  886. return;
  887. // Retry the current vote now that we have our answer
  888. m_potentialIssues[m_pendingVoteParams.m_iIssueIndex]->GCResponseReceived( bVerdict );
  889. SubmitPendingVote( m_pendingVoteParams );
  890. }
  891. //-----------------------------------------------------------------------------
  892. // Purpose: BaseIssue
  893. //-----------------------------------------------------------------------------
  894. CBaseIssue::CBaseIssue( const char *pszTypeString )
  895. {
  896. V_strcpy_safe( m_szTypeString, pszTypeString );
  897. m_iNumYesVotes = 0;
  898. m_iNumNoVotes = 0;
  899. m_iNumPotentialVotes = 0;
  900. m_flNextCallTime = -1.f;
  901. m_bGCNotified = false;
  902. m_bGCApproved = false;
  903. m_bGCResponded = false;
  904. ASSERT( g_voteController );
  905. g_voteController->RegisterIssue( this );
  906. }
  907. //-----------------------------------------------------------------------------
  908. // Purpose:
  909. //-----------------------------------------------------------------------------
  910. CBaseIssue::~CBaseIssue()
  911. {
  912. for ( int index = 0; index < m_FailedVotes.Count(); index++ )
  913. {
  914. FailedVote *pFailedVote = m_FailedVotes[index];
  915. delete pFailedVote;
  916. }
  917. }
  918. //-----------------------------------------------------------------------------
  919. // Purpose:
  920. //-----------------------------------------------------------------------------
  921. const char *CBaseIssue::GetTypeString( void )
  922. {
  923. return m_szTypeString;
  924. }
  925. //-----------------------------------------------------------------------------
  926. // Purpose:
  927. //-----------------------------------------------------------------------------
  928. const char *CBaseIssue::GetDetailsString( void )
  929. {
  930. return m_szDetailsString;
  931. }
  932. //-----------------------------------------------------------------------------
  933. // Purpose:
  934. //-----------------------------------------------------------------------------
  935. void CBaseIssue::SetIssueDetails( const char *pszDetails )
  936. {
  937. V_strcpy_safe( m_szDetailsString, pszDetails );
  938. }
  939. //-----------------------------------------------------------------------------
  940. // Purpose:
  941. //-----------------------------------------------------------------------------
  942. bool CBaseIssue::IsTeamRestrictedVote( void )
  943. {
  944. return false;
  945. }
  946. //-----------------------------------------------------------------------------
  947. // Purpose:
  948. //-----------------------------------------------------------------------------
  949. const char *CBaseIssue::GetVotePassedString( void )
  950. {
  951. return "Unknown vote passed.";
  952. }
  953. //-----------------------------------------------------------------------------
  954. // Purpose: Store failures to prevent vote spam
  955. //-----------------------------------------------------------------------------
  956. void CBaseIssue::OnVoteFailed( int iEntityHoldingVote )
  957. {
  958. // Don't track failed dedicated server votes
  959. if ( BRecordVoteFailureEventForEntity( iEntityHoldingVote ) )
  960. {
  961. // Check for an existing match
  962. for ( int index = 0; index < m_FailedVotes.Count(); index++ )
  963. {
  964. FailedVote *pFailedVote = m_FailedVotes[index];
  965. if ( Q_strcmp( pFailedVote->szFailedVoteParameter, GetDetailsString() ) == 0 )
  966. {
  967. int nTime = sv_vote_failure_timer.GetInt();
  968. #ifdef TF_DLL
  969. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  970. {
  971. nTime = sv_vote_failure_timer_mvm.GetInt();
  972. }
  973. #endif // TF_DLL
  974. pFailedVote->flLockoutTime = gpGlobals->curtime + nTime;
  975. return;
  976. }
  977. }
  978. // Need to create a new one
  979. FailedVote *pNewFailedVote = new FailedVote;
  980. int iIndex = m_FailedVotes.AddToTail( pNewFailedVote );
  981. V_strcpy_safe( m_FailedVotes[iIndex]->szFailedVoteParameter, GetDetailsString() );
  982. m_FailedVotes[iIndex]->flLockoutTime = gpGlobals->curtime + sv_vote_failure_timer.GetFloat();
  983. }
  984. }
  985. //-----------------------------------------------------------------------------
  986. // Purpose:
  987. //-----------------------------------------------------------------------------
  988. bool CBaseIssue::CanTeamCallVote( int iTeam ) const
  989. {
  990. return true;
  991. }
  992. //-----------------------------------------------------------------------------
  993. // Purpose:
  994. //-----------------------------------------------------------------------------
  995. bool CBaseIssue::CanCallVote( int iEntIndex, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime )
  996. {
  997. // Automated server vote - don't bother testing against it
  998. if ( !BRecordVoteFailureEventForEntity( iEntIndex ) )
  999. return true;
  1000. // Bogus player
  1001. if ( iEntIndex == -1 )
  1002. return false;
  1003. // Note: Issue timers reset on level change because the class is created/destroyed during transitions.
  1004. // It'd be nice to refactor the basic framework of the system to get rid of side-effects like this.
  1005. if ( m_flNextCallTime != -1.f && gpGlobals->curtime < m_flNextCallTime )
  1006. {
  1007. nFailCode = VOTE_FAILED_ON_COOLDOWN;
  1008. nTime = m_flNextCallTime - gpGlobals->curtime;
  1009. return false;
  1010. }
  1011. #ifdef TF_DLL
  1012. if ( TFGameRules() && TFGameRules()->IsInWaitingForPlayers() && !TFGameRules()->IsInTournamentMode() )
  1013. {
  1014. nFailCode = VOTE_FAILED_WAITINGFORPLAYERS;
  1015. return false;
  1016. }
  1017. #endif // TF_DLL
  1018. CBaseEntity *pVoteCaller = UTIL_EntityByIndex( iEntIndex );
  1019. if ( pVoteCaller && !CanTeamCallVote( GetVoterTeam( pVoteCaller ) ) )
  1020. {
  1021. nFailCode = VOTE_FAILED_TEAM_CANT_CALL;
  1022. return false;
  1023. }
  1024. // Did this fail recently?
  1025. for ( int iIndex = 0; iIndex < m_FailedVotes.Count(); iIndex++ )
  1026. {
  1027. FailedVote *pCurrentFailure = m_FailedVotes[iIndex];
  1028. int nTimeRemaining = pCurrentFailure->flLockoutTime - gpGlobals->curtime;
  1029. bool bFailed = false;
  1030. // If this issue requires a parameter, see if we're voting for the same one again (i.e. changelevel ctf_2fort)
  1031. if ( Q_strlen( pCurrentFailure->szFailedVoteParameter ) > 0 )
  1032. {
  1033. if( nTimeRemaining > 1 && FStrEq( pCurrentFailure->szFailedVoteParameter, pszDetails ) )
  1034. {
  1035. bFailed = true;
  1036. }
  1037. }
  1038. // Otherwise we have a parameter-less vote, so just check the lockout timer (i.e. restartgame)
  1039. else
  1040. {
  1041. if( nTimeRemaining > 1 )
  1042. {
  1043. bFailed = true;
  1044. }
  1045. }
  1046. if ( bFailed )
  1047. {
  1048. nFailCode = VOTE_FAILED_ON_COOLDOWN;
  1049. nTime = nTimeRemaining;
  1050. return false;
  1051. }
  1052. }
  1053. return true;
  1054. }
  1055. //-----------------------------------------------------------------------------
  1056. // Purpose:
  1057. //-----------------------------------------------------------------------------
  1058. int CBaseIssue::CountPotentialVoters( void )
  1059. {
  1060. int nTotalPlayers = 0;
  1061. for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; ++playerIndex )
  1062. {
  1063. CBasePlayer *pPlayer = UTIL_PlayerByIndex( playerIndex );
  1064. if( g_voteController->IsValidVoter( pPlayer ) )
  1065. {
  1066. if ( g_voteController->CanTeamCastVote( GetVoterTeam( pPlayer ) ) )
  1067. {
  1068. nTotalPlayers++;
  1069. }
  1070. }
  1071. }
  1072. return nTotalPlayers;
  1073. }
  1074. //-----------------------------------------------------------------------------
  1075. // Purpose:
  1076. //-----------------------------------------------------------------------------
  1077. int CBaseIssue::GetNumberVoteOptions( void )
  1078. {
  1079. return 2; // The default issue is Yes/No (so 2), but it can be anywhere between 1 and MAX_VOTE_COUNT
  1080. }
  1081. //-----------------------------------------------------------------------------
  1082. // Purpose:
  1083. //-----------------------------------------------------------------------------
  1084. bool CBaseIssue::IsYesNoVote( void )
  1085. {
  1086. return true; // Default
  1087. }
  1088. //-----------------------------------------------------------------------------
  1089. // Purpose:
  1090. //-----------------------------------------------------------------------------
  1091. void CBaseIssue::SetYesNoVoteCount( int iNumYesVotes, int iNumNoVotes, int iNumPotentialVotes )
  1092. {
  1093. m_iNumYesVotes = iNumYesVotes;
  1094. m_iNumNoVotes = iNumNoVotes;
  1095. m_iNumPotentialVotes = iNumPotentialVotes;
  1096. }
  1097. //-----------------------------------------------------------------------------
  1098. // Purpose:
  1099. //-----------------------------------------------------------------------------
  1100. void CBaseIssue::ListStandardNoArgCommand( CBasePlayer *forWhom, const char *issueString )
  1101. {
  1102. ClientPrint( forWhom, HUD_PRINTCONSOLE, "callvote %s1\n", issueString );
  1103. }
  1104. //-----------------------------------------------------------------------------
  1105. // Purpose:
  1106. //-----------------------------------------------------------------------------
  1107. bool CBaseIssue::GetVoteOptions( CUtlVector <const char*> &vecNames )
  1108. {
  1109. // The default vote issue is a Yes/No vote
  1110. vecNames.AddToHead( "Yes" );
  1111. vecNames.AddToTail( "No" );
  1112. return true;
  1113. }
  1114. //-----------------------------------------------------------------------------
  1115. // Purpose:
  1116. //-----------------------------------------------------------------------------
  1117. float CBaseIssue::GetQuorumRatio( void )
  1118. {
  1119. return sv_vote_quorum_ratio.GetFloat();
  1120. }
  1121. //-----------------------------------------------------------------------------
  1122. // Purpose:
  1123. //-----------------------------------------------------------------------------
  1124. void CBaseIssue::GCResponseReceived( bool bApproved )
  1125. {
  1126. m_bGCResponded = true;
  1127. m_bGCApproved = bApproved;
  1128. }