/*
   Copyright (C) 2006 Pekka Lampila ("Medar"), Damien Deville ("Pb")
   and German Garcia Fernandez ("Jal") for Chasseur de bots association.


   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"
#include "g_gametype_ca.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;
	qboolean operatorcall;
	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_VoteLockValidate( callvotedata_t *vote, qboolean first );
static void G_VoteLockPassed( callvotedata_t *vote );

static qboolean G_VoteUnlockValidate( callvotedata_t *vote, qboolean first );
static void G_VoteUnlockPassed( callvotedata_t *vote );

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_VoteKickBanValidate( callvotedata_t *vote, qboolean first );
static void G_VoteKickBanPassed( callvotedata_t *vote );
static void G_VoteKickBanExtraHelp( 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_VoteUnmuteValidate( callvotedata_t *vote, qboolean first );
static void G_VoteUnmutePassed( callvotedata_t *vote );
static void G_VoteVUnmutePassed( callvotedata_t *vote );
static void G_VoteUnmuteExtraHelp( 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 );

static qboolean G_VoteAllowUnevenValidate( callvotedata_t *vote, qboolean first );
static void G_VoteAllowUnevenPassed( callvotedata_t *vote );
static char *G_VoteAllowUnevenCurrent( void );

#ifndef WSW_RELEASE
static qboolean G_VoteCAHealthValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAHealthPassed( callvotedata_t *vote );
static char *G_VoteCAHealthCurrent( void );

static qboolean G_VoteCAArmorValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAArmorPassed( callvotedata_t *vote );
static char *G_VoteCAArmorCurrent( void );

static qboolean G_VoteCAWeaponsValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAWeaponsPassed( callvotedata_t *vote );
static char *G_VoteCAWeaponsCurrent( void );

static qboolean G_VoteCAWeakAmmoValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAWeakAmmoPassed( callvotedata_t *vote );
static char *G_VoteCAWeakAmmoCurrent( void );

static qboolean G_VoteCAStrongAmmoValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAStrongAmmoPassed( callvotedata_t *vote );
static char *G_VoteCAStrongAmmoCurrent( void );
#endif // WSW_RELEASE

static qboolean G_VoteCARoundlimitValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCARoundlimitPassed( callvotedata_t *vote );
static char *G_VoteCARoundlimitCurrent( void );

static qboolean G_VoteCAAllowSelfDamageValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAAllowSelfDamagePassed( callvotedata_t *vote );
static char *G_VoteCAAllowSelfDamageCurrent( void );

static qboolean G_VoteCAAllowTeamDamageValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAAllowTeamDamagePassed( callvotedata_t *vote );
static char *G_VoteCAAllowTeamDamageCurrent( void );

static qboolean G_VoteCACompetitionModeValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCACompetitionModePassed( callvotedata_t *vote );
static char *G_VoteCACompetitionModeCurrent( void );

#ifndef WSW_RELEASE
static qboolean G_VoteCAClassModeValidate( callvotedata_t *vote, qboolean first );
static void G_VoteCAClassModePassed( callvotedata_t *vote );
static char *G_VoteCAClassModeCurrent( void );

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

//================================================
//				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/[startnum]>", // 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",
		0,
		G_VoteLockValidate,
		G_VoteLockPassed,
		NULL,
		NULL,
		NULL,
		"- Locks teams to disallow players joining in mid-game"
	},

	{
		"unlock",
		0,
		G_VoteUnlockValidate,
		G_VoteUnlockPassed,
		NULL,
		NULL,
		NULL,
		"- Unlocks teams to allow players joining in mid-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"
	},

	{
		"kickban",
		1,
		G_VoteKickBanValidate,
		G_VoteKickBanPassed,
		NULL,
		G_VoteKickBanExtraHelp,
		"<id or name>",
		"- Removes player from the server and bans his IP-address for 15 minutes"
	},

	{
		"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"
	},

	{
		"unmute",
		1,
		G_VoteUnmuteValidate,
		G_VoteUnmutePassed,
		NULL,
		G_VoteUnmuteExtraHelp,
		"<id or name>",
		"- Reallows chat messages from the unmuted player"
	},

	{
		"vunmute",
		1,
		G_VoteUnmuteValidate,
		G_VoteVUnmutePassed,
		NULL,
		G_VoteUnmuteExtraHelp,
		"<id or name>",
		"- Reallows voice chat messages from the unmuted 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"
	},

	{
		"allow_uneven",
		1,
		G_VoteAllowUnevenValidate,
		G_VoteAllowUnevenPassed,
		G_VoteAllowUnevenCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether uneven teams is allowed"
	},

#ifndef WSW_RELEASE
	{
		"ca_health",
		1,
		G_VoteCAHealthValidate,
		G_VoteCAHealthPassed,
		G_VoteCAHealthCurrent,
		NULL,
		"<number>",
		"- Sets the spawn health for every player"
	},

	{
		"ca_armor",
		1,
		G_VoteCAArmorValidate,
		G_VoteCAArmorPassed,
		G_VoteCAArmorCurrent,
		NULL,
		"<number>",
		"- Sets the spawn armor for every player"
	},

	{
		"ca_weapons",
		1,
		G_VoteCAWeaponsValidate,
		G_VoteCAWeaponsPassed,
		G_VoteCAWeaponsCurrent,
		NULL,
		"\"<normal> <grunt> <camper> <spammer>\"",
		"- Sets weapon flag" // FIXME: show available value of weaponflag
	},

	{
		"ca_weak_ammo",
		1,
		G_VoteCAWeakAmmoValidate,
		G_VoteCAWeakAmmoPassed,
		G_VoteCAWeakAmmoCurrent,
		NULL,
		"\"<gb> <rg> <gl> <rl> <pg> <lg> <eb>\"",
		"- Sets the spawn weak ammo"
	},

	{
		"ca_strong_ammo",
		1,
		G_VoteCAStrongAmmoValidate,
		G_VoteCAStrongAmmoPassed,
		G_VoteCAStrongAmmoCurrent,
		NULL,
		"\"<gb> <rg> <gl> <rl> <pg> <lg> <eb>\"",
		"- Sets the spawn strong ammo"
	},

#endif // WSW_RELEASE

	{
		"ca_roundlimit",
		1,
		G_VoteCARoundlimitValidate,
		G_VoteCARoundlimitPassed,
		G_VoteCARoundlimitCurrent,
		NULL,
		"<number>",
		"- Sets number of rounds after which the match ends\n- Use 0 to disable"
	},

	{
		"ca_allow_selfdamage",
		1,
		G_VoteCAAllowSelfDamageValidate,
		G_VoteCAAllowSelfDamagePassed,
		G_VoteCAAllowSelfDamageCurrent,
		NULL,
		"<1, 0 or 2>",
		"- Toggles whether self-inflicted wounds damage\n- Use 2 to limit armor only damage"
	},

	{
		"ca_allow_teamdamage",
		1,
		G_VoteCAAllowTeamDamageValidate,
		G_VoteCAAllowTeamDamagePassed,
		G_VoteCAAllowTeamDamageCurrent,
		NULL,
		"<1, 0 or 2>",
		"- Toggles whether shooting teammates will do damage to them\n- Use 2 to limit armor only damage"
	},

	{
		"ca_competitionmode",
		1,
		G_VoteCACompetitionModeValidate,
		G_VoteCACompetitionModePassed,
		G_VoteCACompetitionModeCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether competition mode is enabled"
	},

#ifndef WSW_RELEASE
	{
		"ca_classmode",
		1,
		G_VoteCAClassModeValidate,
		G_VoteCAClassModePassed,
		G_VoteCAClassModeCurrent,
		NULL,
		"<1 or 0>",
		"- Toggles whether class mode is enabled"
	},

	{
		"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 // WSW_RELEASE

	{ 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 );
	}
	if( callvote.data.data )
	{
		G_Free( callvote.data.data );
	}
	for( i = 0; i < callvote.data.argc; i++ )
	{
		if( callvote.data.argv[i] )
			G_Free( callvote.data.argv[i] );
	}

	memset( &callvote.data, 0, sizeof( callvotedata_t ) );
}

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_Value( va( "g_disable_vote_%s", votetype->name ) ) == 1 )
			continue;

#ifndef WSW_RELEASE
		// FIXME: omit ca_* to avoid "Unknown game command" error. maybe message length is too long?
		if( !strncmp( votetype->name, "ca_", sizeof( "ca_" ) - 1 ) )
			continue;
#else
		if( game.gametype != GAMETYPE_CA && !strncmp( votetype->name, "ca_", sizeof( "ca_" ) - 1 ) )
			continue;
#endif // WSW_RELEASE

		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++ )
	{
		if( !ent->r.inuse || trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED )
			continue;
		if( ( ent->r.svflags & SVF_FAKECLIENT ) || ent->r.client->pers.tv )
			continue;

		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 || callvote.data.operatorcall )
	{
		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->s.team == TEAM_SPECTATORS ) {
	//	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;
	}
}

static void G_CallVote( edict_t *ent, qboolean isoperator )
{
	int i, type = -1;
	char *votename;
	callvotetype_t *votetype;

	// dmh - trying spectators cant start vote idea
	// FIXME: Gameplay specific?
	if( ( ent->s.team == TEAM_SPECTATOR && game.gametype == GAMETYPE_DUEL
	      && match.state > MATCH_STATE_WARMUP && !gtimeout.active ) && !g_allow_spectator_voting->integer )
	{
		int team, count;
		edict_t *e;

		for( count = 0, team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ )
		{
			if( !teamlist[team].numplayers )
				continue;

			for( i = 0; teamlist[team].playerIndices[i] != -1; i++ )
			{
				e = game.edicts + teamlist[team].playerIndices[i];
				if( e->r.inuse && e->r.svflags & SVF_FAKECLIENT )
					count++;
			}
		}

		if( !count )
		{
			G_PrintMsg( ent, "%sSpectators cannot start a vote while a match is in progress\n", S_COLOR_RED );
			return;
		}
	}

	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_Value( 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;
	callvote.data.operatorcall = isoperator;

	//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
}

void G_CallVote_Cmd( edict_t *ent )
{
	G_CallVote( ent, qfalse );
}

void G_OperatorVote_Cmd( edict_t *ent )
{
	if( !ent->r.client )
		return;
	if( !ent->r.client->pers.isoperator )
	{
		G_PrintMsg( ent, "You are not a game operator\n" );
		return;
	}

	G_CallVote( ent, qtrue );
}

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


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

static void G_VoteMapExtraHelp( edict_t *ent )
{
	char *s;
	char buffer[MAX_STRING_CHARS];
	char message[MAX_STRING_CHARS];    // use buffer to send only one print message
	int nummaps, i, j, start;
	size_t length, msglength;

	// don't use Q_strncatz and Q_strncpyz below because we
	// check length of the message string manually

	memset( message, 0, sizeof( message ) );
	strcpy( message, "- Available maps:" );

	i = 0;
	nummaps = trap_FS_GetFileList( "maps", ".bsp", buffer, sizeof( buffer ), 0, 0 );
	if( nummaps )
	{
		if( trap_Cmd_Argc() > 2 )
		{
			start = atoi( trap_Cmd_Argv( 2 ) ) - 1;
			if( start < 0 )
				start = 0;
		}
		else
		{
			start = 0;
		}

		msglength = strlen( message );
		i = start;
		do
		{
			if( ( j = trap_FS_GetFileList( "maps", ".bsp", buffer, sizeof( buffer ), i, nummaps ) ) == 0 )
			{
				i++; // can happen if the filename is too long to fit into the buffer or we're done
				continue;
			}

			for( s = buffer; j > 0; j--, i++, s += length + 1, msglength += length - 4 + 1 )
			{
				length = strlen( s );
				s[length - 4] = '\0'; // get rid of the .bsp
				if( msglength + length + 1 >= sizeof( message ) )
					break;
				strcat( message, " " );
				strcat( message, s );
			}
			if( j )
				break;
		}
		while( i < nummaps );
	}
	else
	{
		strcat( message, "\nNone" );
	}

	G_PrintMsg( ent, "%s\n", message );
	if( i < nummaps )
		G_PrintMsg( ent, "Type 'callvote map %i' for more maps\n", i );
}

static qboolean G_VoteMapValidate( callvotedata_t *vote, qboolean first )
{
	char mapname[MAX_CONFIGSTRING_CHARS];

	if( !first )  // map can't become invalid while voting
		return qtrue;
	if( Q_isdigit( vote->argv[0] ) )  // FIXME
		return qfalse;

	if( strlen( "maps/" ) + strlen( vote->argv[0] ) + strlen( ".bsp" ) >= MAX_CONFIGSTRING_CHARS )
	{
		G_PrintMsg( vote->caller, "%sToo long map name\n", S_COLOR_RED );
		return qfalse;
	}

	Q_strncpyz( mapname, vote->argv[0], sizeof( mapname ) );
	COM_SanitizeFilePath( mapname );

	if( !COM_ValidateRelativeFilename( mapname ) || strchr( mapname, '/' ) || strchr( mapname, '.' ) )
	{
		G_PrintMsg( vote->caller, "%sInvalid map name\n", S_COLOR_RED );
		return qfalse;
	}

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

	if( trap_FS_FOpenFile( va( "maps/%s.bsp", mapname ), NULL, FS_READ ) != -1 )
	{
		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 *gametype_string;
	int timelimit, extended_time, scorelimit;

	gametype_string = vote->argv[0];
	type = GS_Gametype_FindByShortName( gametype_string );
	timelimit = gametypes[type].timelimit;
	extended_time = gametypes[type].extended_time;
	scorelimit = gametypes[type].scorelimit;

	trap_Cvar_Set( "g_gametype", gametype_string );
	trap_Cvar_Set( "g_timelimit", va( "%i", timelimit ) );
	trap_Cvar_Set( "g_match_extendedtime", va( "%i", extended_time) );
	trap_Cvar_Set( "g_scorelimit", va( "%i", scorelimit ) );
	trap_Cvar_Set( "g_allow_falldamage", ((type == GAMETYPE_CA || type == GAMETYPE_DA)? "0" : "1") );

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

	// we can't use gametype_string here, because there's a big chance it has just been freed after G_EndMatch
	G_PrintMsg( NULL, "Gametype changed to %s\nTimelimit: %i\nExtended time: %i\nScorelimit: %i\n",
	            GS_Gametype_ShortName( type ), timelimit, extended_time, scorelimit );
}

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";
	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 );
}

//====================
// lock
//====================

static qboolean G_VoteLockValidate( callvotedata_t *vote, qboolean first )
{
	if( match.state > MATCH_STATE_PLAYTIME )
	{
		if( first ) G_PrintMsg( vote->caller, "%sCan't lock teams after the match\n", S_COLOR_RED );
		return qfalse;
	}

	if( game.lock_teams )
	{
		if( match.state < MATCH_STATE_COUNTDOWN && first )
		{
			G_PrintMsg( vote->caller, "%sTeams are already set to be locked on match start\n", S_COLOR_RED );
		}
		else if( first )
		{
			G_PrintMsg( vote->caller, "%sTeams are already locked\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

static void G_VoteLockPassed( callvotedata_t *vote )
{
	int team;

	game.lock_teams = qtrue;

	// if we are inside a match, update the teams state
	if( match.state >= MATCH_STATE_COUNTDOWN && match.state <= MATCH_STATE_PLAYTIME )
	{
		if( GS_Gametype_IsTeamBased( game.gametype ) )
		{
			for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
				G_Teams_LockTeam( team );
		}
		else
		{
			G_Teams_LockTeam( TEAM_PLAYERS );
		}
		G_PrintMsg( NULL, "Teams locked\n" );
	}
	else
	{
		G_PrintMsg( NULL, "Teams will be locked when the match starts\n" );
	}
}

//====================
// unlock
//====================

static qboolean G_VoteUnlockValidate( callvotedata_t *vote, qboolean first )
{
	if( match.state > MATCH_STATE_PLAYTIME )
	{
		if( first ) G_PrintMsg( vote->caller, "%sCan't unlock teams after the match\n", S_COLOR_RED );
		return qfalse;
	}

	if( !game.lock_teams )
	{
		if( match.state < MATCH_STATE_COUNTDOWN && first )
		{
			G_PrintMsg( vote->caller, "%sTeams are not set to be locked\n", S_COLOR_RED );
		}
		else if( first )
		{
			G_PrintMsg( vote->caller, "%sTeams are not locked\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

static void G_VoteUnlockPassed( callvotedata_t *vote )
{
	int team;

	game.lock_teams = qfalse;

	// if we are inside a match, update the teams state
	if( match.state >= MATCH_STATE_COUNTDOWN && match.state <= MATCH_STATE_PLAYTIME )
	{
		if( GS_Gametype_IsTeamBased( game.gametype ) )
		{
			for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
				G_Teams_UnLockTeam( team );
		}
		else
		{
			G_Teams_UnLockTeam( TEAM_PLAYERS );
		}
		G_PrintMsg( NULL, "Teams unlocked\n" );
	}
	else
	{
		G_PrintMsg( NULL, "Teams will no longer be locked when the match starts\n" );
	}
}

//====================
// 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_ALPHA; team < TEAM_ALPHA + 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 );
	ent->r.client->pers.queueTimeStamp = 0;
}


//====================
// 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 disconnected along the callvote time
		return;

	trap_DropClient( ent, DROP_TYPE_NORECONNECT, "Kicked" );
}


//====================
// kickban
//====================

static void G_VoteKickBanExtraHelp( 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_VoteKickBanValidate( callvotedata_t *vote, qboolean first )
{
	int who = -1;

	if( !filterban->integer )
	{
		G_PrintMsg( vote->caller, "%sFilterban is disabled on this server\n", S_COLOR_RED );
		return qfalse;
	}

	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_VoteKickBanPassed( 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 disconnected along the callvote time
		return;

	trap_AddCommandString( va( "addip %s %i\n", ent->r.client->pers.ip, 15 ) );
	trap_DropClient( ent, DROP_TYPE_NORECONNECT, "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;
}

//====================
// unmute
//====================

static void G_VoteUnmuteExtraHelp( 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_VoteUnmuteValidate( 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 unmute
static void G_VoteUnmutePassed( 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 unmute
static void G_VoteVUnmutePassed( 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] );

	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( numbots < 0 )
	{
		if( first ) G_PrintMsg( vote->caller, "%sNegative number of bots is not allowed\n", S_COLOR_RED );
		return qfalse;
	}

	if( numbots > game.maxclients )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sNumber of bots can't be higher than the number of client spots (%i)\n",
			            S_COLOR_RED, game.maxclients );
		}
		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 )
{
	if( !Q_stricmp( vote->argv[0], "unlimited" ) )
	{
		trap_Cvar_Set( "g_maxtimeouts", va( "%i", -1 ) );
	}
	else
	{
		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";
}

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

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

	if( allow_uneven && g_teams_allow_uneven->integer )
	{
		if( first ) G_PrintMsg( vote->caller, "%sUneven teams is already allowed.\n", S_COLOR_RED );
		return qfalse;
	}

	if( !allow_uneven && !g_teams_allow_uneven->integer )
	{
		if( first ) G_PrintMsg( vote->caller, "%sUneven teams is already disallowed\n", S_COLOR_RED );
		return qfalse;
	}

	return qtrue;
}

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

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

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

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

	if( health < 1 )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sThe minimum health value can't be less than 1\n", S_COLOR_RED );
		}
		return qfalse;
	}
	if( health > 200 )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sThe maximum health value can't be higher than 200\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

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

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

//====================
// ca_armor
//====================
static qboolean G_VoteCAArmorValidate( callvotedata_t *vote, qboolean first )
{
	int armor = atoi( vote->argv[0] );

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

	if( armor < 0 )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sThe minimum armor value can't be less than 0\n", S_COLOR_RED );
		}
		return qfalse;
	}
	if( armor > 200 )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sThe maximum armor value can't be higher than 200\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

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

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

//====================
// ca_weapons
//====================
static qboolean G_VoteCAWeaponsValidate( callvotedata_t *vote, qboolean first )
{
	char *weaponinfo = vote->argv[0];
	int normal, grunt, camper, spammer;

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

	normal = CA_WEAPONFLAG_ALL;
	grunt = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_RL | CA_WEAPONFLAG_STRONG_RG; // (all of weak) + GB + RL + RG
	camper = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_EB | CA_WEAPONFLAG_STRONG_GL; // (all of weak) + GB + EB + GL
	spammer = CA_WEAPONFLAG_WEAK_ALL | CA_WEAPONFLAG_STRONG_GB | CA_WEAPONFLAG_STRONG_LG | CA_WEAPONFLAG_STRONG_PG; // (all of weak) + GB + LG + PG

	if( !G_Gametype_CA_SetWeaponFlag( weaponinfo, &normal, &grunt, &camper, &spammer ) )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sInvalid weaponinfo format\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

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

static char *G_VoteCAWeaponsCurrent( void )
{
	return g_ca_weapons->string;
}

//====================
// ca_weak_ammo
//====================
static qboolean G_VoteCAWeakAmmoValidate( callvotedata_t *vote, qboolean first )
{
	char *ammoinfo = vote->argv[0];
	int gb, rg, gl, rl, pg, lg, eb;

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

	gb = rg = gl = rl = pg = lg = eb = 0;
	if( !G_Gametype_CA_SetAmmo( ammoinfo, &gb, &rg, &gl, &rl, &pg, &lg, &eb ) )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sInvalid ammoinfo format\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

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

static char *G_VoteCAWeakAmmoCurrent( void )
{
	return g_ca_weak_ammo->string;
}

//====================
// ca_strong_ammo
//====================
static qboolean G_VoteCAStrongAmmoValidate( callvotedata_t *vote, qboolean first )
{
	char *ammoinfo = vote->argv[0];
	int gb, rg, gl, rl, pg, lg, eb;

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

	gb = rg = gl = rl = pg = lg = eb = 0;
	if( !G_Gametype_CA_SetAmmo( ammoinfo, &gb, &rg, &gl, &rl, &pg, &lg, &eb ) )
	{
		if( first )
		{
			G_PrintMsg( vote->caller, "%sInvalid ammoinfo format\n", S_COLOR_RED );
		}
		return qfalse;
	}

	return qtrue;
}

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

static char *G_VoteCAStrongAmmoCurrent( void )
{
	return g_ca_strong_ammo->string;
}

#endif // WSW_RELEASE


//====================
// ca_roundlimit
//====================
static qboolean G_VoteCARoundlimitValidate( callvotedata_t *vote, qboolean first )
{
	int roundlimit = atoi( vote->argv[0] );

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

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

	return qtrue;
}

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

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

//====================
// ca_allow_selfdamage
//====================
static qboolean G_VoteCAAllowSelfDamageValidate( callvotedata_t *vote, qboolean first )
{
	int selfdamage = atoi( vote->argv[0] );

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

	if( selfdamage != 0 && selfdamage != 1 && selfdamage != 2 )
		return qfalse;

	if( ( selfdamage == 1 && g_ca_allow_selfdamage->integer == 1 ) ||
	   ( selfdamage == 2 && g_ca_allow_selfdamage->integer == 2 ) )
	{
		if( first ) G_PrintMsg( vote->caller, "%sSelf damage is already allowed\n", S_COLOR_RED );
		return qfalse;
	}

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

	return qtrue;
}

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

static char *G_VoteCAAllowSelfDamageCurrent( void )
{
	if( g_ca_allow_selfdamage->integer == 1 )
		return "1";
	else if( g_ca_allow_selfdamage->integer == 2 )
		return "2";
	else
		return "0";
}

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

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

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

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

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

	return qtrue;
}

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

static char *G_VoteCAAllowTeamDamageCurrent( void )
{
	if( g_ca_allow_teamdamage->integer == 1 )
		return "1";
	else if( g_ca_allow_teamdamage->integer == 2 )
		return "2";
	else
		return "0";
}

//====================
// ca_competitionmode
//====================
static qboolean G_VoteCACompetitionModeValidate( callvotedata_t *vote, qboolean first )
{
	int competitionmode = atoi( vote->argv[0] );

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

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

	if( competitionmode == 1 && g_ca_competitionmode->integer == 1 )
	{
		if( first ) G_PrintMsg( vote->caller, "%scompetitionmode is already enabled\n", S_COLOR_RED );
		return qfalse;
	}

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

	return qtrue;
}

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

static char *G_VoteCACompetitionModeCurrent( void )
{
	if( g_ca_competitionmode->integer == 1 )
		return "1";
	else
		return "0";
}

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

	if( game.gametype != GAMETYPE_CA )
	{
		if( first ) G_PrintMsg( vote->caller, "%sThis feature is available only for CA gametype\n", S_COLOR_RED );
		return qfalse;
	}

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

	if( classmode == 1 && g_ca_classmode->integer == 1 )
	{
		if( first ) G_PrintMsg( vote->caller, "%sclassmode is already enabled\n", S_COLOR_RED );
		return qfalse;
	}

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

	return qtrue;
}

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

static char *G_VoteCAClassModeCurrent( void )
{
	if( g_ca_classmode->integer == 1 )
		return "1";
	else
		return "0";
}

//====================
// deadbody_filter
//====================
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
