/*
Copyright (C) 2006 Warsow Team.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "g_local.h"
#include "g_gametypes.h"


//===================================================================

int	clientVoted[MAX_CLIENTS];

cvar_t	*g_callvote_electpercentage;
cvar_t	*g_callvote_electtime;			// in seconds
cvar_t	*g_callvote_enabled;

enum {
	CALLVOTE_NONE = 0,
	CALLVOTE_INPROGRESS
};

enum {
	VOTED_NOTHING = 0,
	VOTED_YES,
	VOTED_NO
};

// Data that can be used by the vote specific functions
typedef struct
{
	edict_t			*caller;
	int				argc;
	char			*argv[MAX_STRING_TOKENS];
	char			*string;		// can be used to overwrite the displayed vote string
	void			*data;			// any data vote wants to carry over multiple calls of validate and to execute
} callvotedata_t;

// Data that will only be used by the common callvote functions
typedef struct
{
	int				state;
	int				type;				// number of callvote type in callvoteslist
	unsigned int	timeout;			// time to finish
	callvotedata_t	data;
} gamecallvote_t;

gamecallvote_t	callvote;

typedef struct 
{
	char		*name;
	int			expectedargs;			// -1 = any amount, -2 = any amount except 0
	qboolean	(*validate)(callvotedata_t *vote, qboolean first);
	void		(*execute)(callvotedata_t *vote);
	char		*(*current)(void);
	void		(*extraHelp)(edict_t *ent);
	char		*argument_format;
	char		*help;
} callvotetype_t;


//===================================================================
// Props of functions below
//===================================================================

static qboolean G_VoteMapValidate( callvotedata_t *vote, qboolean first );
static void G_VoteMapPassed( callvotedata_t *vote );
static char *G_VoteMapCurrent( void );
static void G_VoteMapExtraHelp( edict_t *ent );

static void G_VoteRestartPassed( callvotedata_t *vote );

static void G_VoteNextMapPassed( callvotedata_t *vote );

static qboolean G_VoteScorelimitValidate( callvotedata_t *vote, qboolean first );
static void G_VoteScorelimitPassed( callvotedata_t *vote );
static char *G_VoteScorelimitCurrent( void );

static qboolean G_VoteAllreadyValidate( callvotedata_t *vote, qboolean first );
static void G_VoteAllreadyPassed( callvotedata_t *vote );

static qboolean G_VoteTimelimitValidate( callvotedata_t *vote, qboolean first );
static void G_VoteTimelimitPassed( callvotedata_t *vote );
static char *G_VoteTimelimitCurrent( void );

static qboolean G_VoteGametypeValidate( callvotedata_t *vote, qboolean first );
static void G_VoteGametypePassed( callvotedata_t *vote );
static char *G_VoteGametypeCurrent( void );
static void G_VoteGametypeExtraHelp( edict_t *ent );

static qboolean G_VoteWarmupValidate( callvotedata_t *vote, qboolean first );
static void G_VoteWarmupPassed( callvotedata_t *vote );
static char *G_VoteWarmupCurrent( void );

static qboolean G_VoteWarmupTimelimitValidate( callvotedata_t *vote, qboolean first );
static void G_VoteWarmupTimelimitPassed( callvotedata_t *vote );
static char *G_VoteWarmupTimelimitCurrent( void );

static qboolean G_VoteExtendedTimeValidate( callvotedata_t *vote, qboolean first );
static void G_VoteExtendedTimePassed( callvotedata_t *vote );
static char *G_VoteExtendedTimeCurrent( void );

static qboolean G_VoteMaxTeamsValidate( callvotedata_t *vote, qboolean first );
static void G_VoteMaxTeamsPassed( callvotedata_t *vote );
static char *G_VoteMaxTeamsCurrent( void );

static qboolean G_VoteMaxTeamplayersValidate( callvotedata_t *vote, qboolean first );
static void G_VoteMaxTeamplayersPassed( callvotedata_t *vote );
static char *G_VoteMaxTeamplayersCurrent( void );

static qboolean G_VoteLockTeamsValidate( callvotedata_t *vote, qboolean first );
static void G_VoteLockTeamsPassed( callvotedata_t *vote );
static char *G_VoteLockTeamsCurrent( void );

static qboolean G_VoteRemoveValidate( callvotedata_t *vote, qboolean first );
static void G_VoteRemovePassed( callvotedata_t *vote );
static void G_VoteRemoveExtraHelp( edict_t *ent );

static qboolean G_VoteKickValidate( callvotedata_t *vote, qboolean first );
static void G_VoteKickPassed( callvotedata_t *vote );
static void G_VoteKickExtraHelp( edict_t *ent );

static qboolean G_VoteMuteValidate( callvotedata_t *vote, qboolean first );
static void G_VoteMutePassed( callvotedata_t *vote );
static void G_VoteVMutePassed( callvotedata_t *vote );
static void G_VoteMuteExtraHelp( edict_t *ent );

static qboolean G_VoteNumBotsValidate( callvotedata_t *vote, qboolean first );
static void G_VoteNumBotsPassed( callvotedata_t *vote );
static char *G_VoteNumBotsCurrent( void );

static qboolean G_VoteAllowTeamDamageValidate( callvotedata_t *vote, qboolean first );
static void G_VoteAllowTeamDamagePassed( callvotedata_t *vote );
static char *G_VoteAllowTeamDamageCurrent( void );

static qboolean G_VoteAllowFallDamageValidate( callvotedata_t *vote, qboolean first );
static void G_VoteAllowFallDamagePassed( callvotedata_t *vote );
static char *G_VoteAllowFallDamageCurrent( void );

static qboolean G_VoteMaxTimeoutsValidate( callvotedata_t *vote, qboolean first );
static void G_VoteMaxTimeoutsPassed( callvotedata_t *vote );
static char *G_VoteMaxTimeoutsCurrent( void );

static qboolean G_VoteTimeoutValidate( callvotedata_t *vote, qboolean first );
static void G_VoteTimeoutPassed( callvotedata_t *vote );

static qboolean G_VoteTimeinValidate( callvotedata_t *vote, qboolean first );
static void G_VoteTimeinPassed( callvotedata_t *vote );

static qboolean G_VoteChallengersValidate( callvotedata_t *vote, qboolean first );
static void G_VoteChallengersPassed( callvotedata_t *vote );
static char *G_VoteChallengersCurrent( void );

#ifndef WSW_RELEASE
static qboolean G_VoteDeadBodyFilterValidate( callvotedata_t *vote, qboolean first );
static void G_VoteDeadBodyFilterPassed( callvotedata_t *vote );
static char *G_VoteDeadBodyFilterCurrent( void );
#endif

//================================================
//				Votes definitions 
//================================================
callvotetype_t	callvoteslist[] = 
{
	{
		"map",						// callvote name
		1,							// expects number of argument after vote name
		G_VoteMapValidate,			// validate the vote, with arguments (NULL for don't)
		G_VoteMapPassed,			// execute when vote passed
		G_VoteMapCurrent,			// current value of the setting (NULL for no value)
		G_VoteMapExtraHelp,			// extra help in addition to argument format and help string
		"<name>",					// argument format
		"- Changes map"				// help string
	},

	{
		"restart",
		0,
		NULL,
		G_VoteRestartPassed,
		NULL,
		NULL,
		NULL,
		"- Restarts current map"
	},

	{
		"nextmap",
		0,
		NULL,
		G_VoteNextMapPassed,
		NULL,
		NULL,
		NULL,
		"- Jumps to the next map"
	},

	{
		"scorelimit",
		1,
		G_VoteScorelimitValidate,
		G_VoteScorelimitPassed,
		G_VoteScorelimitCurrent,
		NULL,
		"<number>",
		"- Sets the number of frags or caps needed to win the match\n- Use 0 to disable"
	},

	{
		"timelimit",
		1,
		G_VoteTimelimitValidate,
		G_VoteTimelimitPassed,
		G_VoteTimelimitCurrent,
		NULL,
		"<minutes>",
		"- Sets number of minutes after which the match ends\n- Use 0 to disable"
	},

	{
		"gametype",
		1,
		G_VoteGametypeValidate,
		G_VoteGametypePassed,
		G_VoteGametypeCurrent,
		G_VoteGametypeExtraHelp,
		"<name>",
		"- Changes the gametype"
	},

	{
		"warmup",
		1,
		G_VoteWarmupValidate,
		G_VoteWarmupPassed,
		G_VoteWarmupCurrent,
		NULL,
		"<1 or 0>",
		"- Enables or disables the warmup period before the match",
	},

	{
		"warmup_timelimit",
		1,
		G_VoteWarmupTimelimitValidate,
		G_VoteWarmupTimelimitPassed,
		G_VoteWarmupTimelimitCurrent,
		NULL,
		"<minutes>",
		"- Sets the number of minutes after which the warmup ends\n- Use 0 to disable"
	},

	{
		"extended_time",
		1,
		G_VoteExtendedTimeValidate,
		G_VoteExtendedTimePassed,
		G_VoteExtendedTimeCurrent,
		NULL,
		"<value>",
		"- Sets the length of the overtime\n- Use 0 to enable suddendeath mode"
	},

	{
		"maxteams",
		1,
		G_VoteMaxTeamsValidate,
		G_VoteMaxTeamsPassed,
		G_VoteMaxTeamsCurrent,
		NULL,
		"<number>",
		"- Set the maximum number of teams allowed"
	},

	{
		"maxteamplayers",
		1,
		G_VoteMaxTeamplayersValidate,
		G_VoteMaxTeamplayersPassed,
		G_VoteMaxTeamplayersCurrent,
		NULL,
		"<number>",
		"- Sets the maximum number of players in one team"
	},

	{
		"lock_teams",
		1,
		G_VoteLockTeamsValidate,
		G_VoteLockTeamsPassed,
		G_VoteLockTeamsCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether people are able to join teams in middle of the game"
	},

	{
		"allready",
		0,
		G_VoteAllreadyValidate,
		G_VoteAllreadyPassed,
		NULL,
		NULL,
		NULL,
		"- Sets all players as ready so the match can start"
	},

	{
		"remove",
		1,
		G_VoteRemoveValidate,
		G_VoteRemovePassed,
		NULL,
		G_VoteRemoveExtraHelp,
		"<id or name>",
		"- Forces player back to spectator mode"
	},

	{
		"kick",
		1,
		G_VoteKickValidate,
		G_VoteKickPassed,
		NULL,
		G_VoteKickExtraHelp,
		"<id or name>",
		"- Removes player from the server"
	},

	{
		"mute",
		1,
		G_VoteMuteValidate,
		G_VoteMutePassed,
		NULL,
		G_VoteMuteExtraHelp,
		"<id or name>",
		"- Disallows chat messages from the muted player"
	},
		
	{
		"vmute",
		1,
		G_VoteMuteValidate,
		G_VoteVMutePassed,
		NULL,
		G_VoteMuteExtraHelp,
		"<id or name>",
		"- Disallows voice chat messages from the muted player"
	},

	{
		"numbots",
		1,
		G_VoteNumBotsValidate,
		G_VoteNumBotsPassed,
		G_VoteNumBotsCurrent,
		NULL,
		"<count>",
		"- Sets the number of bots to play on the server"
	},

	{
		"allow_teamdamage",
		1,
		G_VoteAllowTeamDamageValidate,
		G_VoteAllowTeamDamagePassed,
		G_VoteAllowTeamDamageCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether shooting teammates will do damage to them"
	},

	{
		"allow_falldamage",
		1,
		G_VoteAllowFallDamageValidate,
		G_VoteAllowFallDamagePassed,
		G_VoteAllowFallDamageCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether falling long distances deals damage"
	},

	{
		"maxtimeouts",
		1,
		G_VoteMaxTimeoutsValidate,
		G_VoteMaxTimeoutsPassed,
		G_VoteMaxTimeoutsCurrent,
		NULL,
		"<number>",
		"- Sets the maximum number of timeouts per team or player\n- Use 0 to disable\n- Use unlimited to allow unlimited amount"
	},

	{
		"timeout",
		0,
		G_VoteTimeoutValidate,
		G_VoteTimeoutPassed,
		NULL,
		NULL,
		NULL,
		"- Pauses the game"
	},

	{
		"timein",
		0,
		G_VoteTimeinValidate,
		G_VoteTimeinPassed,
		NULL,
		NULL,
		NULL,
		"- Resumes the game if in timeout"
	},

	{
		"challengers_queue",
		1,
		G_VoteChallengersValidate,
		G_VoteChallengersPassed,
		G_VoteChallengersCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles the challenging spectators queue line"
	},

#ifndef WSW_RELEASE
	{
		"deadbody_filter",
		1,
		G_VoteDeadBodyFilterValidate,
		G_VoteDeadBodyFilterPassed,
		G_VoteDeadBodyFilterCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether the dead bodies remain in the game or go away fast"
	},
#endif

	{NULL}
};


//===================================================================
// Common functions
//===================================================================

void G_CallVotes_Reset( void )
{
	int i;

	callvote.state = CALLVOTE_NONE;
	memset( clientVoted, VOTED_NOTHING, sizeof(clientVoted) );
	callvote.timeout = 0;

	callvote.data.caller = NULL;
	if( callvote.data.string ) {
		G_Free( callvote.data.string );
		callvote.data.string = NULL;
	}
	if( callvote.data.data ) {
		G_Free( callvote.data.data );
		callvote.data.data = NULL;
	}
	for( i = 0; i < callvote.data.argc; i++ ) {
		G_Free( callvote.data.argv[i] );
		callvote.data.argv[i] = NULL;
	}
}

void G_CallVotes_Init( void )
{
	int		i = 0;

	g_callvote_electpercentage =	trap_Cvar_Get( "g_vote_percent", "55", CVAR_ARCHIVE );
	g_callvote_electtime =			trap_Cvar_Get( "g_vote_electtime", "40", CVAR_ARCHIVE );
	g_callvote_enabled =			trap_Cvar_Get( "g_vote_allowed", "1", CVAR_ARCHIVE );

	// wsw : pb : server admin can now disable a specific vote command (g_disable_vote_<vote name>)
	// init disable_vote_ cvars
	while( callvoteslist[i].name != NULL )
	{
		trap_Cvar_Get( va("g_disable_vote_%s", callvoteslist[i].name), "0", CVAR_ARCHIVE );
		i++;
	}

	G_CallVotes_Reset();
}

static void G_CallVotes_PrintUsagesToPlayer( edict_t *ent )
{
	callvotetype_t *votetype;

	G_PrintMsg( ent, "Available votes:\n" );
	for( votetype = callvoteslist; votetype->name; votetype++ ) {
		if( trap_Cvar_VariableValue( va("g_disable_vote_%s", votetype->name) ) == 1 )
			continue;
		if( votetype->argument_format )
			G_PrintMsg( ent, " %s %s\n", votetype->name, votetype->argument_format );
		else
			G_PrintMsg( ent, " %s\n", votetype->name );
	}
}

static void G_CallVotes_PrintHelpToPlayer( edict_t *ent, int type )
{
	callvotetype_t *vt = &callvoteslist[type];

	G_PrintMsg( ent, "Usage: %s %s\n%s%s\n", vt->name,
	            ( vt->argument_format ? vt->argument_format : "" ),
	            ( vt->current ? va("Current: %s\n", vt->current()) : "" ),
	            ( vt->help ? vt->help : "" ) );
	if( callvoteslist[type].extraHelp != NULL ) callvoteslist[type].extraHelp( ent );
}

static char *G_CallVotes_ArgsToString( callvotedata_t *vote )
{
	static char argstring[MAX_STRING_CHARS];
	int i;

	argstring[0] = 0;

	if( vote->argc > 0 ) Q_strncatz(argstring, vote->argv[0], sizeof(argstring));
	for( i = 1; i < vote->argc; i++ ) {
		Q_strncatz(argstring, " ", sizeof(argstring));
		Q_strncatz(argstring, vote->argv[i], sizeof(argstring));
	}

	return argstring;
}

static char *G_CallVotes_String( callvotedata_t *vote )
{
	if( vote->string )
		return vote->string;
	else
		return G_CallVotes_ArgsToString( vote );
}

static void G_CallVotes_CheckState( void )
{
	edict_t		*ent;
	int			needvotes, yeses = 0, voters = 0, noes = 0;
	static unsigned int	warntimer;

	if( callvote.state != CALLVOTE_INPROGRESS )
	{
		warntimer = 0;
		return;
	}

	if( callvoteslist[callvote.type].validate != NULL &&
		!callvoteslist[callvote.type].validate( &callvote.data, qfalse ) )
	{
		// fixme: should be vote cancelled or something
		G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_CALLVOTE_FAILED_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );
		G_PrintMsg( NULL, "Vote is no longer valid\nVote %s%s %s%s canceled\n", S_COLOR_YELLOW,
			callvoteslist[callvote.type].name, G_CallVotes_String(&callvote.data), S_COLOR_WHITE );
		G_CallVotes_Reset();
		return;
	}

	//analize votation state
	for( ent = game.edicts + 1; PLAYERNUM(ent) < game.maxclients; ent++ ) 
	{
		//fixme: ignore spectators too?
		if( ent->r.inuse && !(ent->r.svflags & SVF_FAKECLIENT) ) { //ignore bots
			voters++;
			if( clientVoted[PLAYERNUM(ent)] == VOTED_YES )
				yeses++;
			else if( clientVoted[PLAYERNUM(ent)] == VOTED_NO )
				noes++;
		}
	}

	//passed?
	needvotes = (int)((voters * g_callvote_electpercentage->value) / 100);
	if( yeses > needvotes ) 
	{
		G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_CALLVOTE_PASSED_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );
		G_PrintMsg( NULL, "Vote %s%s %s%s passed\n", S_COLOR_YELLOW, callvoteslist[callvote.type].name,
			G_CallVotes_String(&callvote.data), S_COLOR_WHITE );
		if( callvoteslist[callvote.type].execute != NULL )
			callvoteslist[callvote.type].execute(&callvote.data);
		G_CallVotes_Reset();
		return;
	}

	//failed?
	if( game.realtime > callvote.timeout || voters-noes <= needvotes ) // no change to pass anymore
	{
		G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_CALLVOTE_FAILED_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );
		G_PrintMsg( NULL, "Vote %s%s %s%s failed\n", S_COLOR_YELLOW, callvoteslist[callvote.type].name,
			G_CallVotes_String(&callvote.data), S_COLOR_WHITE );
		G_CallVotes_Reset();
		return;
	}

	if( warntimer < game.realtime ) {
		if( callvote.timeout - game.realtime <= 7.5 && callvote.timeout - game.realtime > 2.5 )
			G_AnnouncerSound( NULL, trap_SoundIndex(S_ANNOUNCER_CALLVOTE_VOTE_NOW), GS_MAX_TEAMS, qtrue );
		G_PrintMsg( NULL, "Vote in progress: %s%s %s%s, %i voted yes, %i voted no. %i required\n", S_COLOR_YELLOW,
			callvoteslist[callvote.type].name, G_CallVotes_String(&callvote.data), S_COLOR_WHITE, yeses, noes,
			needvotes + 1 );
		warntimer = game.realtime + 5 * 1000;
	}
}

void G_CallVotes_CmdVote( edict_t *ent )
{
	char		*vote;

	if( callvote.state != CALLVOTE_INPROGRESS ) {
		G_PrintMsg( ent, "%sThere's no vote in progress\n", S_COLOR_RED );
		return;
	}

	if( clientVoted[PLAYERNUM(ent)] != VOTED_NOTHING ) {
		G_PrintMsg( ent, "%sYou have already voted\n", S_COLOR_RED );
		return;
	}

	//fixme: check the rules
	//if( ent->r.svflags & SVF_NOCLIENT ) {
	//	G_PrintMsg (ent, "Spectators are not allowed to vote\n");
	//	return;
	//}

	vote = trap_Cmd_Argv(1);
	if( !Q_stricmp( vote, "yes" ) )
	{
		clientVoted[PLAYERNUM(ent)] = VOTED_YES;
		G_CallVotes_CheckState();
		return;
	} else if( !Q_stricmp( vote, "no" ) ) {
		
		clientVoted[PLAYERNUM(ent)] = VOTED_NO;
		G_CallVotes_CheckState();
		return;
	}

	G_PrintMsg( ent, "%sInvalid vote: %s%s%s. Use yes or no\n", S_COLOR_RED
	            S_COLOR_YELLOW, vote, S_COLOR_RED );
}

void G_CallVotes_Think( void )
{
	static unsigned int	callvotethinktimer = 0;
	
	if( callvote.state != CALLVOTE_INPROGRESS ) {
		callvotethinktimer = 0;
		return;
	}

	if( callvotethinktimer < game.realtime ) {
		G_CallVotes_CheckState();
		callvotethinktimer = game.realtime + 1000;
	}
}

void G_CallVote_Cmd( edict_t *ent )
{
	int				i, type = -1;
	char			*votename;
	callvotetype_t	*votetype;

	if( !g_callvote_enabled->integer ) {
		G_PrintMsg( ent, "%sCallvoting is disabled on this server\n", S_COLOR_RED );
		return;
	}

	if( callvote.state != CALLVOTE_NONE ) {
		G_PrintMsg( ent, "%sA vote is already in progress\n", S_COLOR_RED );
		return;
	}

	votename = trap_Cmd_Argv(1);
	if( !votename || !votename[0] ) {
		G_CallVotes_PrintUsagesToPlayer( ent );
		return;
	}

	if( strlen(votename) > MAX_QPATH ) {
		G_PrintMsg( ent, "%sInvalid vote\n", S_COLOR_RED );
		G_CallVotes_PrintUsagesToPlayer( ent );
		return;
	}

	//find the actual callvote command
	for( votetype = callvoteslist; votetype->name; votetype++ )
	{
		if( votetype->name && !Q_stricmp( votetype->name, votename ) ) {
			type = votetype - callvoteslist;
			break;
		}
	}

	//unrecognized callvote type
	if( type == -1 ) {
		G_PrintMsg( ent, "%sUnrecognized vote: %s\n", S_COLOR_RED, votename );
		G_CallVotes_PrintUsagesToPlayer( ent );
		return;
	}

	// wsw : pb : server admin can now disable a specific vote command (g_disable_vote_<vote name>)
	// check if vote is disabled
	if( trap_Cvar_VariableValue( va("g_disable_vote_%s", callvoteslist[type].name) ) )
	{
		G_PrintMsg( ent, "%sCallvote %s is disabled on this server\n",
		            S_COLOR_RED, callvoteslist[type].name );
		return;
	}

	//we got a valid type. Get the parameters if any
	if( callvoteslist[type].expectedargs != trap_Cmd_Argc()-2 ) {
		if( callvoteslist[type].expectedargs != -1 &&
		    (callvoteslist[type].expectedargs != -2 || trap_Cmd_Argc()-2 > 0) )
		{
			// wrong number of parametres
			G_CallVotes_PrintHelpToPlayer( ent, type );
			return;
		}
	}

	callvote.data.argc = trap_Cmd_Argc()-2;
	for( i = 0; i < callvote.data.argc; i++ )
		callvote.data.argv[i] = G_CopyString(trap_Cmd_Argv(i+2));

	callvote.data.caller = ent;

	//validate if there's a validation func
	if( callvoteslist[type].validate != NULL && !callvoteslist[type].validate( &callvote.data, qtrue ) )
	{
		G_CallVotes_PrintHelpToPlayer( ent, type );
		G_CallVotes_Reset(); // free the args
		return;
	}

	//we're done. Proceed launching the election
	memset( clientVoted, VOTED_NOTHING, sizeof(clientVoted) );
	callvote.type = type;
	callvote.timeout = game.realtime + (g_callvote_electtime->integer * 1000);

	//caller is assumed to vote YES
	clientVoted[PLAYERNUM(ent)] = VOTED_YES;
	callvote.state = CALLVOTE_INPROGRESS;

	G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_CALLVOTE_CALLED_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );

	G_PrintMsg( NULL, "%s%s requested to vote %s%s %s%s\n", ent->r.client->pers.netname, S_COLOR_WHITE, S_COLOR_YELLOW,
		callvoteslist[callvote.type].name, G_CallVotes_String(&callvote.data), S_COLOR_WHITE );

	G_PrintMsg( NULL, "%sPress %sF1 (\\vote yes)%s or %sF2 (\\vote no)%s\n", S_COLOR_WHITE, S_COLOR_YELLOW,
		S_COLOR_WHITE, S_COLOR_YELLOW, S_COLOR_WHITE );

	G_CallVotes_Think(); //make the first think
}


//==============================================
//		Vote specifics
//==============================================


//====================
// map
//====================

static void G_VoteMapExtraHelp( edict_t *ent )
{
	char	*s;
	char		buffer[2048];
	char		message[2048+200]; // use buffer to send only one print message
	int			nummaps, length, i;

	message[0] = 0;
	Q_strncatz( message, "- Available maps:", sizeof(message) );

	nummaps = trap_FS_GetFileList( "maps", ".bsp", buffer, sizeof(buffer) );
	s = buffer;
	length = 0;
	for ( i = 0; i < nummaps; i++, s += length+1 )
	{
		length = strlen( s );
		Q_strncatz( message, " ", sizeof(message) );
		Q_strncatz( message, s, sizeof(message) );
		message[strlen(message)-4] = 0; // get rid of the .bsp
	}
	G_PrintMsg( ent, "%s\n", message );
}

static qboolean G_VoteMapValidate( callvotedata_t *vote, qboolean first )
{
	char	*mapname = vote->argv[0];
	char	*s;
	char		buffer[2048];
	int			nummaps, length, i;

	if( !first ) // map can't become invalid while voting
		return qtrue;

	if( !Q_stricmp( level.mapname, mapname ) )
	{
		G_PrintMsg( vote->caller, "%sYou are already on that map\n", S_COLOR_RED );
		return qfalse;
	}

	// use filelist for case insensitive matching
	nummaps = trap_FS_GetFileList( "maps", ".bsp", buffer, sizeof(buffer) );
	s = buffer;
	length = 0;
	for ( i = 0; i < nummaps; i++, s += length+1 )
	{
		length = strlen( s );
		if( length - 4 <= 0 || (int)strlen(mapname) != length - 4 )
			continue;

		if( !Q_strnicmp( s, mapname, length-4 ) ) // ignore the .bsp
			return qtrue;
	}
	G_PrintMsg( vote->caller, "%sNo such map available on this server\n", S_COLOR_RED );

	return qfalse;
}

static void G_VoteMapPassed( callvotedata_t *vote )
{
	Q_strncpyz (level.forcemap, Q_strlwr(vote->argv[0]), sizeof(level.forcemap));
	G_EndMatch();
}

static char *G_VoteMapCurrent( void )
{
	return level.mapname;
}


//====================
// restart
//====================

static void G_VoteRestartPassed( callvotedata_t *vote )
{
	Q_strncpyz( level.forcemap, level.mapname, sizeof(level.mapname) );
	G_EndMatch();
}


//====================
// nextmap
//====================

static void G_VoteNextMapPassed( callvotedata_t *vote )
{
	level.forcemap[0] = 0;
	G_EndMatch();
}


//====================
// scorelimit
//====================

static qboolean G_VoteScorelimitValidate( callvotedata_t *vote, qboolean first )
{
	int scorelimit = atoi(vote->argv[0]);

	if( scorelimit < 0 ) {
		if( first ) G_PrintMsg( vote->caller, "%sCan't set negative scorelimit\n", S_COLOR_RED );
		return qfalse;
	}

	if( scorelimit == g_scorelimit->integer ) {
		if( first ) {
			G_PrintMsg( vote->caller, "%sScorelimit is already set to %i\n", S_COLOR_RED, scorelimit );
		}
		return qfalse;
	}

	return qtrue;
}

static void G_VoteScorelimitPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_scorelimit", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteScorelimitCurrent( void )
{
	return va("%i", g_scorelimit->integer);
}

//====================
// timelimit
//====================

static qboolean G_VoteTimelimitValidate( callvotedata_t *vote, qboolean first )
{
	int timelimit = atoi(vote->argv[0]);

	if( timelimit < 0 ) {
		if( first ) G_PrintMsg( vote->caller, "%sCan't set negative timelimit\n", S_COLOR_RED );
		return qfalse;
	}

	if( timelimit == g_timelimit->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sTimelimit is already set to %i\n", S_COLOR_RED, timelimit );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteTimelimitPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_timelimit", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteTimelimitCurrent( void )
{
	return va("%i", g_timelimit->integer);
}


//====================
// gametype
//====================

static void G_VoteGametypeExtraHelp( edict_t *ent )
{
	int		type;
	char	message[2048]; // use buffer to send only one print message

	message[0] = 0;

	if( (g_gametype->latched_string && strlen(g_gametype->latched_string) > 0) &&
			(GS_Gametype_FindByShortName(g_gametype->latched_string) != -1) ) {
		Q_strncatz( message, "- Will be changed to: ", sizeof(message) );
		Q_strncatz( message, g_gametype->latched_string, sizeof(message) );
		Q_strncatz( message, "\n", sizeof(message) );
	}

	Q_strncatz( message, "- Available gametypes:", sizeof(message) );

	for( type = GAMETYPE_DM; type < GAMETYPE_TOTAL; type++ ) {
		if( G_Gametype_IsVotable(type) ) {
			Q_strncatz( message, " ", sizeof(message) );
			Q_strncatz( message, GS_Gametype_ShortName(type), sizeof(message) );
		}
	}

	G_PrintMsg( ent, "%s\n", message );
}


static qboolean G_VoteGametypeValidate( callvotedata_t *vote, qboolean first )
{
	int		type, type_latched;

	type = GS_Gametype_FindByShortName( vote->argv[0] );

	if( type == -1 ) {
		if( first ) G_PrintMsg( vote->caller, "%sUnknown gametype\n", S_COLOR_RED );
		return qfalse;
	}

	if( (g_gametype->latched_string && strlen(g_gametype->latched_string) > 0) &&
			(GS_Gametype_FindByShortName(g_gametype->latched_string) != -1) )
		type_latched = GS_Gametype_FindByShortName(g_gametype->latched_string);
	else
		type_latched = -1;

	if( match.state > MATCH_STATE_PLAYTIME && type == type_latched ) {
		if( first )
			G_PrintMsg( vote->caller, "%s%s is already the next gametype\n", S_COLOR_RED, vote->argv[0] );
		return qfalse;
	}

	if( (match.state <= MATCH_STATE_PLAYTIME || type_latched == -1) && type == game.gametype ) {
		if( first )
			G_PrintMsg( vote->caller, "%s%s is the current gametype\n", S_COLOR_RED, vote->argv[0] );
		return qfalse;
	}

	// if the g_votable_gametypes is empty, allow all gametypes
	if( !G_Gametype_IsVotable(type) ) {
		if( first ) {
			G_PrintMsg( vote->caller, "%sVoting gametype %s is not allowed on this server\n",
					S_COLOR_RED, vote->argv[0] );
		}
		return qfalse;
	}

	return qtrue;
}

static void G_VoteGametypePassed( callvotedata_t *vote )
{
	int type;
	char message[MAX_STRING_CHARS];

	type = GS_Gametype_FindByShortName( vote->argv[0] );
	trap_Cvar_Set( "g_gametype", vote->argv[0] );

	trap_Cvar_Set( "g_timelimit", va("%i", gametypes[type].timelimit));
	trap_Cvar_Set( "g_match_extendedtime", va("%i", gametypes[type].extended_time));
	trap_Cvar_Set( "g_scoreslimit", va("%i", gametypes[type].scorelimit));
	trap_Cvar_Set( "g_teams_lock", (gametypes[type].lock_teams ? "1" : "0"));

	if( match.state == MATCH_STATE_COUNTDOWN || match.state == MATCH_STATE_PLAYTIME ) {
		// go thought scoreboard if in game
		Q_strncpyz( level.forcemap, level.mapname, sizeof(level.forcemap) );
		G_EndMatch();
	} else {
		if( !G_Match_RestartLevel() ) {
			Q_strncpyz( level.forcemap, level.mapname, sizeof(level.forcemap) );
			G_EndMatch();
		}
	}

	message[0] = 0;
	G_PrintMsg( NULL, "Gametype changed to %s\nTimelimit: %i\nExtended time: %i\nScorelimit: %i\nTeam locking: %s\n",
		g_gametype->string, g_timelimit->integer, g_match_extendedtime->integer, g_scorelimit->integer,
		(g_teams_lock->integer ? "Enabled" : "Disabled") );
}

static char *G_VoteGametypeCurrent( void )
{
	return GS_Gametype_ShortName(game.gametype);
}


//====================
// warmup
//====================

static qboolean G_VoteWarmupValidate( callvotedata_t *vote, qboolean first )
{
	int warmup = atoi(vote->argv[0]);

	if( warmup != 0 && warmup != 1 ) {
		return qfalse;
	}

	if( warmup && g_warmup_enabled->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sWarmup is already enabled\n", S_COLOR_RED );
		return qfalse;
	}

	if( !warmup && !g_warmup_enabled->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sWarmup is already disabled\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteWarmupPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_warmup_enabled", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteWarmupCurrent( void )
{
	if( g_warmup_enabled->integer )
		return "1";
	else
		return "0";
}


//====================
// warmup_timelimit
//====================

static qboolean G_VoteWarmupTimelimitValidate( callvotedata_t *vote, qboolean first )
{
	int warmup_timelimit = atoi(vote->argv[0]);

	if( warmup_timelimit < 0 ) {
		if( first ) G_PrintMsg( vote->caller, "%sCan't set negative warmup timelimit\n", S_COLOR_RED );
		return qfalse;
	}

	if( warmup_timelimit == g_warmup_timelimit->integer ) {
		if( first )
			G_PrintMsg( vote->caller, "%sWarmup timelimit is already set to %i\n", S_COLOR_RED, warmup_timelimit );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteWarmupTimelimitPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_warmup_timelimit", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteWarmupTimelimitCurrent( void )
{
	return va("%i", g_warmup_timelimit->integer);
}


//====================
// extended_time
//====================

static qboolean G_VoteExtendedTimeValidate( callvotedata_t *vote, qboolean first )
{
	int extended_time = atoi(vote->argv[0]);

	if( extended_time < 0 ) {
		if( first ) G_PrintMsg( vote->caller, "%sCan't set negative extended time\n", S_COLOR_RED );
		return qfalse;
	}

	if( extended_time == g_match_extendedtime->integer ) {
		if( first )
			G_PrintMsg( vote->caller, "%sExtended time is already set to %i\n", S_COLOR_RED, extended_time );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteExtendedTimePassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_match_extendedtime", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteExtendedTimeCurrent( void )
{
	return va("%i", g_match_extendedtime->integer);
}

//====================
// allready
//====================

static qboolean G_VoteAllreadyValidate( callvotedata_t *vote, qboolean first )
{
	int notreadys = 0;
	edict_t	*ent;

	if( match.state >= MATCH_STATE_COUNTDOWN ) {
		if( first ) G_PrintMsg( vote->caller, "%sThe game is not in warmup mode\n", S_COLOR_RED );
		return qfalse;
	}

	for( ent = game.edicts+1; PLAYERNUM(ent) < game.maxclients; ent++ ) {
		if( trap_GetClientState(PLAYERNUM(ent)) < CS_SPAWNED )
			continue;

		if( ent->s.team > TEAM_SPECTATOR && !match.ready[PLAYERNUM(ent)] )
			notreadys++;
	}

	if( !notreadys ) {
		if( first ) G_PrintMsg( vote->caller, "%sEveryone is already ready\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteAllreadyPassed( callvotedata_t *vote )
{
	edict_t	*ent;

	for( ent = game.edicts+1; PLAYERNUM(ent) < game.maxclients; ent++ ) {
		if( trap_GetClientState(PLAYERNUM(ent)) < CS_SPAWNED )
			continue;

		if( ent->s.team > TEAM_SPECTATOR && !match.ready[PLAYERNUM(ent)] ) {
			match.ready[PLAYERNUM(ent)] = qtrue;
			G_UpdatePlayerMatchMsg(ent);
			G_Match_CheckReadys();
		}
	}
}

//====================
// maxteams
//====================

static qboolean G_VoteMaxTeamsValidate( callvotedata_t *vote, qboolean first )
{
	int	maxteams = atoi(vote->argv[0]);

	if( maxteams < 2 || maxteams > GS_MAX_TEAMS ) {
		if( first ) {
			G_PrintMsg( vote->caller, "%sThe number of teams must be inbetween 2 and %i\n",
			            S_COLOR_RED, GS_MAX_TEAMS );
		}
		return qfalse;
	}

	if( g_maxteams->integer == maxteams ) {
		if( first ) G_PrintMsg( vote->caller, "%sMaximum number of teams is already %i\n", S_COLOR_RED, maxteams );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteMaxTeamsPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_maxteams", va("%i", atoi(vote->argv[0])) );

	//force map reload and restart the match
	Q_strncpyz( level.forcemap, level.mapname, sizeof(level.forcemap) );
	G_EndMatch();
}

static char *G_VoteMaxTeamsCurrent( void )
{
	return va("%i", g_maxteams->integer);
}

//====================
// maxteamplayers
//====================

static qboolean G_VoteMaxTeamplayersValidate( callvotedata_t *vote, qboolean first )
{
	int	maxteamplayers = atoi(vote->argv[0]);

	if( maxteamplayers < 1 ) {
		if( first ) {
			G_PrintMsg( vote->caller, "%sThe maximum number of players in team can't be less than 1\n",
			            S_COLOR_RED );
		}
		return qfalse;
	}

	if( g_teams_maxplayers->integer == maxteamplayers ) {
		if( first ) {
			G_PrintMsg( vote->caller, "%sMaximum number of players in team is already %i\n",
			            S_COLOR_RED, maxteamplayers );
		}
		return qfalse;
	}

	return qtrue;
}

static void G_VoteMaxTeamplayersPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_teams_maxplayers", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteMaxTeamplayersCurrent( void )
{
	return va("%i", g_teams_maxplayers->integer);
}

//====================
// teamlock
//====================

static qboolean G_VoteLockTeamsValidate( callvotedata_t *vote, qboolean first )
{
	int lockteams = atoi(vote->argv[0]);

	if( lockteams != 0 && lockteams != 1 )
		return qfalse;

	if( lockteams && g_teams_lock->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sTeam locking is already enabled\n", S_COLOR_RED );
		return qfalse;
	}

	if( !lockteams && !g_teams_lock->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sTeam locking is already disabled\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteLockTeamsPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_teams_lock", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteLockTeamsCurrent( void )
{
	return va("%i", g_teams_lock->integer);
}

//====================
// remove
//====================

static void G_VoteRemoveExtraHelp( edict_t *ent )
{
	int i;
	edict_t *e;
	char msg[1024];

	msg[0] = 0;
	Q_strncatz( msg, "- List of players in game:\n", sizeof(msg) );

	if( GS_Gametype_IsTeamBased(game.gametype) ) {
		int team;

		for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ ) {
			Q_strncatz( msg, va("%s:\n", GS_TeamName(team)), sizeof(msg) );
			for( i = 0, e = game.edicts+1; i < game.maxclients; i++, e++ ) {
				if( !e->r.inuse || e->s.team != team )
					continue;

				Q_strncatz( msg, va("%3i: %s\n", PLAYERNUM(e), e->r.client->pers.netname), sizeof(msg) );
			}
		}
	} else {
		for( i = 0, e = game.edicts+1; i < game.maxclients; i++, e++ ) {
			if( !e->r.inuse || e->s.team != TEAM_PLAYERS )
				continue;

			Q_strncatz( msg, va("%3i: %s\n", PLAYERNUM(e), e->r.client->pers.netname), sizeof(msg) );
		}
	}

	G_PrintMsg( ent, "%s", msg );
}

static qboolean G_VoteRemoveValidate( callvotedata_t *vote, qboolean first )
{
	int who = -1;

	if( first )
	{
		edict_t *tokick = G_PlayerForText( vote->argv[0] );

		if( tokick )
			who = PLAYERNUM(tokick);
		else
			who = -1;

		if( who == -1 )
		{
			G_PrintMsg( vote->caller, "%sNo such player\n", S_COLOR_RED );
			return qfalse;
		}
		else if( tokick->s.team == TEAM_SPECTATOR )
		{
			G_PrintMsg( vote->caller, "Player %s%s%s is already spectator.\n", S_COLOR_WHITE,
				tokick->r.client->pers.netname, S_COLOR_RED );
			return qfalse;
		}
		else
		{
			// we save the player id to be removed, so we don't later get confused by new ids or players changing names
			vote->data = G_Malloc( sizeof(int) );
			memcpy( vote->data, &who, sizeof(int) );
		}
	}
	else
	{
		memcpy( &who, vote->data, sizeof(int) );
	}

	if( !game.edicts[who+1].r.inuse || game.edicts[who+1].s.team == TEAM_SPECTATOR ) {
		return qfalse;
	} else {
		if( !vote->string || Q_stricmp(vote->string, game.edicts[who+1].r.client->pers.netname) )
		{
			if( vote->string ) G_Free(vote->string);
			vote->string = G_CopyString( game.edicts[who+1].r.client->pers.netname );
		}
		return qtrue;
	}
}

static void G_VoteRemovePassed( callvotedata_t *vote )
{
	int who;
	edict_t *ent;

	memcpy( &who, vote->data, sizeof(int) );
	ent = &game.edicts[who+1];
	// may have disconnect along the callvote time
	if( !ent->r.inuse || !ent->r.client || ent->s.team == TEAM_SPECTATOR )
		return;

	G_PrintMsg( NULL, "Player %s%s removed from team %s%s.\n", ent->r.client->pers.netname, S_COLOR_WHITE,
		GS_TeamName(ent->s.team), S_COLOR_WHITE );

	G_Teams_SetTeam( ent, TEAM_SPECTATOR );
}


//====================
// kick
//====================

static void G_VoteKickExtraHelp( edict_t *ent )
{
	int i;
	edict_t *e;
	char msg[1024];

	msg[0] = 0;
	Q_strncatz( msg, "- List of current players:\n", sizeof(msg) );

	for( i = 0, e = game.edicts+1; i < game.maxclients; i++, e++ ) {
		if( !e->r.inuse )
			continue;

		Q_strncatz( msg, va("%3i: %s\n", PLAYERNUM(e), e->r.client->pers.netname), sizeof(msg) );
	}

	G_PrintMsg( ent, "%s", msg );
}

static qboolean G_VoteKickValidate( callvotedata_t *vote, qboolean first )
{
	int who = -1;

	if( first )
	{
		edict_t *tokick = G_PlayerForText( vote->argv[0] );

		if( tokick )
			who = PLAYERNUM(tokick);
		else
			who = -1;

		if( who != -1 )
		{
			// we save the player id to be kicked, so we don't later get confused by new ids or players changing names
			vote->data = G_Malloc( sizeof(int) );
			memcpy( vote->data, &who, sizeof(int) );
		}
		else
		{
			G_PrintMsg( vote->caller, "%sNo such player\n", S_COLOR_RED );
			return qfalse;
		}
	}
	else
	{
		memcpy( &who, vote->data, sizeof(int) );
	}

	if( !game.edicts[who+1].r.inuse ) {
		return qfalse;
	} else {
		if( !vote->string || Q_stricmp(vote->string, game.edicts[who+1].r.client->pers.netname) )
		{
			if( vote->string ) G_Free(vote->string);
			vote->string = G_CopyString( game.edicts[who+1].r.client->pers.netname );
		}
		return qtrue;
	}
}

static void G_VoteKickPassed( callvotedata_t *vote )
{
	int who;
	edict_t *ent;

	memcpy( &who, vote->data, sizeof(int) );
	ent = &game.edicts[who+1];
	if( !ent->r.inuse || !ent->r.client ) // may have disconnect along the callvote time
		return;

	trap_DropClient(ent, DROP_TYPE_NORECONNECT, "You were kicked");
}



//====================
// mute
//====================

static void G_VoteMuteExtraHelp( edict_t *ent )
{
	int i;
	edict_t *e;
	char msg[1024];

	msg[0] = 0;
	Q_strncatz( msg, "- List of current players:\n", sizeof(msg) );

	for( i = 0, e = game.edicts+1; i < game.maxclients; i++, e++ ) {
		if( !e->r.inuse )
			continue;

		Q_strncatz( msg, va("%3i: %s\n", PLAYERNUM(e), e->r.client->pers.netname), sizeof(msg) );
	}

	G_PrintMsg( ent, "%s", msg );
}

static qboolean G_VoteMuteValidate( callvotedata_t *vote, qboolean first )
{
	int who = -1;

	if( first )
	{
		edict_t *tomute = G_PlayerForText( vote->argv[0] );

		if( tomute )
			who = PLAYERNUM(tomute);
		else
			who = -1;

		if( who != -1 )
		{
			// we save the player id to be kicked, so we don't later get confused by new ids or players changing names
			vote->data = G_Malloc( sizeof(int) );
			memcpy( vote->data, &who, sizeof(int) );
		}
		else
		{
			G_PrintMsg( vote->caller, "%sNo such player\n", S_COLOR_RED );
			return qfalse;
		}
	}
	else
	{
		memcpy( &who, vote->data, sizeof(int) );
	}

	if( !game.edicts[who+1].r.inuse ) {
		return qfalse;
	} else {
		if( !vote->string || Q_stricmp(vote->string, game.edicts[who+1].r.client->pers.netname) )
		{
			if( vote->string ) G_Free(vote->string);
			vote->string = G_CopyString( game.edicts[who+1].r.client->pers.netname );
		}
		return qtrue;
	}
}

// chat mute
static void G_VoteMutePassed( callvotedata_t *vote )
{
	int who;
	edict_t *ent;

	memcpy( &who, vote->data, sizeof(int) );
	ent = &game.edicts[who+1];
	if( !ent->r.inuse || !ent->r.client ) // may have disconnect along the callvote time
		return;

	ent->r.client->pers.muted |= 1;
}

// vsay mute
static void G_VoteVMutePassed( callvotedata_t *vote )
{
	int who;
	edict_t *ent;

	memcpy( &who, vote->data, sizeof(int) );
	ent = &game.edicts[who+1];
	if( !ent->r.inuse || !ent->r.client ) // may have disconnect along the callvote time
		return;

	ent->r.client->pers.muted |= 2;
}

//====================
// addbots
//====================

static qboolean G_VoteNumBotsValidate( callvotedata_t *vote, qboolean first )
{
	int	numbots = atoi(vote->argv[0]);
	edict_t *e;
	int	count;

	//count the active client spots not being bots
	for( count = 0, e = game.edicts + 1; PLAYERNUM(e) < game.maxclients; e++ )
	{
		if( e->r.client && e->r.inuse && e->ai.type != AI_ISBOT )
			count++;
	}

	if( numbots < 0 )
		return qfalse;

	if( g_numbots->integer == numbots ) {
		if( first ) G_PrintMsg( vote->caller, "%sNumber of bots is already %i\n", S_COLOR_RED, numbots );
		return qfalse;
	}

	if( !count )
		count = 1;

	if( numbots > game.numBots ) {
		if( game.maxclients - (count+game.numBots) == 0 ) {
			if( first ) G_PrintMsg( vote->caller, "%sThere isn't room for any more bots\n", S_COLOR_RED );
			return qfalse;
		}

		if( count + numbots > game.maxclients ) {
			if( first ) {
				G_PrintMsg( vote->caller, "%sThere is only room for %i more bots. Increase sv_maxclients.\n",
					S_COLOR_RED, game.maxclients - (count+game.numBots) );
			}
			return qfalse;
		}
	}

	return qtrue;
}

static void G_VoteNumBotsPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_numbots", vote->argv[0] );
}

static char *G_VoteNumBotsCurrent( void )
{
	return va("%i", g_numbots->integer);
}

//====================
// allow_teamdamage
//====================

static qboolean G_VoteAllowTeamDamageValidate( callvotedata_t *vote, qboolean first )
{
	int teamdamage = atoi(vote->argv[0]);

	if( teamdamage != 0 && teamdamage != 1 )
		return qfalse;

	if( teamdamage && g_teams_teamdamage->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sTeam damage is already allowed\n", S_COLOR_RED );
		return qfalse;
	}

	if( !teamdamage && !g_teams_teamdamage->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sTeam damage is already disabled\n", S_COLOR_RED );
		return qfalse;
	}
	
	return qtrue;
}

static void G_VoteAllowTeamDamagePassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_teams_teamdamage", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteAllowTeamDamageCurrent( void )
{
	if( g_teams_teamdamage->integer )
		return "1";
	else
		return "0";
}


//====================
// allow_falldamage
//====================

static qboolean G_VoteAllowFallDamageValidate( callvotedata_t *vote, qboolean first )
{
	int falldamage = atoi(vote->argv[0]);

	if( falldamage != 0 && falldamage != 1 )
		return qfalse;

	if( falldamage && g_allow_falldamage->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sFall damage is already allowed\n", S_COLOR_RED );
		return qfalse;
	}

	if( !falldamage && !g_allow_falldamage->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sFall damage is already disabled\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteAllowFallDamagePassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_allow_falldamage", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteAllowFallDamageCurrent( void )
{
	if( g_allow_falldamage->integer )
		return "1";
	else
		return "0";
}

//====================
// maxtimeouts
//====================

static qboolean G_VoteMaxTimeoutsValidate( callvotedata_t *vote, qboolean first )
{
	int	maxtimeouts = atoi(vote->argv[0]);

	if( !Q_stricmp(vote->argv[0], "unlimited") ) {
		maxtimeouts = -1;
	} else {
		if( maxtimeouts < 0 ) {
			if( first ) {
				G_PrintMsg( vote->caller, "%sThe maximum number of timeouts can't be negative\n",
						S_COLOR_RED );
			}
			return qfalse;
		}
	}

	if( g_maxtimeouts->integer == maxtimeouts ) {
		if( first && maxtimeouts == -1 ) {
			G_PrintMsg( vote->caller, "%sMaximum number of timeouts is already unlimited\n",
					S_COLOR_RED, maxtimeouts );
		} else {
			G_PrintMsg( vote->caller, "%sMaximum number of timeouts is already %i\n",
					S_COLOR_RED, maxtimeouts );
		}
		return qfalse;
	}

	return qtrue;
}

static void G_VoteMaxTimeoutsPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_maxtimeouts", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteMaxTimeoutsCurrent( void )
{
	if( g_maxtimeouts->integer == -1 ) {
		return "unlimited";
	} else {
		return va("%i", g_maxtimeouts->integer);
	}
}

//====================
// timeout
//====================
static qboolean G_VoteTimeoutValidate( callvotedata_t *vote, qboolean first )
{
	if( gtimeout.active && (gtimeout.endtime - gtimeout.time) >= 2 * TIMEIN_TIME ) {
		if( first ) G_PrintMsg( vote->caller, "%sTimeout already in progress\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteTimeoutPassed( callvotedata_t *vote )
{
	if( !gtimeout.active )
		G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_TIMEOUT_TIMEOUT_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );
	gtimeout.active = qtrue;
	gtimeout.caller = 0;
	gtimeout.endtime = gtimeout.time + TIMEOUT_TIME + FRAMETIME;
}

//====================
// timein
//====================
static qboolean G_VoteTimeinValidate( callvotedata_t *vote, qboolean first )
{
	if( !gtimeout.active ) {
		if( first ) G_PrintMsg( vote->caller, "%sNo timeout in progress\n", S_COLOR_RED );
		return qfalse;
	}

	if( gtimeout.endtime - gtimeout.time <= 2 * TIMEIN_TIME ) {
		if( first ) G_PrintMsg( vote->caller, "%sTimeout is about to end already\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteTimeinPassed( callvotedata_t *vote )
{
	G_AnnouncerSound( NULL, trap_SoundIndex(va(S_ANNOUNCER_TIMEOUT_TIMEIN_1_to_2, (rand()&1)+1)), GS_MAX_TEAMS, qtrue );
	gtimeout.endtime = gtimeout.time + TIMEIN_TIME + FRAMETIME;
}

//====================
// challengers_queue
//====================
static qboolean G_VoteChallengersValidate( callvotedata_t *vote, qboolean first )
{
	int challengers = atoi(vote->argv[0]);

	if( challengers != 0 && challengers != 1 )
		return qfalse;

	if( challengers && g_challengers_queue->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sChallengers queue is already enabled\n", S_COLOR_RED );
		return qfalse;
	}

	if( !challengers && !g_challengers_queue->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sChallengers queue is already disabled\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteChallengersPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_challengers_queue", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteChallengersCurrent( void )
{
	if( g_challengers_queue->integer )
		return "1";
	else
		return "0";
}

//====================
// deadbody_filter
//====================
#ifndef WSW_RELEASE
static qboolean G_VoteDeadBodyFilterValidate( callvotedata_t *vote, qboolean first )
{
	int bodyfilter = atoi(vote->argv[0]);

	if( bodyfilter != 0 && bodyfilter != 1 )
		return qfalse;

	if( bodyfilter && g_deadbody_filter->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sDeadbody filter is already allowed\n", S_COLOR_RED );
		return qfalse;
	}

	if( !bodyfilter && !g_deadbody_filter->integer ) {
		if( first ) G_PrintMsg( vote->caller, "%sDeadbody filter is already disabled\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

static void G_VoteDeadBodyFilterPassed( callvotedata_t *vote )
{
	trap_Cvar_Set( "g_deadbody_filter", va("%i", atoi(vote->argv[0])) );
}

static char *G_VoteDeadBodyFilterCurrent( void )
{
	if( g_deadbody_filter->integer )
		return "1";
	else
		return "0";
}
#endif // WSW_RELEASE
