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
1290 lines
40 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Base VoteController. Handles holding and voting on issues.
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "cbase.h"
|
|
#include "vote_controller.h"
|
|
#include "shareddefs.h"
|
|
#include "eiface.h"
|
|
#include "team.h"
|
|
#include "gameinterface.h"
|
|
#include "cs_gamerules.h"
|
|
#include "usermessages.h"
|
|
|
|
#ifdef TF_DLL
|
|
#include "tf/tf_gamerules.h"
|
|
#endif
|
|
|
|
#include "EventLog.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// Datatable
|
|
IMPLEMENT_SERVERCLASS_ST( CVoteController, DT_VoteController )
|
|
SendPropInt( SENDINFO( m_iActiveIssueIndex ) ),
|
|
SendPropInt( SENDINFO( m_iOnlyTeamToVote ) ),
|
|
SendPropArray3( SENDINFO_ARRAY3( m_nVoteOptionCount ), SendPropInt( SENDINFO_ARRAY( m_nVoteOptionCount ), 8, SPROP_UNSIGNED ) ),
|
|
SendPropInt( SENDINFO( m_nPotentialVotes ) ),
|
|
SendPropBool( SENDINFO( m_bIsYesNoVote ) )
|
|
END_SEND_TABLE()
|
|
|
|
BEGIN_DATADESC( CVoteController )
|
|
DEFINE_THINKFUNC( VoteControllerThink ),
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( vote_controller, CVoteController );
|
|
|
|
CVoteController *g_voteControllerGlobal = NULL;
|
|
CVoteController *g_voteControllerCT = NULL;
|
|
CVoteController *g_voteControllerT = NULL;
|
|
|
|
ConVar sv_vote_timer_duration("sv_vote_timer_duration", "15", FCVAR_RELEASE, "How long to allow voting on an issue");
|
|
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);
|
|
ConVar sv_allow_votes("sv_allow_votes", "1", FCVAR_RELEASE, "Allow voting?");
|
|
ConVar sv_vote_failure_timer("sv_vote_failure_timer", "300", FCVAR_RELEASE, "A vote that fails cannot be re-submitted for this long");
|
|
ConVar sv_vote_creation_timer("sv_vote_creation_timer", "120", FCVAR_RELEASE, "How often someone can individually call a vote.");
|
|
// 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
|
|
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 );
|
|
ConVar sv_vote_allow_spectators( "sv_vote_allow_spectators", "0", FCVAR_RELEASE, "Allow spectators to initiate votes?" );
|
|
ConVar sv_vote_count_spectator_votes( "sv_vote_count_spectator_votes", "0", FCVAR_RELEASE, "Allow spectators to vote on issues?" );
|
|
ConVar sv_vote_allow_in_warmup( "sv_vote_allow_in_warmup", "0", FCVAR_RELEASE, "Allow voting during warmup?" );
|
|
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." );
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CommandListIssues( void )
|
|
{
|
|
CBasePlayer *commandIssuer = UTIL_GetCommandClient() ;
|
|
|
|
if ( !commandIssuer )
|
|
return;
|
|
|
|
// list team-specific issues
|
|
if ( commandIssuer->GetTeamVoteController() )
|
|
{
|
|
commandIssuer->GetTeamVoteController()->ListIssues( commandIssuer );
|
|
}
|
|
|
|
// and always list global issues
|
|
if ( g_voteControllerGlobal )
|
|
{
|
|
g_voteControllerGlobal->ListIssues( commandIssuer );
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
ConCommand ListIssues("listissues", CommandListIssues, "List all the issues that can be voted on.", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: This should eventually ask the player what team they are voting on
|
|
// to take into account different idle / spectator rules.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int GetVoterTeam( CBaseEntity *pEntity )
|
|
{
|
|
if ( !pEntity )
|
|
return TEAM_UNASSIGNED;
|
|
|
|
CBasePlayer *pPlayer = ToBasePlayer( pEntity );
|
|
if ( !pPlayer )
|
|
return TEAM_UNASSIGNED;
|
|
|
|
int iTeam = pPlayer->GetAssociatedTeamNumber( );
|
|
|
|
return iTeam;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CON_COMMAND_F( callvote, "Start a vote on an issue.", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS )
|
|
{
|
|
if ( !g_voteControllerGlobal || !g_voteControllerCT || !g_voteControllerT )
|
|
{
|
|
DevMsg( "Vote Controllers Not Found!\n" );
|
|
return;
|
|
}
|
|
|
|
CBasePlayer *pVoteCaller = UTIL_GetCommandClient();
|
|
int iEntindex = 99;
|
|
if ( pVoteCaller )
|
|
iEntindex = pVoteCaller->entindex();
|
|
|
|
if ( !sv_vote_allow_spectators.GetBool() && pVoteCaller && pVoteCaller->IsSpectator() )
|
|
{
|
|
g_voteControllerGlobal->SendVoteFailedMessage( VOTE_FAILED_SPECTATOR, pVoteCaller );
|
|
return;
|
|
|
|
}
|
|
|
|
// Parameters
|
|
char szEmptyDetails[ MAX_VOTE_DETAILS_LENGTH ];
|
|
szEmptyDetails[ 0 ] = '\0';
|
|
const char *arg2 = args[ 1 ];
|
|
const char *arg3 = args.ArgC() >= 3 ? args[ 2 ] : szEmptyDetails;
|
|
|
|
|
|
CVoteController *pVoteController;
|
|
if ( pVoteCaller )
|
|
{
|
|
pVoteController = pVoteCaller->GetTeamVoteController();
|
|
}
|
|
else
|
|
{
|
|
pVoteController = g_voteControllerGlobal;
|
|
}
|
|
|
|
// If we don't have any arguments, invoke VoteSetup UI
|
|
if( args.ArgC() < 2 )
|
|
{
|
|
pVoteController->SetupVote( iEntindex );
|
|
|
|
return;
|
|
}
|
|
|
|
if ( g_voteControllerGlobal->HasIssue( arg2 ) )
|
|
{
|
|
g_voteControllerGlobal->CreateVote( iEntindex, arg2, arg3 );
|
|
}
|
|
else if ( pVoteController->HasIssue( arg2 ) )
|
|
{
|
|
pVoteController->CreateVote( iEntindex, arg2, arg3 );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "Vote Issue Not Found!\n" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CVoteController::~CVoteController()
|
|
{
|
|
if ( g_voteControllerGlobal == this ) { g_voteControllerGlobal = NULL; }
|
|
else if ( g_voteControllerCT == this ) { g_voteControllerCT = NULL; }
|
|
else if ( g_voteControllerT == this ) { g_voteControllerT = NULL; }
|
|
|
|
|
|
for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
|
|
{
|
|
delete m_potentialIssues[issueIndex];
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::ResetData( void )
|
|
{
|
|
m_iActiveIssueIndex = INVALID_ISSUE;
|
|
|
|
for ( int index = 0; index < m_nVoteOptionCount.Count(); index++ )
|
|
{
|
|
m_nVoteOptionCount.Set( index, 0 );
|
|
}
|
|
|
|
m_nPotentialVotes = 0;
|
|
m_acceptingVotesTimer.Invalidate();
|
|
m_executeCommandTimer.Invalidate();
|
|
m_iEntityHoldingVote = -1;
|
|
m_iOnlyTeamToVote = TEAM_INVALID;
|
|
m_bIsYesNoVote = true;
|
|
|
|
for( int voteIndex = 0; voteIndex < MAX_PLAYERS; ++voteIndex )
|
|
{
|
|
m_nVotesCast[voteIndex] = VOTE_UNCAST;
|
|
}
|
|
m_arrVotedUsers.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::Spawn( void )
|
|
{
|
|
ResetData();
|
|
|
|
BaseClass::Spawn();
|
|
|
|
SetThink( &CVoteController::VoteControllerThink );
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CVoteController::UpdateTransmitState( void )
|
|
{
|
|
// ALWAYS transmit to all clients.
|
|
return SetTransmitState( FL_EDICT_ALWAYS );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CVoteController::CanTeamCastVote( int iTeam ) const
|
|
{
|
|
if ( m_iOnlyTeamToVote == TEAM_INVALID )
|
|
return true;
|
|
|
|
return iTeam == m_iOnlyTeamToVote;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Handles menu-driven setup of Voting
|
|
//-----------------------------------------------------------------------------
|
|
bool CVoteController::SetupVote( int iEntIndex )
|
|
{
|
|
CBasePlayer *pVoteCaller = UTIL_PlayerByIndex( iEntIndex );
|
|
if( !pVoteCaller )
|
|
return false;
|
|
|
|
bool bAllowVotes = sv_allow_votes.GetBool();
|
|
if ( bAllowVotes && m_potentialIssues.Count() )
|
|
{
|
|
CSingleUserRecipientFilter filter( pVoteCaller );
|
|
filter.MakeReliable();
|
|
|
|
CCSUsrMsg_VoteSetup msg;
|
|
|
|
for( int iIndex = 0; iIndex < m_potentialIssues.Count(); ++iIndex )
|
|
{
|
|
CBaseIssue *pCurrentIssue = m_potentialIssues[iIndex];
|
|
|
|
if ( pCurrentIssue )
|
|
{
|
|
if ( pCurrentIssue->IsEnabled() )
|
|
{
|
|
msg.add_potential_issues( pCurrentIssue->GetTypeString() );
|
|
}
|
|
else
|
|
{
|
|
char szDisabledIssueStr[MAX_COMMAND_LENGTH + 12];
|
|
V_strcpy( szDisabledIssueStr, pCurrentIssue->GetTypeString() );
|
|
V_strcat( szDisabledIssueStr, " (Disabled on Server)", sizeof(szDisabledIssueStr) );
|
|
|
|
msg.add_potential_issues( szDisabledIssueStr );
|
|
}
|
|
}
|
|
}
|
|
|
|
SendUserMessage( filter, CS_UM_VoteSetup, msg );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Handles console-driven setup of Voting
|
|
//-----------------------------------------------------------------------------
|
|
bool CVoteController::CreateVote( int iEntIndex, const char *pszTypeString, const char *pszDetailString )
|
|
{
|
|
// Terrible Hack: Dedicated servers pass 99 as the EntIndex
|
|
bool bDedicatedServer = ( iEntIndex == 99 ) ? true : false;
|
|
|
|
CBasePlayer *pVoteCaller = UTIL_PlayerByIndex( iEntIndex );
|
|
|
|
if ( !pVoteCaller && !bDedicatedServer )
|
|
return false;
|
|
|
|
if( !sv_allow_votes.GetBool() )
|
|
{
|
|
SendVoteFailedMessage( VOTE_FAILED_DISABLED, pVoteCaller );
|
|
|
|
return false;
|
|
}
|
|
|
|
// Already running a vote?
|
|
if ( IsAVoteInProgress() )
|
|
{
|
|
// send a message to the user to who tried to vote that their vote failed and why
|
|
ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#SFUI_vote_failed_vote_in_progress" );
|
|
return false;
|
|
}
|
|
|
|
// Find the issue the user is asking for
|
|
for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
|
|
{
|
|
CBaseIssue *pCurrentIssue = m_potentialIssues[issueIndex];
|
|
if ( !pCurrentIssue )
|
|
return false;
|
|
|
|
if( FStrEq( pszTypeString, pCurrentIssue->GetTypeString() ) )
|
|
{
|
|
vote_create_failed_t nErrorCode = VOTE_FAILED_GENERIC;
|
|
int nTime = 0;
|
|
|
|
if( pCurrentIssue->CanCallVote( iEntIndex, pszTypeString, pszDetailString, nErrorCode, nTime ) )
|
|
{
|
|
// Prevent spamming commands
|
|
#ifndef _DEBUG
|
|
if ( pVoteCaller && !pCurrentIssue->ShouldIgnoreCreationTimer() )
|
|
{
|
|
int nTimeLeft = sv_vote_creation_timer.GetFloat() - pVoteCaller->GetLastHeldVoteTimer().GetElapsedTime();
|
|
if( pVoteCaller->GetLastHeldVoteTimer().HasStarted() && nTimeLeft > 1 )
|
|
{
|
|
SendVoteFailedMessage( VOTE_FAILED_RATE_EXCEEDED, pVoteCaller, nTimeLeft );
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// if this is not an instant voting issue ( i.e. tournament pause match )
|
|
|
|
if (pCurrentIssue->GetVotesRequiredToPass() > 1 )
|
|
{
|
|
// can't call it if there's a global vote in progress.
|
|
if ( g_voteControllerGlobal && g_voteControllerGlobal->IsAVoteInProgress() )
|
|
{
|
|
// send a message to the user to who tried to vote that their vote failed and why
|
|
ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#SFUI_vote_failed_vote_in_progress" );
|
|
return false;
|
|
}
|
|
|
|
|
|
// can't call it if this is a global vote and the other team is mid-vote.
|
|
|
|
CVoteController * pOtherTeamVoteController = g_voteControllerGlobal;
|
|
|
|
if ( pVoteCaller )
|
|
{
|
|
switch ( pVoteCaller->GetAssociatedTeamNumber() )
|
|
{
|
|
case TEAM_CT:
|
|
pOtherTeamVoteController = g_voteControllerT;
|
|
break;
|
|
|
|
case TEAM_TERRORIST:
|
|
pOtherTeamVoteController = g_voteControllerCT;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( ( this == g_voteControllerGlobal ) && ( pOtherTeamVoteController && pOtherTeamVoteController->IsAVoteInProgress() ) )
|
|
{
|
|
// send a message to the user to who tried to vote that their vote failed and why
|
|
ClientPrint( pVoteCaller, HUD_PRINTCENTER, "#SFUI_vote_failed_vote_in_progress" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Establish a bunch of data on this particular issue
|
|
m_iEntityHoldingVote = iEntIndex;
|
|
pCurrentIssue->SetIssueDetails( pszDetailString );
|
|
m_bIsYesNoVote = pCurrentIssue->IsYesNoVote();
|
|
m_iActiveIssueIndex = issueIndex;
|
|
|
|
m_iOnlyTeamToVote = TEAM_INVALID;
|
|
if ( !bDedicatedServer && pCurrentIssue->IsAllyRestrictedVote() )
|
|
m_iOnlyTeamToVote = GetVoterTeam( pVoteCaller );
|
|
|
|
// Now get our choices
|
|
m_VoteOptions.RemoveAll();
|
|
pCurrentIssue->GetVoteOptions( m_VoteOptions );
|
|
int nNumVoteOptions = m_VoteOptions.Count();
|
|
if ( nNumVoteOptions >= 2 )
|
|
{
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "vote_options" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "count", nNumVoteOptions );
|
|
for ( int iIndex = 0; iIndex < nNumVoteOptions; iIndex++ )
|
|
{
|
|
char szNumber[2];
|
|
Q_snprintf( szNumber, sizeof( szNumber ), "%i", iIndex + 1 );
|
|
|
|
char szOptionName[8] = "option";
|
|
Q_strncat( szOptionName, szNumber, sizeof( szOptionName ), COPY_ALL_CHARACTERS );
|
|
|
|
event->SetString( szOptionName, m_VoteOptions[iIndex] );
|
|
}
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( nNumVoteOptions >= 2 );
|
|
}
|
|
|
|
// Get the data out to the client
|
|
for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; ++playerIndex )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( playerIndex );
|
|
|
|
if ( pPlayer && pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
|
|
{
|
|
CSingleUserRecipientFilter filter( pPlayer );
|
|
filter.MakeReliable();
|
|
|
|
CCSUsrMsg_VoteStart msg;
|
|
msg.set_team( m_iOnlyTeamToVote ); // move into the filter
|
|
msg.set_ent_idx( m_iEntityHoldingVote );
|
|
msg.set_vote_type( pCurrentIssue->GetVoteIssue() );
|
|
msg.set_disp_str( pCurrentIssue->GetDisplayString() );
|
|
msg.set_details_str( pCurrentIssue->GetDetailsString() );
|
|
msg.set_other_team_str( pCurrentIssue->GetOtherTeamDisplayString() );
|
|
msg.set_is_yes_no_vote( m_bIsYesNoVote );
|
|
SendUserMessage( filter, CS_UM_VoteStart, msg );
|
|
}
|
|
}
|
|
|
|
UTIL_LogPrintf( "Vote started \"%s %s\" from #%u %s\n",
|
|
pCurrentIssue->GetTypeString(),
|
|
pCurrentIssue->GetDetailsString(),
|
|
m_iEntityHoldingVote,
|
|
pVoteCaller ? GameLogSystem()->FormatPlayer( pVoteCaller ) : "n/a" );
|
|
|
|
m_nPotentialVotes = pCurrentIssue->CountPotentialVoters();
|
|
// FIX TO MAKE REMATCH ONLY
|
|
float flVoteDuration = CSGameRules()->IsQueuedMatchmaking() ? 45.0f : sv_vote_timer_duration.GetFloat();
|
|
m_acceptingVotesTimer.Start( flVoteDuration );
|
|
pCurrentIssue->OnVoteStarted();
|
|
|
|
// Force the vote holder to agree with a Yes/No vote
|
|
if ( m_bIsYesNoVote && !bDedicatedServer )
|
|
{
|
|
TryCastVote( iEntIndex, "Option1" );
|
|
}
|
|
|
|
if ( !bDedicatedServer )
|
|
{
|
|
pVoteCaller->GetLastHeldVoteTimer().Start();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( !bDedicatedServer )
|
|
{
|
|
SendVoteFailedMessage( pCurrentIssue->MakeVoteFailErrorCodeForClients( nErrorCode ), pVoteCaller, nTime );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sent to everyone, unless we pass a player pointer
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::SendVoteFailedMessage( vote_create_failed_t nReason, CBasePlayer *pVoteCaller, int nTime )
|
|
{
|
|
// driller: need to merge all failure case stuff into a single path
|
|
if ( pVoteCaller )
|
|
{
|
|
CSingleUserRecipientFilter user( pVoteCaller );
|
|
user.MakeReliable();
|
|
|
|
CCSUsrMsg_CallVoteFailed msg;
|
|
msg.set_reason( nReason );
|
|
msg.set_time( nTime );
|
|
SendUserMessage( user, CS_UM_CallVoteFailed, msg );
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("Vote failed \"%s %s\" \n",
|
|
m_potentialIssues[m_iActiveIssueIndex]->GetTypeString(),
|
|
m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString() );
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
|
|
CCSUsrMsg_VoteFailed msg;
|
|
msg.set_team( m_iOnlyTeamToVote );
|
|
msg.set_reason( nReason );
|
|
SendUserMessage( filter, CS_UM_VoteFailed, msg );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Player generated a vote command. i.e. /vote option1
|
|
//-----------------------------------------------------------------------------
|
|
CVoteController::TryCastVoteResult CVoteController::TryCastVote( int iEntIndex, const char *pszVoteString )
|
|
{
|
|
if( !sv_allow_votes.GetBool() )
|
|
return CAST_FAIL_SERVER_DISABLE;
|
|
|
|
if( iEntIndex > MAX_PLAYERS )
|
|
return CAST_FAIL_SYSTEM_ERROR;
|
|
|
|
if( m_iActiveIssueIndex == INVALID_ISSUE )
|
|
return CAST_FAIL_NO_ACTIVE_ISSUE;
|
|
|
|
if( m_executeCommandTimer.HasStarted() )
|
|
return CAST_FAIL_VOTE_CLOSED;
|
|
|
|
CBaseEntity *pVoter = UTIL_EntityByIndex( iEntIndex );
|
|
if ( !IsValidVoter( ToBasePlayer( pVoter ) ) )
|
|
return CAST_FAIL_TEAM_RESTRICTED;
|
|
|
|
if( m_potentialIssues[m_iActiveIssueIndex] && m_potentialIssues[m_iActiveIssueIndex]->IsAllyRestrictedVote() )
|
|
{
|
|
CBaseEntity *pVoteHolder = UTIL_EntityByIndex( m_iEntityHoldingVote );
|
|
|
|
if( ( pVoteHolder == NULL ) || ( pVoter == NULL ) || ( GetVoterTeam( pVoteHolder ) != GetVoterTeam( pVoter ) ) )
|
|
{
|
|
return CAST_FAIL_TEAM_RESTRICTED;
|
|
}
|
|
}
|
|
|
|
// Look for a previous vote
|
|
int nOldVote = VOTE_UNCAST;
|
|
if ( iEntIndex < MAX_PLAYERS )
|
|
{
|
|
nOldVote = m_nVotesCast[iEntIndex];
|
|
}
|
|
#ifndef DEBUG
|
|
if( nOldVote != VOTE_UNCAST )
|
|
{
|
|
return CAST_FAIL_NO_CHANGES;
|
|
}
|
|
#endif // !DEBUG
|
|
|
|
// Which option are they voting for?
|
|
int nCurrentVote = VOTE_UNCAST;
|
|
if ( !StringHasPrefix( pszVoteString, "Option" ) )
|
|
return CAST_FAIL_SYSTEM_ERROR;
|
|
|
|
nCurrentVote = (CastVote)( atoi( pszVoteString + V_strlen( "Option" ) ) - 1 );
|
|
|
|
if ( nCurrentVote < VOTE_OPTION1 || nCurrentVote > VOTE_OPTION5 )
|
|
return CAST_FAIL_SYSTEM_ERROR;
|
|
|
|
// They're changing their vote
|
|
#ifdef DEBUG
|
|
if ( nOldVote != VOTE_UNCAST )
|
|
{
|
|
if( nOldVote == nCurrentVote )
|
|
{
|
|
return CAST_FAIL_DUPLICATE;
|
|
}
|
|
VoteChoice_Decrement( nOldVote );
|
|
}
|
|
#endif // DEBUG
|
|
|
|
// With a Yes/No vote, slam anything past "No" to No
|
|
if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
|
|
{
|
|
if ( nCurrentVote > VOTE_OPTION2 )
|
|
nCurrentVote = VOTE_OPTION2;
|
|
}
|
|
|
|
#ifndef DEBUG
|
|
if ( CBasePlayer *pBasePlayerVoter = ToBasePlayer( pVoter ) )
|
|
{
|
|
if ( uint64 uiSteamID = pBasePlayerVoter->GetSteamIDAsUInt64() )
|
|
{
|
|
if ( m_arrVotedUsers.Find( uiSteamID ) == m_arrVotedUsers.InvalidIndex() )
|
|
{
|
|
m_arrVotedUsers.AddToTail( uiSteamID ); // remember that this user already voted
|
|
}
|
|
else
|
|
{
|
|
return CAST_FAIL_NO_CHANGES;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Register and track this vote
|
|
VoteChoice_Increment( nCurrentVote );
|
|
m_nVotesCast[iEntIndex] = nCurrentVote;
|
|
|
|
// Tell the client-side UI
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "vote_cast" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "vote_option", nCurrentVote );
|
|
event->SetInt( "team", m_iOnlyTeamToVote );
|
|
event->SetInt( "entityid", iEntIndex );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
UTIL_LogPrintf( "Vote cast \"%s %s\" from #%u %s option%d\n",
|
|
m_potentialIssues[m_iActiveIssueIndex]->GetTypeString(),
|
|
m_potentialIssues[m_iActiveIssueIndex]->GetDetailsString(),
|
|
iEntIndex,
|
|
pVoter ? GameLogSystem()->FormatPlayer( pVoter ) : "n/a",
|
|
nCurrentVote );
|
|
|
|
CheckForEarlyVoteClose();
|
|
|
|
return CAST_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Increments the vote count for a particular vote option
|
|
// i.e. nVoteChoice = 0 might mean a Yes vote
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::VoteChoice_Increment( int nVoteChoice )
|
|
{
|
|
if ( nVoteChoice < VOTE_OPTION1 || nVoteChoice > VOTE_OPTION5 )
|
|
return;
|
|
|
|
int nValue = m_nVoteOptionCount.Get( nVoteChoice );
|
|
m_nVoteOptionCount.Set( nVoteChoice, ++nValue );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::VoteChoice_Decrement( int nVoteChoice )
|
|
{
|
|
if ( nVoteChoice < VOTE_OPTION1 || nVoteChoice > VOTE_OPTION5 )
|
|
return;
|
|
|
|
int nValue = m_nVoteOptionCount.Get( nVoteChoice );
|
|
m_nVoteOptionCount.Set( nVoteChoice, --nValue );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::VoteControllerThink( void )
|
|
{
|
|
if ( !m_potentialIssues.IsValidIndex( m_iActiveIssueIndex ) )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.5f );
|
|
|
|
return;
|
|
}
|
|
|
|
CBaseIssue *pActiveIssue = m_potentialIssues[m_iActiveIssueIndex];
|
|
|
|
int nWinningVoteOption = GetWinningVoteOption();
|
|
if ( !pActiveIssue->IsYesNoVote() || nWinningVoteOption >= m_VoteOptions.Count() )
|
|
{
|
|
Assert( nWinningVoteOption >= 0 && nWinningVoteOption < m_VoteOptions.Count() );
|
|
Msg( "Trying to resolve a vote that is not a YES/NO vote, YES/NO votes aren't currently supported! \n");
|
|
return;
|
|
}
|
|
|
|
bool bVoteHasFinished = false;
|
|
bool bVoteShouldBailOnNos = (pActiveIssue->GetVoteIssue() == VOTEISSUE_REMATCH);
|
|
|
|
// Vote time is up - process the result
|
|
if( m_acceptingVotesTimer.HasStarted() )
|
|
{
|
|
int nNumVotesYES = m_nVoteOptionCount[VOTE_OPTION1];
|
|
int nNumVotesNO = m_nVoteOptionCount[VOTE_OPTION2];
|
|
|
|
//int nVoteTally = nNumVotesYES + nNumVotesNO;
|
|
|
|
bool bVotePassed = false;
|
|
|
|
int nVotesToSucceed = pActiveIssue->GetVotesRequiredToPass();
|
|
|
|
// Have we exceeded the required ratio of Voted-vs-Abstained?
|
|
if ( nNumVotesYES >= nVotesToSucceed )
|
|
{
|
|
// bail early, we succeeded
|
|
bVotePassed = true;
|
|
bVoteHasFinished = true;
|
|
}
|
|
else if ( bVoteShouldBailOnNos && (m_nPotentialVotes - nNumVotesNO) < nVotesToSucceed )
|
|
{
|
|
// failed because too many no votes
|
|
SendVoteFailedMessage( pActiveIssue->MakeVoteFailErrorCodeForClients( VOTE_FAILED_YES_MUST_EXCEED_NO ) );
|
|
bVotePassed = false;
|
|
bVoteHasFinished = true;
|
|
}
|
|
else if ( m_acceptingVotesTimer.IsElapsed() )
|
|
{
|
|
// timed out, not enough people voted yes
|
|
SendVoteFailedMessage( pActiveIssue->MakeVoteFailErrorCodeForClients( VOTE_FAILED_QUORUM_FAILURE ) );
|
|
bVotePassed = false;
|
|
bVoteHasFinished = true;
|
|
}
|
|
|
|
if ( bVoteHasFinished )
|
|
{
|
|
// for record-keeping
|
|
if ( pActiveIssue->IsYesNoVote() )
|
|
{
|
|
pActiveIssue->SetYesNoVoteCount( m_nVoteOptionCount[VOTE_OPTION1], m_nVoteOptionCount[VOTE_OPTION2], m_nPotentialVotes );
|
|
}
|
|
|
|
m_acceptingVotesTimer.Invalidate();
|
|
|
|
if ( bVotePassed )
|
|
{
|
|
m_executeCommandTimer.Start( pActiveIssue->GetCommandDelay() );
|
|
m_resetVoteTimer.Start( 5.0 );
|
|
|
|
CBaseEntity *pVoteHolder = UTIL_EntityByIndex( m_iEntityHoldingVote );
|
|
CBasePlayer *pVoteHolderPlayer = ( pVoteHolder && pVoteHolder->IsPlayer() ) ? (CBasePlayer *)( pVoteHolder ) : NULL;
|
|
if( pVoteHolderPlayer )
|
|
{
|
|
pVoteHolderPlayer->GetLastHeldVoteTimer().Invalidate(); // You can go ahead and make a new vote since yours passed.
|
|
}
|
|
|
|
UTIL_LogPrintf("Vote succeeded \"%s %s\" from #%u %s\n",
|
|
pActiveIssue->GetTypeString(),
|
|
pActiveIssue->GetDetailsString(),
|
|
m_iEntityHoldingVote,
|
|
pVoteHolderPlayer ? GameLogSystem()->FormatPlayer( pVoteHolderPlayer ) : "n/a" );
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
filter.MakeReliable();
|
|
|
|
CCSUsrMsg_VotePass msg;
|
|
msg.set_team( m_iOnlyTeamToVote );
|
|
msg.set_vote_type( pActiveIssue->GetVoteIssue() );
|
|
msg.set_disp_str( pActiveIssue->GetVotePassedString() );
|
|
msg.set_details_str( pActiveIssue->GetDetailsString() );
|
|
SendUserMessage( filter, CS_UM_VotePass, msg );
|
|
}
|
|
else
|
|
{
|
|
pActiveIssue->OnVoteFailed();
|
|
m_resetVoteTimer.Start( 5.0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Vote passed - execute the command
|
|
if( m_executeCommandTimer.HasStarted() && m_executeCommandTimer.IsElapsed() )
|
|
{
|
|
|
|
m_executeCommandTimer.Invalidate();
|
|
m_potentialIssues[m_iActiveIssueIndex]->ExecuteCommand();
|
|
}
|
|
|
|
if ( m_resetVoteTimer.HasStarted() && m_resetVoteTimer.IsElapsed() )
|
|
{
|
|
ResetData();
|
|
m_resetVoteTimer.Invalidate();
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: End the vote early if everyone's voted
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::CheckForEarlyVoteClose( void )
|
|
{
|
|
int nVoteTally = 0;
|
|
for ( int index = 0; index < MAX_VOTE_OPTIONS; index++ )
|
|
{
|
|
nVoteTally += m_nVoteOptionCount.Get( index );
|
|
}
|
|
|
|
if( nVoteTally >= m_nPotentialVotes )
|
|
{
|
|
m_acceptingVotesTimer.Start( 0 ); // Run the timer out right now
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG // Don't want to do this check for debug builds (so we can test with bots)
|
|
ConVar sv_vote_ignore_bots( "sv_vote_ignore_bots", "0" );
|
|
#define SV_VOTE_IGNORE_BOTS sv_vote_ignore_bots.GetBool()
|
|
#else
|
|
#define SV_VOTE_IGNORE_BOTS true
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CVoteController::IsValidVoter( CBasePlayer *pWhom )
|
|
{
|
|
if ( pWhom == NULL )
|
|
return false;
|
|
|
|
if ( !pWhom->IsConnected() )
|
|
return false;
|
|
|
|
if ( !sv_vote_allow_spectators.GetBool() || !sv_vote_count_spectator_votes.GetBool() )
|
|
{
|
|
if ( pWhom->GetTeamNumber() != TEAM_TERRORIST && pWhom->GetTeamNumber() != TEAM_CT )
|
|
return false;
|
|
}
|
|
|
|
if ( SV_VOTE_IGNORE_BOTS ) // Don't want to do this check for debug builds (so we can test with bots)
|
|
{
|
|
if ( pWhom->IsBot() )
|
|
return false;
|
|
|
|
if ( pWhom->IsFakeClient() )
|
|
return false;
|
|
}
|
|
|
|
if ( pWhom->IsHLTV() )
|
|
return false;
|
|
|
|
if ( pWhom->IsReplay() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::RegisterIssue( CBaseIssue *pszNewIssue )
|
|
{
|
|
m_potentialIssues.AddToTail( pszNewIssue );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CVoteController::ListIssues( CBasePlayer *pForWhom )
|
|
{
|
|
if( !sv_allow_votes.GetBool() )
|
|
return;
|
|
|
|
ClientPrint( pForWhom, HUD_PRINTCONSOLE, "---Vote commands---\n" );
|
|
|
|
for( int issueIndex = 0; issueIndex < m_potentialIssues.Count(); ++issueIndex )
|
|
{
|
|
CBaseIssue *pCurrentIssue = m_potentialIssues[issueIndex];
|
|
pCurrentIssue->ListIssueDetails( pForWhom );
|
|
}
|
|
ClientPrint( pForWhom, HUD_PRINTCONSOLE, "--- End Vote commands---\n" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CVoteController::GetWinningVoteOption( void )
|
|
{
|
|
if ( m_potentialIssues[m_iActiveIssueIndex]->IsYesNoVote() )
|
|
{
|
|
return ( m_nVoteOptionCount[VOTE_OPTION1] > m_nVoteOptionCount[VOTE_OPTION2] ) ? VOTE_OPTION1 : VOTE_OPTION2;
|
|
}
|
|
else
|
|
{
|
|
CUtlVector <int> pVoteCounts;
|
|
|
|
// Which option had the most votes?
|
|
// driller: Need to handle ties
|
|
int nHighest = m_nVoteOptionCount[0];
|
|
for ( int iIndex = 0; iIndex < m_nVoteOptionCount.Count(); iIndex ++ )
|
|
{
|
|
nHighest = ( ( nHighest < m_nVoteOptionCount[iIndex] ) ? m_nVoteOptionCount[iIndex] : nHighest );
|
|
pVoteCounts.AddToTail( m_nVoteOptionCount[iIndex] );
|
|
}
|
|
|
|
m_nHighestCountIndex = -1;
|
|
for ( int iIndex = 0; iIndex < m_nVoteOptionCount.Count(); iIndex++ )
|
|
{
|
|
if ( m_nVoteOptionCount[iIndex] == nHighest )
|
|
{
|
|
m_nHighestCountIndex = iIndex;
|
|
// henryg: break on first match, not last. this avoids a crash
|
|
// if we are all tied at zero and we pick something beyond the
|
|
// last vote option. this code really ought to ignore attempts
|
|
// to tally votes for options beyond the last valid one!
|
|
break;
|
|
}
|
|
}
|
|
|
|
return m_nHighestCountIndex;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
bool CVoteController::HasIssue( const char *pszIssue )
|
|
{
|
|
for ( int issueIndex = 0; issueIndex < m_potentialIssues.Count( ); ++issueIndex )
|
|
{
|
|
CBaseIssue *pCurrentIssue = m_potentialIssues[ issueIndex ];
|
|
if ( !pCurrentIssue )
|
|
return false;
|
|
|
|
if ( FStrEq( pszIssue, pCurrentIssue->GetTypeString( ) ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CVoteController::EndVoteImmediately( void )
|
|
{
|
|
if ( !IsAVoteInProgress( ) )
|
|
return;
|
|
|
|
CBaseIssue *pActiveIssue = m_potentialIssues[ m_iActiveIssueIndex ];
|
|
|
|
// for record-keeping
|
|
if ( pActiveIssue->IsYesNoVote( ) )
|
|
{
|
|
pActiveIssue->SetYesNoVoteCount( m_nVoteOptionCount[ VOTE_OPTION1 ], m_nVoteOptionCount[ VOTE_OPTION2 ], m_nPotentialVotes );
|
|
}
|
|
|
|
SendVoteFailedMessage( pActiveIssue->MakeVoteFailErrorCodeForClients( VOTE_FAILED_QUORUM_FAILURE ) );
|
|
pActiveIssue->OnVoteFailed( );
|
|
m_resetVoteTimer.Start( 5.0 );
|
|
|
|
m_acceptingVotesTimer.Invalidate( );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: BaseIssue
|
|
//-----------------------------------------------------------------------------
|
|
CBaseIssue::CBaseIssue( const char *pszTypeString, CVoteController *pVoteController )
|
|
{
|
|
V_strcpy_safe( m_szTypeString, pszTypeString );
|
|
|
|
m_iNumYesVotes = 0;
|
|
m_iNumNoVotes = 0;
|
|
m_iNumPotentialVotes = 0;
|
|
|
|
m_pVoteController = pVoteController;
|
|
|
|
ASSERT( pVoteController );
|
|
if ( pVoteController )
|
|
{
|
|
pVoteController->RegisterIssue( this );
|
|
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CBaseIssue::~CBaseIssue()
|
|
{
|
|
for ( int index = 0; index < m_FailedVotes.Count(); index++ )
|
|
{
|
|
FailedVote *pFailedVote = m_FailedVotes[index];
|
|
delete pFailedVote;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseIssue::GetTypeString( void )
|
|
{
|
|
return m_szTypeString;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseIssue::GetDetailsString( void )
|
|
{
|
|
return m_szDetailsString;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseIssue::SetIssueDetails( const char *pszDetails )
|
|
{
|
|
V_strcpy_safe( m_szDetailsString, pszDetails );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseIssue::IsAllyRestrictedVote( void )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int CBaseIssue::GetVotesRequiredToPass( void )
|
|
{
|
|
// TODO: to reduce risk of new bugs, this logic was preserved as-is from VoteControllerThink. But it can/should be cleaned up for legibility.
|
|
int nPotentialVoters = CountPotentialVoters();
|
|
|
|
// BUGBUG: disconnecting/reconnecting players during the vote can affect the final tally, so we will use the larger number here:
|
|
if ( m_pVoteController && ( m_pVoteController->GetPotentialVotes( ) > nPotentialVoters ) )
|
|
nPotentialVoters = m_pVoteController->GetPotentialVotes( );
|
|
|
|
int nVotesToSucceed = 0;
|
|
|
|
if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking()
|
|
&& CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() && !IsAllyRestrictedVote() )
|
|
{ // in queued matchmaking must have 100% voting to succeed (unless kick vote)
|
|
nVotesToSucceed = CSGameRules()->m_numQueuedMatchmakingAccounts;
|
|
}
|
|
// Unanimous votes require all attending humans (which might be less than 10 players)
|
|
else if ( IsUnanimousVoteToPass() )
|
|
{
|
|
nVotesToSucceed = MAX( 1, nPotentialVoters );
|
|
}
|
|
else
|
|
{
|
|
float flnVotesToSucceed = ( CSGameRules() && CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() ) ? MAX( 1, nPotentialVoters - 1 ) : ( ( float )nPotentialVoters * sv_vote_quorum_ratio.GetFloat() );
|
|
nVotesToSucceed = ceil( flnVotesToSucceed );
|
|
}
|
|
|
|
return nVotesToSucceed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
const char *CBaseIssue::GetVotePassedString( void )
|
|
{
|
|
return "Unknown vote passed.";
|
|
}
|
|
|
|
float CBaseIssue::GetFailedVoteLockOutTime( void )
|
|
{
|
|
return sv_vote_failure_timer.GetFloat();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Store failures to prevent vote spam
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseIssue::OnVoteFailed( void )
|
|
{
|
|
// Check for an existing match
|
|
for ( int index = 0; index < m_FailedVotes.Count(); index++ )
|
|
{
|
|
FailedVote *pFailedVote = m_FailedVotes[index];
|
|
if ( Q_strncmp( pFailedVote->szFailedVoteParameter, GetDetailsString(), Q_ARRAYSIZE( pFailedVote->szFailedVoteParameter ) - 1 ) == 0 )
|
|
{
|
|
pFailedVote->flLockoutTime = gpGlobals->curtime + GetFailedVoteLockOutTime();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Need to create a new one
|
|
FailedVote *pNewFailedVote = new FailedVote;
|
|
int iIndex = m_FailedVotes.AddToTail( pNewFailedVote );
|
|
V_strcpy_safe( m_FailedVotes[iIndex]->szFailedVoteCommand, GetTypeString() );
|
|
V_strcpy_safe( m_FailedVotes[iIndex]->szFailedVoteParameter, GetDetailsString() );
|
|
m_FailedVotes[iIndex]->flLockoutTime = gpGlobals->curtime + GetFailedVoteLockOutTime();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseIssue::CanTeamCallVote( int iTeam ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseIssue::CanCallVote( int iEntIndex, const char *pszCommand, const char *pszDetails, vote_create_failed_t &nFailCode, int &nTime )
|
|
{
|
|
// Automated server vote - don't bother testing against it
|
|
if ( iEntIndex == 99 )
|
|
return true;
|
|
|
|
// Bogus player
|
|
if( iEntIndex == -1 )
|
|
return false;
|
|
|
|
#ifdef TF_DLL
|
|
if ( TFGameRules() && TFGameRules()->IsInWaitingForPlayers() && !TFGameRules()->IsInTournamentMode() )
|
|
{
|
|
nFailCode = VOTE_FAILED_WAITINGFORPLAYERS;
|
|
return false;
|
|
}
|
|
#endif // TF_DLL
|
|
|
|
if ( !sv_vote_allow_in_warmup.GetBool() && CSGameRules() && CSGameRules()->IsWarmupPeriod() && !IsEnabledDuringWarmup() )
|
|
{
|
|
nFailCode = VOTE_FAILED_WAITINGFORPLAYERS;
|
|
return false;
|
|
}
|
|
|
|
CBaseEntity *pVoteCaller = UTIL_EntityByIndex( iEntIndex );
|
|
if( pVoteCaller && !CanTeamCallVote( GetVoterTeam( pVoteCaller ) ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_TEAM_CANT_CALL;
|
|
return false;
|
|
}
|
|
|
|
if ( IsVoteCallExclusiveToSpectators( ) )
|
|
{
|
|
CBasePlayer *pVoteCallerPlayer = ToBasePlayer( pVoteCaller );
|
|
|
|
if ( !pVoteCallerPlayer || !pVoteCallerPlayer->IsSpectator( ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_TEAM_CANT_CALL;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Only few votes are actually allowed in queue matchmaking mode
|
|
if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking() && !IsEnabledInQueuedMatchmaking() )
|
|
{
|
|
nFailCode = VOTE_FAILED_ISSUE_DISABLED;
|
|
return false;
|
|
}
|
|
|
|
// Disable all voting after rematch vote is initiated at the end of the match
|
|
if ( CSGameRules() && CSGameRules()->IsQueuedMatchmaking() && ( CSGameRules()->m_eQueuedMatchmakingRematchState >= CSGameRules()->k_EQueuedMatchmakingRematchState_VoteToRematchInProgress ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_ISSUE_DISABLED;
|
|
return false;
|
|
}
|
|
|
|
// don't let kick votes happen on match point or last round
|
|
if ( CSGameRules() && (CSGameRules()->IsQueuedMatchmaking() || sv_vote_disallow_kick_on_match_point.GetBool()) &&
|
|
CSGameRules()->IsLastRoundOfMatch() && CSGameRules()->IsMatchPoint() && FStrEq( VOTEISSUE_NAME_KICK, pszCommand ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_ISSUE_DISABLED;
|
|
return false;
|
|
}
|
|
|
|
// Did this fail recently?
|
|
for( int iIndex = 0; iIndex < m_FailedVotes.Count(); iIndex++ )
|
|
{
|
|
FailedVote *pCurrentFailure = m_FailedVotes[iIndex];
|
|
int nTimeRemaining = pCurrentFailure->flLockoutTime - gpGlobals->curtime;
|
|
bool bFailed = false;
|
|
|
|
if ( nTimeRemaining > 1 )
|
|
{
|
|
if ( Q_strlen( pCurrentFailure->szFailedVoteCommand ) > 0 && FStrEq( pCurrentFailure->szFailedVoteCommand, pszCommand ) )
|
|
{
|
|
// If this issue requires a parameter, see if we're voting for the same one again (i.e. changelevel ctf_2fort)
|
|
if ( Q_strlen( pCurrentFailure->szFailedVoteParameter ) > 0 && FStrEq( pCurrentFailure->szFailedVoteParameter, pszDetails ) )
|
|
{
|
|
bFailed = true;
|
|
if ( FStrEq( VOTEISSUE_NAME_CHANGELEVEL, pszCommand ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_FAILED_RECENT_CHANGEMAP;
|
|
}
|
|
else if ( FStrEq( VOTEISSUE_NAME_KICK, pszCommand ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_FAILED_RECENT_KICK;
|
|
}
|
|
else
|
|
{
|
|
nFailCode = VOTE_FAILED_FAILED_RECENTLY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( FStrEq( VOTEISSUE_NAME_SWAPTEAMS, pszCommand ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_FAILED_RECENT_SWAPTEAMS;
|
|
bFailed = true;
|
|
}
|
|
else if ( FStrEq( VOTEISSUE_NAME_SCRAMBLE, pszCommand ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_FAILED_RECENT_SCRAMBLETEAMS;
|
|
bFailed = true;
|
|
}
|
|
else if ( FStrEq( VOTEISSUE_NAME_RESTARTGAME, pszCommand ) )
|
|
{
|
|
nFailCode = VOTE_FAILED_FAILED_RECENT_RESTART;
|
|
bFailed = true;
|
|
}
|
|
}
|
|
}
|
|
// Otherwise we have a parameter-less vote, so just check the lockout timer (i.e. restartgame)
|
|
else
|
|
{
|
|
bFailed = true;
|
|
nFailCode = VOTE_FAILED_FAILED_RECENTLY;
|
|
}
|
|
}
|
|
|
|
if ( bFailed )
|
|
{
|
|
nTime = nTimeRemaining;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseIssue::CountPotentialVoters( void )
|
|
{
|
|
int nTotalPlayers = 0;
|
|
|
|
for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; ++playerIndex )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( playerIndex );
|
|
if ( m_pVoteController && m_pVoteController->IsValidVoter( pPlayer ) )
|
|
{
|
|
if ( m_pVoteController->CanTeamCastVote( GetVoterTeam( pPlayer ) ) )
|
|
{
|
|
nTotalPlayers++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nTotalPlayers;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CBaseIssue::GetNumberVoteOptions( void )
|
|
{
|
|
return 2; // The default issue is Yes/No (so 2), but it can be anywhere between 1 and MAX_VOTE_COUNT
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseIssue::IsYesNoVote( void )
|
|
{
|
|
return true; // Default
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseIssue::SetYesNoVoteCount( int iNumYesVotes, int iNumNoVotes, int iNumPotentialVotes )
|
|
{
|
|
m_iNumYesVotes = iNumYesVotes;
|
|
m_iNumNoVotes = iNumNoVotes;
|
|
m_iNumPotentialVotes = iNumPotentialVotes;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseIssue::ListStandardNoArgCommand( CBasePlayer *forWhom, const char *issueString )
|
|
{
|
|
char szBuffer[MAX_COMMAND_LENGTH];
|
|
Q_snprintf( szBuffer, MAX_COMMAND_LENGTH, "callvote %s\n", issueString );
|
|
ClientPrint( forWhom, HUD_PRINTCONSOLE, szBuffer );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CBaseIssue::GetVoteOptions( CUtlVector <const char*> &vecNames )
|
|
{
|
|
// The default vote issue is a Yes/No vote
|
|
vecNames.AddToHead( "Yes" );
|
|
vecNames.AddToTail( "No" );
|
|
|
|
return true;
|
|
}
|
|
|
|
float CBaseIssue::GetCommandDelay( void )
|
|
{
|
|
return sv_vote_command_delay.GetFloat();
|
|
}
|