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.

1290 lines
40 KiB

  1. //========= Copyright � 1996-2005, 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 "cs_gamerules.h"
  14. #include "usermessages.h"
  15. #ifdef TF_DLL
  16. #include "tf/tf_gamerules.h"
  17. #endif
  18. #include "EventLog.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  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_voteControllerGlobal = NULL;
  34. CVoteController *g_voteControllerCT = NULL;
  35. CVoteController *g_voteControllerT = NULL;
  36. ConVar sv_vote_timer_duration("sv_vote_timer_duration", "15", FCVAR_RELEASE, "How long to allow voting on an issue");
  37. ConVar sv_vote_command_delay("sv_vote_command_delay", "2", FCVAR_RELEASE, "How long after a vote passes until the action happens", false, 0, true, 4.5);
  38. ConVar sv_allow_votes("sv_allow_votes", "1", FCVAR_RELEASE, "Allow voting?");
  39. ConVar sv_vote_failure_timer("sv_vote_failure_timer", "300", FCVAR_RELEASE, "A vote that fails cannot be re-submitted for this long");
  40. ConVar sv_vote_creation_timer("sv_vote_creation_timer", "120", FCVAR_RELEASE, "How often someone can individually call a vote.");
  41. // default value of the sv_vote_quorum_ratio is 0.501 so on a 32 player server, you will still need 1 more than half, otherwise at 0.6, you would need 20 people to vote yes instead of 17
  42. ConVar sv_vote_quorum_ratio( "sv_vote_quorum_ratio", "0.501", FCVAR_RELEASE, "The minimum ratio of players needed to vote on an issue to resolve it.", true, 0.01, true, 1.0 );
  43. ConVar sv_vote_allow_spectators( "sv_vote_allow_spectators", "0", FCVAR_RELEASE, "Allow spectators to initiate votes?" );
  44. ConVar sv_vote_count_spectator_votes( "sv_vote_count_spectator_votes", "0", FCVAR_RELEASE, "Allow spectators to vote on issues?" );
  45. ConVar sv_vote_allow_in_warmup( "sv_vote_allow_in_warmup", "0", FCVAR_RELEASE, "Allow voting during warmup?" );
  46. ConVar sv_vote_disallow_kick_on_match_point( "sv_vote_disallow_kick_on_match_point", "0", FCVAR_RELEASE, "Disallow vote kicking on the match point round." );
  47. //-----------------------------------------------------------------------------
  48. // Purpose:
  49. //-----------------------------------------------------------------------------
  50. void CommandListIssues( void )
  51. {
  52. CBasePlayer *commandIssuer = UTIL_GetCommandClient() ;
  53. if ( !commandIssuer )
  54. return;
  55. // list team-specific issues
  56. if ( commandIssuer->GetTeamVoteController() )
  57. {
  58. commandIssuer->GetTeamVoteController()->ListIssues( commandIssuer );
  59. }
  60. // and always list global issues
  61. if ( g_voteControllerGlobal )
  62. {
  63. g_voteControllerGlobal->ListIssues( commandIssuer );
  64. }
  65. }
  66. //-----------------------------------------------------------------------------
  67. // Purpose:
  68. //-----------------------------------------------------------------------------
  69. ConCommand ListIssues("listissues", CommandListIssues, "List all the issues that can be voted on.", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS );
  70. //-----------------------------------------------------------------------------
  71. // Purpose: This should eventually ask the player what team they are voting on
  72. // to take into account different idle / spectator rules.
  73. //-----------------------------------------------------------------------------
  74. int GetVoterTeam( CBaseEntity *pEntity )
  75. {
  76. if ( !pEntity )
  77. return TEAM_UNASSIGNED;
  78. CBasePlayer *pPlayer = ToBasePlayer( pEntity );
  79. if ( !pPlayer )
  80. return TEAM_UNASSIGNED;
  81. int iTeam = pPlayer->GetAssociatedTeamNumber( );
  82. return iTeam;
  83. }
  84. //-----------------------------------------------------------------------------
  85. // Purpose:
  86. //-----------------------------------------------------------------------------
  87. CON_COMMAND_F( callvote, "Start a vote on an issue.", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS )
  88. {
  89. if ( !g_voteControllerGlobal || !g_voteControllerCT || !g_voteControllerT )
  90. {
  91. DevMsg( "Vote Controllers Not Found!\n" );
  92. return;
  93. }
  94. CBasePlayer *pVoteCaller = UTIL_GetCommandClient();
  95. int iEntindex = 99;
  96. if ( pVoteCaller )
  97. iEntindex = pVoteCaller->entindex();
  98. if ( !sv_vote_allow_spectators.GetBool() && pVoteCaller && pVoteCaller->IsSpectator() )
  99. {
  100. g_voteControllerGlobal->SendVoteFailedMessage( VOTE_FAILED_SPECTATOR, pVoteCaller );
  101. return;
  102. }
  103. // Parameters
  104. char szEmptyDetails[ MAX_VOTE_DETAILS_LENGTH ];
  105. szEmptyDetails[ 0 ] = '\0';
  106. const char *arg2 = args[ 1 ];
  107. const char *arg3 = args.ArgC() >= 3 ? args[ 2 ] : szEmptyDetails;
  108. CVoteController *pVoteController;
  109. if ( pVoteCaller )
  110. {
  111. pVoteController = pVoteCaller->GetTeamVoteController();
  112. }
  113. else
  114. {
  115. pVoteController = g_voteControllerGlobal;
  116. }
  117. // If we don't have any arguments, invoke VoteSetup UI
  118. if( args.ArgC() < 2 )
  119. {
  120. pVoteController->SetupVote( iEntindex );
  121. return;
  122. }
  123. if ( g_voteControllerGlobal->HasIssue( arg2 ) )
  124. {
  125. g_voteControllerGlobal->CreateVote( iEntindex, arg2, arg3 );
  126. }
  127. else if ( pVoteController->HasIssue( arg2 ) )
  128. {
  129. pVoteController->CreateVote( iEntindex, arg2, arg3 );
  130. }
  131. else
  132. {
  133. DevMsg( "Vote Issue Not Found!\n" );
  134. return;
  135. }
  136. }
  137. //-----------------------------------------------------------------------------
  138. // Purpose:
  139. //-----------------------------------------------------------------------------
  140. CVoteController::~CVoteController()
  141. {
  142. if ( g_voteControllerGlobal == this ) { g_voteControllerGlobal = NULL; }
  143. else if ( g_voteControllerCT == this ) { g_voteControllerCT = NULL; }
  144. else if ( g_voteControllerT == this ) { g_voteControllerT = NULL; }
  145. for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
  146. {
  147. delete m_potentialIssues[issueIndex];
  148. }
  149. }
  150. //-----------------------------------------------------------------------------
  151. // Purpose:
  152. //-----------------------------------------------------------------------------
  153. void CVoteController::ResetData( void )
  154. {
  155. m_iActiveIssueIndex = INVALID_ISSUE;
  156. for ( int index = 0; index < m_nVoteOptionCount.Count(); index++ )
  157. {
  158. m_nVoteOptionCount.Set( index, 0 );
  159. }
  160. m_nPotentialVotes = 0;
  161. m_acceptingVotesTimer.Invalidate();
  162. m_executeCommandTimer.Invalidate();
  163. m_iEntityHoldingVote = -1;
  164. m_iOnlyTeamToVote = TEAM_INVALID;
  165. m_bIsYesNoVote = true;
  166. for( int voteIndex = 0; voteIndex < MAX_PLAYERS; ++voteIndex )
  167. {
  168. m_nVotesCast[voteIndex] = VOTE_UNCAST;
  169. }
  170. m_arrVotedUsers.RemoveAll();
  171. }
  172. //-----------------------------------------------------------------------------
  173. // Purpose:
  174. //-----------------------------------------------------------------------------
  175. void CVoteController::Spawn( void )
  176. {
  177. ResetData();
  178. BaseClass::Spawn();
  179. SetThink( &CVoteController::VoteControllerThink );
  180. SetNextThink( gpGlobals->curtime );
  181. }
  182. //-----------------------------------------------------------------------------
  183. // Purpose:
  184. //-----------------------------------------------------------------------------
  185. int CVoteController::UpdateTransmitState( void )
  186. {
  187. // ALWAYS transmit to all clients.
  188. return SetTransmitState( FL_EDICT_ALWAYS );
  189. }
  190. //-----------------------------------------------------------------------------
  191. // Purpose:
  192. //-----------------------------------------------------------------------------
  193. bool CVoteController::CanTeamCastVote( int iTeam ) const
  194. {
  195. if ( m_iOnlyTeamToVote == TEAM_INVALID )
  196. return true;
  197. return iTeam == m_iOnlyTeamToVote;
  198. }
  199. //-----------------------------------------------------------------------------
  200. // Purpose: Handles menu-driven setup of Voting
  201. //-----------------------------------------------------------------------------
  202. bool CVoteController::SetupVote( int iEntIndex )
  203. {
  204. CBasePlayer *pVoteCaller = UTIL_PlayerByIndex( iEntIndex );
  205. if( !pVoteCaller )
  206. return false;
  207. bool bAllowVotes = sv_allow_votes.GetBool();
  208. if ( bAllowVotes && m_potentialIssues.Count() )
  209. {
  210. CSingleUserRecipientFilter filter( pVoteCaller );
  211. filter.MakeReliable();
  212. CCSUsrMsg_VoteSetup msg;
  213. for( int iIndex = 0; iIndex < m_potentialIssues.Count(); ++iIndex )
  214. {
  215. CBaseIssue *pCurrentIssue = m_potentialIssues[iIndex];
  216. if ( pCurrentIssue )
  217. {
  218. if ( pCurrentIssue->IsEnabled() )
  219. {
  220. msg.add_potential_issues( pCurrentIssue->GetTypeString() );
  221. }
  222. else
  223. {
  224. char szDisabledIssueStr[MAX_COMMAND_LENGTH + 12];
  225. V_strcpy( szDisabledIssueStr, pCurrentIssue->GetTypeString() );
  226. V_strcat( szDisabledIssueStr, " (Disabled on Server)", sizeof(szDisabledIssueStr) );
  227. msg.add_potential_issues( szDisabledIssueStr );
  228. }
  229. }
  230. }
  231. SendUserMessage( filter, CS_UM_VoteSetup, msg );
  232. }
  233. return true;
  234. }
  235. //-----------------------------------------------------------------------------
  236. // Purpose: Handles console-driven setup of Voting
  237. //-----------------------------------------------------------------------------
  238. bool CVoteController::CreateVote( int iEntIndex, const char *pszTypeString, const char *pszDetailString )
  239. {
  240. // Terrible Hack: Dedicated servers pass 99 as the EntIndex
  241. bool bDedicatedServer = ( iEntIndex == 99 ) ? true : false;
  242. CBasePlayer *pVoteCaller = UTIL_PlayerByIndex( iEntIndex );
  243. if ( !pVoteCaller && !bDedicatedServer )
  244. return false;
  245. if( !sv_allow_votes.GetBool() )
  246. {
  247. SendVoteFailedMessage( VOTE_FAILED_DISABLED, pVoteCaller );
  248. return false;
  249. }
  250. // Already running a vote?
  251. if ( IsAVoteInProgress() )
  252. {
  253. // send a message to the user to who tried to vote that their vote failed and why
  254. ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#SFUI_vote_failed_vote_in_progress" );
  255. return false;
  256. }
  257. // Find the issue the user is asking for
  258. for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
  259. {
  260. CBaseIssue *pCurrentIssue = m_potentialIssues[issueIndex];
  261. if ( !pCurrentIssue )
  262. return false;
  263. if( FStrEq( pszTypeString, pCurrentIssue->GetTypeString() ) )
  264. {
  265. vote_create_failed_t nErrorCode = VOTE_FAILED_GENERIC;
  266. int nTime = 0;
  267. if( pCurrentIssue->CanCallVote( iEntIndex, pszTypeString, pszDetailString, nErrorCode, nTime ) )
  268. {
  269. // Prevent spamming commands
  270. #ifndef _DEBUG
  271. if ( pVoteCaller && !pCurrentIssue->ShouldIgnoreCreationTimer() )
  272. {
  273. int nTimeLeft = sv_vote_creation_timer.GetFloat() - pVoteCaller->GetLastHeldVoteTimer().GetElapsedTime();
  274. if( pVoteCaller->GetLastHeldVoteTimer().HasStarted() && nTimeLeft > 1 )
  275. {
  276. SendVoteFailedMessage( VOTE_FAILED_RATE_EXCEEDED, pVoteCaller, nTimeLeft );
  277. return false;
  278. }
  279. }
  280. #endif
  281. // if this is not an instant voting issue ( i.e. tournament pause match )
  282. if (pCurrentIssue->GetVotesRequiredToPass() > 1 )
  283. {
  284. // can't call it if there's a global vote in progress.
  285. if ( g_voteControllerGlobal && g_voteControllerGlobal->IsAVoteInProgress() )
  286. {
  287. // send a message to the user to who tried to vote that their vote failed and why
  288. ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#SFUI_vote_failed_vote_in_progress" );
  289. return false;
  290. }
  291. // can't call it if this is a global vote and the other team is mid-vote.
  292. CVoteController * pOtherTeamVoteController = g_voteControllerGlobal;
  293. if ( pVoteCaller )
  294. {
  295. switch ( pVoteCaller->GetAssociatedTeamNumber() )
  296. {
  297. case TEAM_CT:
  298. pOtherTeamVoteController = g_voteControllerT;
  299. break;
  300. case TEAM_TERRORIST:
  301. pOtherTeamVoteController = g_voteControllerCT;
  302. break;
  303. default:
  304. break;
  305. }
  306. }
  307. if ( ( this == g_voteControllerGlobal ) && ( pOtherTeamVoteController && pOtherTeamVoteController->IsAVoteInProgress() ) )
  308. {
  309. // send a message to the user to who tried to vote that their vote failed and why
  310. ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#SFUI_vote_failed_vote_in_progress" );
  311. return false;
  312. }
  313. }
  314. // Establish a bunch of data on this particular issue
  315. m_iEntityHoldingVote = iEntIndex;
  316. pCurrentIssue->SetIssueDetails( pszDetailString );
  317. m_bIsYesNoVote = pCurrentIssue->IsYesNoVote();
  318. m_iActiveIssueIndex = issueIndex;
  319. m_iOnlyTeamToVote = TEAM_INVALID;
  320. if ( !bDedicatedServer && pCurrentIssue->IsAllyRestrictedVote() )
  321. m_iOnlyTeamToVote = GetVoterTeam( pVoteCaller );
  322. // Now get our choices
  323. m_VoteOptions.RemoveAll();
  324. pCurrentIssue->GetVoteOptions( m_VoteOptions );
  325. int nNumVoteOptions = m_VoteOptions.Count();
  326. if ( nNumVoteOptions >= 2 )
  327. {
  328. IGameEvent *event = gameeventmanager->CreateEvent( "vote_options" );
  329. if ( event )
  330. {
  331. event->SetInt( "count", nNumVoteOptions );
  332. for ( int iIndex = 0; iIndex < nNumVoteOptions; iIndex++ )
  333. {
  334. char szNumber[2];
  335. Q_snprintf( szNumber, sizeof( szNumber ), "%i", iIndex + 1 );
  336. char szOptionName[8] = "option";
  337. Q_strncat( szOptionName, szNumber, sizeof( szOptionName ), COPY_ALL_CHARACTERS );
  338. event->SetString( szOptionName, m_VoteOptions[iIndex] );
  339. }
  340. gameeventmanager->FireEvent( event );
  341. }
  342. }
  343. else
  344. {
  345. Assert( nNumVoteOptions >= 2 );
  346. }
  347. // Get the data out to the client
  348. for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; ++playerIndex )
  349. {
  350. CBasePlayer *pPlayer = UTIL_PlayerByIndex( playerIndex );
  351. if ( pPlayer && pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
  352. {
  353. CSingleUserRecipientFilter filter( pPlayer );
  354. filter.MakeReliable();
  355. CCSUsrMsg_VoteStart msg;
  356. msg.set_team( m_iOnlyTeamToVote ); // move into the filter
  357. msg.set_ent_idx( m_iEntityHoldingVote );
  358. msg.set_vote_type( pCurrentIssue->GetVoteIssue() );
  359. msg.set_disp_str( pCurrentIssue->GetDisplayString() );
  360. msg.set_details_str( pCurrentIssue->GetDetailsString() );
  361. msg.set_other_team_str( pCurrentIssue->GetOtherTeamDisplayString() );
  362. msg.set_is_yes_no_vote( m_bIsYesNoVote );
  363. SendUserMessage( filter, CS_UM_VoteStart, msg );
  364. }
  365. }
  366. UTIL_LogPrintf( "Vote started \"%s %s\" from #%u %s\n",
  367. pCurrentIssue->GetTypeString(),
  368. pCurrentIssue->GetDetailsString(),
  369. m_iEntityHoldingVote,
  370. pVoteCaller ? GameLogSystem()->FormatPlayer( pVoteCaller ) : "n/a" );
  371. m_nPotentialVotes = pCurrentIssue->CountPotentialVoters();
  372. // FIX TO MAKE REMATCH ONLY
  373. float flVoteDuration = CSGameRules()->IsQueuedMatchmaking() ? 45.0f : sv_vote_timer_duration.GetFloat();
  374. m_acceptingVotesTimer.Start( flVoteDuration );
  375. pCurrentIssue->OnVoteStarted();
  376. // Force the vote holder to agree with a Yes/No vote
  377. if ( m_bIsYesNoVote && !bDedicatedServer )
  378. {
  379. TryCastVote( iEntIndex, "Option1" );
  380. }
  381. if ( !bDedicatedServer )
  382. {
  383. pVoteCaller->GetLastHeldVoteTimer().Start();
  384. }
  385. return true;
  386. }
  387. else
  388. {
  389. if ( !bDedicatedServer )
  390. {
  391. SendVoteFailedMessage( pCurrentIssue->MakeVoteFailErrorCodeForClients( nErrorCode ), pVoteCaller, nTime );
  392. }
  393. }
  394. }
  395. }
  396. return false;
  397. }
  398. //-----------------------------------------------------------------------------
  399. // Purpose: Sent to everyone, unless we pass a player pointer
  400. //-----------------------------------------------------------------------------
  401. void CVoteController::SendVoteFailedMessage( vote_create_failed_t nReason, CBasePlayer *pVoteCaller, int nTime )
  402. {
  403. // driller: need to merge all failure case stuff into a single path
  404. if ( pVoteCaller )
  405. {
  406. CSingleUserRecipientFilter user( pVoteCaller );
  407. user.MakeReliable();
  408. CCSUsrMsg_CallVoteFailed msg;
  409. msg.set_reason( nReason );
  410. msg.set_time( nTime );
  411. SendUserMessage( user, CS_UM_CallVoteFailed, msg );
  412. }
  413. else
  414. {
  415. UTIL_LogPrintf("Vote failed \"%s %s\" \n",
  416. m_potentialIssues[m_iActiveIssueIndex]->GetTypeString(),
  417. m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString() );
  418. CBroadcastRecipientFilter filter;
  419. filter.MakeReliable();
  420. CCSUsrMsg_VoteFailed msg;
  421. msg.set_team( m_iOnlyTeamToVote );
  422. msg.set_reason( nReason );
  423. SendUserMessage( filter, CS_UM_VoteFailed, msg );
  424. }
  425. }
  426. //-----------------------------------------------------------------------------
  427. // Purpose: Player generated a vote command. i.e. /vote option1
  428. //-----------------------------------------------------------------------------
  429. CVoteController::TryCastVoteResult CVoteController::TryCastVote( int iEntIndex, const char *pszVoteString )
  430. {
  431. if( !sv_allow_votes.GetBool() )
  432. return CAST_FAIL_SERVER_DISABLE;
  433. if( iEntIndex > MAX_PLAYERS )
  434. return CAST_FAIL_SYSTEM_ERROR;
  435. if( m_iActiveIssueIndex == INVALID_ISSUE )
  436. return CAST_FAIL_NO_ACTIVE_ISSUE;
  437. if( m_executeCommandTimer.HasStarted() )
  438. return CAST_FAIL_VOTE_CLOSED;
  439. CBaseEntity *pVoter = UTIL_EntityByIndex( iEntIndex );
  440. if ( !IsValidVoter( ToBasePlayer( pVoter ) ) )
  441. return CAST_FAIL_TEAM_RESTRICTED;
  442. if( m_potentialIssues[m_iActiveIssueIndex] && m_potentialIssues[m_iActiveIssueIndex]->IsAllyRestrictedVote() )
  443. {
  444. CBaseEntity *pVoteHolder = UTIL_EntityByIndex( m_iEntityHoldingVote );
  445. if( ( pVoteHolder == NULL ) || ( pVoter == NULL ) || ( GetVoterTeam( pVoteHolder ) != GetVoterTeam( pVoter ) ) )
  446. {
  447. return CAST_FAIL_TEAM_RESTRICTED;
  448. }
  449. }
  450. // Look for a previous vote
  451. int nOldVote = VOTE_UNCAST;
  452. if ( iEntIndex < MAX_PLAYERS )
  453. {
  454. nOldVote = m_nVotesCast[iEntIndex];
  455. }
  456. #ifndef DEBUG
  457. if( nOldVote != VOTE_UNCAST )
  458. {
  459. return CAST_FAIL_NO_CHANGES;
  460. }
  461. #endif // !DEBUG
  462. // Which option are they voting for?
  463. int nCurrentVote = VOTE_UNCAST;
  464. if ( !StringHasPrefix( pszVoteString, "Option" ) )
  465. return CAST_FAIL_SYSTEM_ERROR;
  466. nCurrentVote = (CastVote)( atoi( pszVoteString + V_strlen( "Option" ) ) - 1 );
  467. if ( nCurrentVote < VOTE_OPTION1 || nCurrentVote > VOTE_OPTION5 )
  468. return CAST_FAIL_SYSTEM_ERROR;
  469. // They're changing their vote
  470. #ifdef DEBUG
  471. if ( nOldVote != VOTE_UNCAST )
  472. {
  473. if( nOldVote == nCurrentVote )
  474. {
  475. return CAST_FAIL_DUPLICATE;
  476. }
  477. VoteChoice_Decrement( nOldVote );
  478. }
  479. #endif // DEBUG
  480. // With a Yes/No vote, slam anything past "No" to No
  481. if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
  482. {
  483. if ( nCurrentVote > VOTE_OPTION2 )
  484. nCurrentVote = VOTE_OPTION2;
  485. }
  486. #ifndef DEBUG
  487. if ( CBasePlayer *pBasePlayerVoter = ToBasePlayer( pVoter ) )
  488. {
  489. if ( uint64 uiSteamID = pBasePlayerVoter->GetSteamIDAsUInt64() )
  490. {
  491. if ( m_arrVotedUsers.Find( uiSteamID ) == m_arrVotedUsers.InvalidIndex() )
  492. {
  493. m_arrVotedUsers.AddToTail( uiSteamID ); // remember that this user already voted
  494. }
  495. else
  496. {
  497. return CAST_FAIL_NO_CHANGES;
  498. }
  499. }
  500. }
  501. #endif
  502. // Register and track this vote
  503. VoteChoice_Increment( nCurrentVote );
  504. m_nVotesCast[iEntIndex] = nCurrentVote;
  505. // Tell the client-side UI
  506. IGameEvent *event = gameeventmanager->CreateEvent( "vote_cast" );
  507. if ( event )
  508. {
  509. event->SetInt( "vote_option", nCurrentVote );
  510. event->SetInt( "team", m_iOnlyTeamToVote );
  511. event->SetInt( "entityid", iEntIndex );
  512. gameeventmanager->FireEvent( event );
  513. }
  514. UTIL_LogPrintf( "Vote cast \"%s %s\" from #%u %s option%d\n",
  515. m_potentialIssues[m_iActiveIssueIndex]->GetTypeString(),
  516. m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString(),
  517. iEntIndex,
  518. pVoter ? GameLogSystem()->FormatPlayer( pVoter ) : "n/a",
  519. nCurrentVote );
  520. CheckForEarlyVoteClose();
  521. return CAST_OK;
  522. }
  523. //-----------------------------------------------------------------------------
  524. // Purpose: Increments the vote count for a particular vote option
  525. // i.e. nVoteChoice = 0 might mean a Yes vote
  526. //-----------------------------------------------------------------------------
  527. void CVoteController::VoteChoice_Increment( int nVoteChoice )
  528. {
  529. if ( nVoteChoice < VOTE_OPTION1 || nVoteChoice > VOTE_OPTION5 )
  530. return;
  531. int nValue = m_nVoteOptionCount.Get( nVoteChoice );
  532. m_nVoteOptionCount.Set( nVoteChoice, ++nValue );
  533. }
  534. //-----------------------------------------------------------------------------
  535. // Purpose:
  536. //-----------------------------------------------------------------------------
  537. void CVoteController::VoteChoice_Decrement( int nVoteChoice )
  538. {
  539. if ( nVoteChoice < VOTE_OPTION1 || nVoteChoice > VOTE_OPTION5 )
  540. return;
  541. int nValue = m_nVoteOptionCount.Get( nVoteChoice );
  542. m_nVoteOptionCount.Set( nVoteChoice, --nValue );
  543. }
  544. //-----------------------------------------------------------------------------
  545. // Purpose:
  546. //-----------------------------------------------------------------------------
  547. void CVoteController::VoteControllerThink( void )
  548. {
  549. if ( !m_potentialIssues.IsValidIndex( m_iActiveIssueIndex ) )
  550. {
  551. SetNextThink( gpGlobals->curtime + 0.5f );
  552. return;
  553. }
  554. CBaseIssue *pActiveIssue = m_potentialIssues[m_iActiveIssueIndex];
  555. int nWinningVoteOption = GetWinningVoteOption();
  556. if ( !pActiveIssue->IsYesNoVote() || nWinningVoteOption >= m_VoteOptions.Count() )
  557. {
  558. Assert( nWinningVoteOption >= 0 && nWinningVoteOption < m_VoteOptions.Count() );
  559. Msg( "Trying to resolve a vote that is not a YES/NO vote, YES/NO votes aren't currently supported! \n");
  560. return;
  561. }
  562. bool bVoteHasFinished = false;
  563. bool bVoteShouldBailOnNos = (pActiveIssue->GetVoteIssue() == VOTEISSUE_REMATCH);
  564. // Vote time is up - process the result
  565. if( m_acceptingVotesTimer.HasStarted() )
  566. {
  567. int nNumVotesYES = m_nVoteOptionCount[VOTE_OPTION1];
  568. int nNumVotesNO = m_nVoteOptionCount[VOTE_OPTION2];
  569. //int nVoteTally = nNumVotesYES + nNumVotesNO;
  570. bool bVotePassed = false;
  571. int nVotesToSucceed = pActiveIssue->GetVotesRequiredToPass();
  572. // Have we exceeded the required ratio of Voted-vs-Abstained?
  573. if ( nNumVotesYES >= nVotesToSucceed )
  574. {
  575. // bail early, we succeeded
  576. bVotePassed = true;
  577. bVoteHasFinished = true;
  578. }
  579. else if ( bVoteShouldBailOnNos && (m_nPotentialVotes - nNumVotesNO) < nVotesToSucceed )
  580. {
  581. // failed because too many no votes
  582. SendVoteFailedMessage( pActiveIssue->MakeVoteFailErrorCodeForClients( VOTE_FAILED_YES_MUST_EXCEED_NO ) );
  583. bVotePassed = false;
  584. bVoteHasFinished = true;
  585. }
  586. else if ( m_acceptingVotesTimer.IsElapsed() )
  587. {
  588. // timed out, not enough people voted yes
  589. SendVoteFailedMessage( pActiveIssue->MakeVoteFailErrorCodeForClients( VOTE_FAILED_QUORUM_FAILURE ) );
  590. bVotePassed = false;
  591. bVoteHasFinished = true;
  592. }
  593. if ( bVoteHasFinished )
  594. {
  595. // for record-keeping
  596. if ( pActiveIssue->IsYesNoVote() )
  597. {
  598. pActiveIssue->SetYesNoVoteCount( m_nVoteOptionCount[VOTE_OPTION1], m_nVoteOptionCount[VOTE_OPTION2], m_nPotentialVotes );
  599. }
  600. m_acceptingVotesTimer.Invalidate();
  601. if ( bVotePassed )
  602. {
  603. m_executeCommandTimer.Start( pActiveIssue->GetCommandDelay() );
  604. m_resetVoteTimer.Start( 5.0 );
  605. CBaseEntity *pVoteHolder = UTIL_EntityByIndex( m_iEntityHoldingVote );
  606. CBasePlayer *pVoteHolderPlayer = ( pVoteHolder && pVoteHolder->IsPlayer() ) ? (CBasePlayer *)( pVoteHolder ) : NULL;
  607. if( pVoteHolderPlayer )
  608. {
  609. pVoteHolderPlayer->GetLastHeldVoteTimer().Invalidate(); // You can go ahead and make a new vote since yours passed.
  610. }
  611. UTIL_LogPrintf("Vote succeeded \"%s %s\" from #%u %s\n",
  612. pActiveIssue->GetTypeString(),
  613. pActiveIssue->GetDetailsString(),
  614. m_iEntityHoldingVote,
  615. pVoteHolderPlayer ? GameLogSystem()->FormatPlayer( pVoteHolderPlayer ) : "n/a" );
  616. CBroadcastRecipientFilter filter;
  617. filter.MakeReliable();
  618. CCSUsrMsg_VotePass msg;
  619. msg.set_team( m_iOnlyTeamToVote );
  620. msg.set_vote_type( pActiveIssue->GetVoteIssue() );
  621. msg.set_disp_str( pActiveIssue->GetVotePassedString() );
  622. msg.set_details_str( pActiveIssue->GetDetailsString() );
  623. SendUserMessage( filter, CS_UM_VotePass, msg );
  624. }
  625. else
  626. {
  627. pActiveIssue->OnVoteFailed();
  628. m_resetVoteTimer.Start( 5.0 );
  629. }
  630. }
  631. }
  632. // Vote passed - execute the command
  633. if( m_executeCommandTimer.HasStarted() && m_executeCommandTimer.IsElapsed() )
  634. {
  635. m_executeCommandTimer.Invalidate();
  636. m_potentialIssues[m_iActiveIssueIndex]->ExecuteCommand();
  637. }
  638. if ( m_resetVoteTimer.HasStarted() && m_resetVoteTimer.IsElapsed() )
  639. {
  640. ResetData();
  641. m_resetVoteTimer.Invalidate();
  642. }
  643. SetNextThink( gpGlobals->curtime + 0.1f );
  644. }
  645. //-----------------------------------------------------------------------------
  646. // Purpose: End the vote early if everyone's voted
  647. //-----------------------------------------------------------------------------
  648. void CVoteController::CheckForEarlyVoteClose( void )
  649. {
  650. int nVoteTally = 0;
  651. for ( int index = 0; index < MAX_VOTE_OPTIONS; index++ )
  652. {
  653. nVoteTally += m_nVoteOptionCount.Get( index );
  654. }
  655. if( nVoteTally >= m_nPotentialVotes )
  656. {
  657. m_acceptingVotesTimer.Start( 0 ); // Run the timer out right now
  658. }
  659. }
  660. #ifdef DEBUG // Don't want to do this check for debug builds (so we can test with bots)
  661. ConVar sv_vote_ignore_bots( "sv_vote_ignore_bots", "0" );
  662. #define SV_VOTE_IGNORE_BOTS sv_vote_ignore_bots.GetBool()
  663. #else
  664. #define SV_VOTE_IGNORE_BOTS true
  665. #endif
  666. //-----------------------------------------------------------------------------
  667. // Purpose:
  668. //-----------------------------------------------------------------------------
  669. bool CVoteController::IsValidVoter( CBasePlayer *pWhom )
  670. {
  671. if ( pWhom == NULL )
  672. return false;
  673. if ( !pWhom->IsConnected() )
  674. return false;
  675. if ( !sv_vote_allow_spectators.GetBool() || !sv_vote_count_spectator_votes.GetBool() )
  676. {
  677. if ( pWhom->GetTeamNumber() != TEAM_TERRORIST && pWhom->GetTeamNumber() != TEAM_CT )
  678. return false;
  679. }
  680. if ( SV_VOTE_IGNORE_BOTS ) // Don't want to do this check for debug builds (so we can test with bots)
  681. {
  682. if ( pWhom->IsBot() )
  683. return false;
  684. if ( pWhom->IsFakeClient() )
  685. return false;
  686. }
  687. if ( pWhom->IsHLTV() )
  688. return false;
  689. if ( pWhom->IsReplay() )
  690. return false;
  691. return true;
  692. }
  693. //-----------------------------------------------------------------------------
  694. // Purpose:
  695. //-----------------------------------------------------------------------------
  696. void CVoteController::RegisterIssue( CBaseIssue *pszNewIssue )
  697. {
  698. m_potentialIssues.AddToTail( pszNewIssue );
  699. }
  700. //-----------------------------------------------------------------------------
  701. // Purpose:
  702. //-----------------------------------------------------------------------------
  703. void CVoteController::ListIssues( CBasePlayer *pForWhom )
  704. {
  705. if( !sv_allow_votes.GetBool() )
  706. return;
  707. ClientPrint( pForWhom, HUD_PRINTCONSOLE, "---Vote commands---\n" );
  708. for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
  709. {
  710. CBaseIssue *pCurrentIssue = m_potentialIssues[issueIndex];
  711. pCurrentIssue->ListIssueDetails( pForWhom );
  712. }
  713. ClientPrint( pForWhom, HUD_PRINTCONSOLE, "--- End Vote commands---\n" );
  714. }
  715. //-----------------------------------------------------------------------------
  716. // Purpose:
  717. //-----------------------------------------------------------------------------
  718. int CVoteController::GetWinningVoteOption( void )
  719. {
  720. if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
  721. {
  722. return ( m_nVoteOptionCount[VOTE_OPTION1] > m_nVoteOptionCount[VOTE_OPTION2] ) ? VOTE_OPTION1 : VOTE_OPTION2;
  723. }
  724. else
  725. {
  726. CUtlVector <int> pVoteCounts;
  727. // Which option had the most votes?
  728. // driller: Need to handle ties
  729. int nHighest = m_nVoteOptionCount[0];
  730. for ( int iIndex = 0; iIndex < m_nVoteOptionCount.Count(); iIndex ++ )
  731. {
  732. nHighest = ( ( nHighest < m_nVoteOptionCount[iIndex] ) ? m_nVoteOptionCount[iIndex] : nHighest );
  733. pVoteCounts.AddToTail( m_nVoteOptionCount[iIndex] );
  734. }
  735. m_nHighestCountIndex = -1;
  736. for ( int iIndex = 0; iIndex < m_nVoteOptionCount.Count(); iIndex++ )
  737. {
  738. if ( m_nVoteOptionCount[iIndex] == nHighest )
  739. {
  740. m_nHighestCountIndex = iIndex;
  741. // henryg: break on first match, not last. this avoids a crash
  742. // if we are all tied at zero and we pick something beyond the
  743. // last vote option. this code really ought to ignore attempts
  744. // to tally votes for options beyond the last valid one!
  745. break;
  746. }
  747. }
  748. return m_nHighestCountIndex;
  749. }
  750. return -1;
  751. }
  752. bool CVoteController::HasIssue( const char *pszIssue )
  753. {
  754. for ( int issueIndex = 0; issueIndex < m_potentialIssues.Count( ); ++issueIndex )
  755. {
  756. CBaseIssue *pCurrentIssue = m_potentialIssues[ issueIndex ];
  757. if ( !pCurrentIssue )
  758. return false;
  759. if ( FStrEq( pszIssue, pCurrentIssue->GetTypeString( ) ) )
  760. return true;
  761. }
  762. return false;
  763. }
  764. void CVoteController::EndVoteImmediately( void )
  765. {
  766. if ( !IsAVoteInProgress( ) )
  767. return;
  768. CBaseIssue *pActiveIssue = m_potentialIssues[ m_iActiveIssueIndex ];
  769. // for record-keeping
  770. if ( pActiveIssue->IsYesNoVote( ) )
  771. {
  772. pActiveIssue->SetYesNoVoteCount( m_nVoteOptionCount[ VOTE_OPTION1 ], m_nVoteOptionCount[ VOTE_OPTION2 ], m_nPotentialVotes );
  773. }
  774. SendVoteFailedMessage( pActiveIssue->MakeVoteFailErrorCodeForClients( VOTE_FAILED_QUORUM_FAILURE ) );
  775. pActiveIssue->OnVoteFailed( );
  776. m_resetVoteTimer.Start( 5.0 );
  777. m_acceptingVotesTimer.Invalidate( );
  778. }
  779. //-----------------------------------------------------------------------------
  780. // Purpose: BaseIssue
  781. //-----------------------------------------------------------------------------
  782. CBaseIssue::CBaseIssue( const char *pszTypeString, CVoteController *pVoteController )
  783. {
  784. V_strcpy_safe( m_szTypeString, pszTypeString );
  785. m_iNumYesVotes = 0;
  786. m_iNumNoVotes = 0;
  787. m_iNumPotentialVotes = 0;
  788. m_pVoteController = pVoteController;
  789. ASSERT( pVoteController );
  790. if ( pVoteController )
  791. {
  792. pVoteController->RegisterIssue( this );
  793. }
  794. }
  795. //-----------------------------------------------------------------------------
  796. // Purpose:
  797. //-----------------------------------------------------------------------------
  798. CBaseIssue::~CBaseIssue()
  799. {
  800. for ( int index = 0; index < m_FailedVotes.Count(); index++ )
  801. {
  802. FailedVote *pFailedVote = m_FailedVotes[index];
  803. delete pFailedVote;
  804. }
  805. }
  806. //-----------------------------------------------------------------------------
  807. // Purpose:
  808. //-----------------------------------------------------------------------------
  809. const char *CBaseIssue::GetTypeString( void )
  810. {
  811. return m_szTypeString;
  812. }
  813. //-----------------------------------------------------------------------------
  814. // Purpose:
  815. //-----------------------------------------------------------------------------
  816. const char *CBaseIssue::GetDetailsString( void )
  817. {
  818. return m_szDetailsString;
  819. }
  820. //-----------------------------------------------------------------------------
  821. // Purpose:
  822. //-----------------------------------------------------------------------------
  823. void CBaseIssue::SetIssueDetails( const char *pszDetails )
  824. {
  825. V_strcpy_safe( m_szDetailsString, pszDetails );
  826. }
  827. //-----------------------------------------------------------------------------
  828. // Purpose:
  829. //-----------------------------------------------------------------------------
  830. bool CBaseIssue::IsAllyRestrictedVote( void )
  831. {
  832. return false;
  833. }
  834. int CBaseIssue::GetVotesRequiredToPass( void )
  835. {
  836. // TODO: to reduce risk of new bugs, this logic was preserved as-is from VoteControllerThink. But it can/should be cleaned up for legibility.
  837. int nPotentialVoters = CountPotentialVoters();
  838. // BUGBUG: disconnecting/reconnecting players during the vote can affect the final tally, so we will use the larger number here:
  839. if ( m_pVoteController && ( m_pVoteController->GetPotentialVotes( ) > nPotentialVoters ) )
  840. nPotentialVoters = m_pVoteController->GetPotentialVotes( );
  841. int nVotesToSucceed = 0;
  842. if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking()
  843. && CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() && !IsAllyRestrictedVote() )
  844. { // in queued matchmaking must have 100% voting to succeed (unless kick vote)
  845. nVotesToSucceed = CSGameRules()->m_numQueuedMatchmakingAccounts;
  846. }
  847. // Unanimous votes require all attending humans (which might be less than 10 players)
  848. else if ( IsUnanimousVoteToPass() )
  849. {
  850. nVotesToSucceed = MAX( 1, nPotentialVoters );
  851. }
  852. else
  853. {
  854. float flnVotesToSucceed = ( CSGameRules() && CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() ) ? MAX( 1, nPotentialVoters - 1 ) : ( ( float )nPotentialVoters * sv_vote_quorum_ratio.GetFloat() );
  855. nVotesToSucceed = ceil( flnVotesToSucceed );
  856. }
  857. return nVotesToSucceed;
  858. }
  859. //-----------------------------------------------------------------------------
  860. // Purpose:
  861. //-----------------------------------------------------------------------------
  862. const char *CBaseIssue::GetVotePassedString( void )
  863. {
  864. return "Unknown vote passed.";
  865. }
  866. float CBaseIssue::GetFailedVoteLockOutTime( void )
  867. {
  868. return sv_vote_failure_timer.GetFloat();
  869. }
  870. //-----------------------------------------------------------------------------
  871. // Purpose: Store failures to prevent vote spam
  872. //-----------------------------------------------------------------------------
  873. void CBaseIssue::OnVoteFailed( void )
  874. {
  875. // Check for an existing match
  876. for ( int index = 0; index < m_FailedVotes.Count(); index++ )
  877. {
  878. FailedVote *pFailedVote = m_FailedVotes[index];
  879. if ( Q_strncmp( pFailedVote->szFailedVoteParameter, GetDetailsString(), Q_ARRAYSIZE( pFailedVote->szFailedVoteParameter ) - 1 ) == 0 )
  880. {
  881. pFailedVote->flLockoutTime = gpGlobals->curtime + GetFailedVoteLockOutTime();
  882. return;
  883. }
  884. }
  885. // Need to create a new one
  886. FailedVote *pNewFailedVote = new FailedVote;
  887. int iIndex = m_FailedVotes.AddToTail( pNewFailedVote );
  888. V_strcpy_safe( m_FailedVotes[iIndex]->szFailedVoteCommand, GetTypeString() );
  889. V_strcpy_safe( m_FailedVotes[iIndex]->szFailedVoteParameter, GetDetailsString() );
  890. m_FailedVotes[iIndex]->flLockoutTime = gpGlobals->curtime + GetFailedVoteLockOutTime();
  891. }
  892. //-----------------------------------------------------------------------------
  893. // Purpose:
  894. //-----------------------------------------------------------------------------
  895. bool CBaseIssue::CanTeamCallVote( int iTeam ) const
  896. {
  897. return true;
  898. }
  899. //-----------------------------------------------------------------------------
  900. // Purpose:
  901. //-----------------------------------------------------------------------------
  902. bool CBaseIssue::CanCallVote( int iEntIndex, const char *pszCommand, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime )
  903. {
  904. // Automated server vote - don't bother testing against it
  905. if ( iEntIndex == 99 )
  906. return true;
  907. // Bogus player
  908. if( iEntIndex == -1 )
  909. return false;
  910. #ifdef TF_DLL
  911. if ( TFGameRules() && TFGameRules()->IsInWaitingForPlayers() && !TFGameRules()->IsInTournamentMode() )
  912. {
  913. nFailCode = VOTE_FAILED_WAITINGFORPLAYERS;
  914. return false;
  915. }
  916. #endif // TF_DLL
  917. if ( !sv_vote_allow_in_warmup.GetBool() && CSGameRules() && CSGameRules()->IsWarmupPeriod() && !IsEnabledDuringWarmup() )
  918. {
  919. nFailCode = VOTE_FAILED_WAITINGFORPLAYERS;
  920. return false;
  921. }
  922. CBaseEntity *pVoteCaller = UTIL_EntityByIndex( iEntIndex );
  923. if( pVoteCaller && !CanTeamCallVote( GetVoterTeam( pVoteCaller ) ) )
  924. {
  925. nFailCode = VOTE_FAILED_TEAM_CANT_CALL;
  926. return false;
  927. }
  928. if ( IsVoteCallExclusiveToSpectators( ) )
  929. {
  930. CBasePlayer *pVoteCallerPlayer = ToBasePlayer( pVoteCaller );
  931. if ( !pVoteCallerPlayer || !pVoteCallerPlayer->IsSpectator( ) )
  932. {
  933. nFailCode = VOTE_FAILED_TEAM_CANT_CALL;
  934. return false;
  935. }
  936. }
  937. // Only few votes are actually allowed in queue matchmaking mode
  938. if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking() && !IsEnabledInQueuedMatchmaking() )
  939. {
  940. nFailCode = VOTE_FAILED_ISSUE_DISABLED;
  941. return false;
  942. }
  943. // Disable all voting after rematch vote is initiated at the end of the match
  944. if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking() && ( CSGameRules()->m_eQueuedMatchmakingRematchState >= CSGameRules()->k_EQueuedMatchmakingRematchState_VoteToRematchInProgress ) )
  945. {
  946. nFailCode = VOTE_FAILED_ISSUE_DISABLED;
  947. return false;
  948. }
  949. // don't let kick votes happen on match point or last round
  950. if ( CSGameRules() && (CSGameRules()->IsQueuedMatchmaking() || sv_vote_disallow_kick_on_match_point.GetBool()) &&
  951. CSGameRules()->IsLastRoundOfMatch() && CSGameRules()->IsMatchPoint() && FStrEq( VOTEISSUE_NAME_KICK, pszCommand ) )
  952. {
  953. nFailCode = VOTE_FAILED_ISSUE_DISABLED;
  954. return false;
  955. }
  956. // Did this fail recently?
  957. for( int iIndex = 0; iIndex < m_FailedVotes.Count(); iIndex++ )
  958. {
  959. FailedVote *pCurrentFailure = m_FailedVotes[iIndex];
  960. int nTimeRemaining = pCurrentFailure->flLockoutTime - gpGlobals->curtime;
  961. bool bFailed = false;
  962. if ( nTimeRemaining > 1 )
  963. {
  964. if ( Q_strlen( pCurrentFailure->szFailedVoteCommand ) > 0 && FStrEq( pCurrentFailure->szFailedVoteCommand, pszCommand ) )
  965. {
  966. // If this issue requires a parameter, see if we're voting for the same one again (i.e. changelevel ctf_2fort)
  967. if ( Q_strlen( pCurrentFailure->szFailedVoteParameter ) > 0 && FStrEq( pCurrentFailure->szFailedVoteParameter, pszDetails ) )
  968. {
  969. bFailed = true;
  970. if ( FStrEq( VOTEISSUE_NAME_CHANGELEVEL, pszCommand ) )
  971. {
  972. nFailCode = VOTE_FAILED_FAILED_RECENT_CHANGEMAP;
  973. }
  974. else if ( FStrEq( VOTEISSUE_NAME_KICK, pszCommand ) )
  975. {
  976. nFailCode = VOTE_FAILED_FAILED_RECENT_KICK;
  977. }
  978. else
  979. {
  980. nFailCode = VOTE_FAILED_FAILED_RECENTLY;
  981. }
  982. }
  983. else
  984. {
  985. if ( FStrEq( VOTEISSUE_NAME_SWAPTEAMS, pszCommand ) )
  986. {
  987. nFailCode = VOTE_FAILED_FAILED_RECENT_SWAPTEAMS;
  988. bFailed = true;
  989. }
  990. else if ( FStrEq( VOTEISSUE_NAME_SCRAMBLE, pszCommand ) )
  991. {
  992. nFailCode = VOTE_FAILED_FAILED_RECENT_SCRAMBLETEAMS;
  993. bFailed = true;
  994. }
  995. else if ( FStrEq( VOTEISSUE_NAME_RESTARTGAME, pszCommand ) )
  996. {
  997. nFailCode = VOTE_FAILED_FAILED_RECENT_RESTART;
  998. bFailed = true;
  999. }
  1000. }
  1001. }
  1002. // Otherwise we have a parameter-less vote, so just check the lockout timer (i.e. restartgame)
  1003. else
  1004. {
  1005. bFailed = true;
  1006. nFailCode = VOTE_FAILED_FAILED_RECENTLY;
  1007. }
  1008. }
  1009. if ( bFailed )
  1010. {
  1011. nTime = nTimeRemaining;
  1012. return false;
  1013. }
  1014. }
  1015. return true;
  1016. }
  1017. //-----------------------------------------------------------------------------
  1018. // Purpose:
  1019. //-----------------------------------------------------------------------------
  1020. int CBaseIssue::CountPotentialVoters( void )
  1021. {
  1022. int nTotalPlayers = 0;
  1023. for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; ++playerIndex )
  1024. {
  1025. CBasePlayer *pPlayer = UTIL_PlayerByIndex( playerIndex );
  1026. if ( m_pVoteController && m_pVoteController->IsValidVoter( pPlayer ) )
  1027. {
  1028. if ( m_pVoteController->CanTeamCastVote( GetVoterTeam( pPlayer ) ) )
  1029. {
  1030. nTotalPlayers++;
  1031. }
  1032. }
  1033. }
  1034. return nTotalPlayers;
  1035. }
  1036. //-----------------------------------------------------------------------------
  1037. // Purpose:
  1038. //-----------------------------------------------------------------------------
  1039. int CBaseIssue::GetNumberVoteOptions( void )
  1040. {
  1041. return 2; // The default issue is Yes/No (so 2), but it can be anywhere between 1 and MAX_VOTE_COUNT
  1042. }
  1043. //-----------------------------------------------------------------------------
  1044. // Purpose:
  1045. //-----------------------------------------------------------------------------
  1046. bool CBaseIssue::IsYesNoVote( void )
  1047. {
  1048. return true; // Default
  1049. }
  1050. //-----------------------------------------------------------------------------
  1051. // Purpose:
  1052. //-----------------------------------------------------------------------------
  1053. void CBaseIssue::SetYesNoVoteCount( int iNumYesVotes, int iNumNoVotes, int iNumPotentialVotes )
  1054. {
  1055. m_iNumYesVotes = iNumYesVotes;
  1056. m_iNumNoVotes = iNumNoVotes;
  1057. m_iNumPotentialVotes = iNumPotentialVotes;
  1058. }
  1059. //-----------------------------------------------------------------------------
  1060. // Purpose:
  1061. //-----------------------------------------------------------------------------
  1062. void CBaseIssue::ListStandardNoArgCommand( CBasePlayer *forWhom, const char *issueString )
  1063. {
  1064. char szBuffer[MAX_COMMAND_LENGTH];
  1065. Q_snprintf( szBuffer, MAX_COMMAND_LENGTH, "callvote %s\n", issueString );
  1066. ClientPrint( forWhom, HUD_PRINTCONSOLE, szBuffer );
  1067. }
  1068. //-----------------------------------------------------------------------------
  1069. // Purpose:
  1070. //-----------------------------------------------------------------------------
  1071. bool CBaseIssue::GetVoteOptions( CUtlVector <const char*> &vecNames )
  1072. {
  1073. // The default vote issue is a Yes/No vote
  1074. vecNames.AddToHead( "Yes" );
  1075. vecNames.AddToTail( "No" );
  1076. return true;
  1077. }
  1078. float CBaseIssue::GetCommandDelay( void )
  1079. {
  1080. return sv_vote_command_delay.GetFloat();
  1081. }