/*
   Copyright (C) 1997-2001 Id Software, Inc.

   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_callvotes.h"

//==================
//G_Teleport
//
//Teleports client to specified position
//If client is not spectator teleporting is only done if position is free and teleport effects are drawn.
//==================
static qboolean G_Teleport( edict_t *ent, vec3_t origin, vec3_t angles )
{
	int i;
	edict_t	*event;

	if( !ent->r.inuse || !ent->r.client )
		return qfalse;

	if( ent->r.client->ps.pmove.pm_type != PM_SPECTATOR )
	{
		trace_t	tr;

		G_Trace( &tr, origin, ent->r.mins, ent->r.maxs, origin, ent, MASK_PLAYERSOLID );
		if( tr.fraction != 1.0f || tr.startsolid )
			return qfalse;

		event = G_SpawnEvent( EV_PLAYER_TELEPORT_OUT, 0, ent->s.origin );
		event->r.svflags = SVF_NOORIGIN2;
		event->s.ownerNum = ENTNUM( ent );
	}

	VectorCopy( origin, ent->s.origin );
	VectorCopy( origin, ent->s.old_origin );
	VectorCopy( origin, ent->olds.origin );
	ent->s.teleported = qtrue;

	VectorClear( ent->velocity );
	ent->r.client->ps.pmove.pm_time = 1;
	ent->r.client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;

	if( ent->r.client->ps.pmove.pm_type != PM_SPECTATOR )
	{
		event = G_SpawnEvent( EV_PLAYER_TELEPORT_IN, 0, ent->s.origin );
		event->r.svflags = SVF_NOORIGIN2;
		event->s.ownerNum = ENTNUM( ent );
	}

	// set angles
	VectorCopy( angles, ent->s.angles );
	VectorCopy( angles, ent->r.client->ps.viewangles );

	// set the delta angle
	for( i = 0; i < 3; i++ )
		ent->r.client->ps.pmove.delta_angles[i] = ANGLE2SHORT( ent->s.angles[i] ) - ent->r.client->pers.cmd_angles[i];

	return qtrue;
}

static void SelectNextItem( edict_t *ent, int itflags )
{
	gclient_t *cl;
	int tag, first;

	cl = ent->r.client;

	if( cl->selected_item < 0 )
		cl->selected_item = 0;

	tag = cl->selected_item;
	tag++;
	if( tag >= game.numItems || tag <= 0 ) tag = 1;
	first = tag;
	while( tag != first )
	{
		if( game.items[tag] )
		{
			if( ( game.items[tag]->flags & ITFLAG_USABLE ) && ( game.items[tag]->type & itflags )
			   && cl->inventory[tag] )
			{
				cl->selected_item = tag;
				return;
			}
		}
		tag++;
		if( tag >= game.numItems )
			tag = 1;
	}

	cl->selected_item = -1;
}

static void SelectPrevItem( edict_t *ent, int itflags )
{
	gclient_t *cl;
	int tag, first;

	cl = ent->r.client;

	tag = cl->selected_item;
	tag--;
	if( tag <= 0 || tag >= game.numItems ) tag = game.numItems - 1;
	first = tag;
	while( tag != first )
	{
		if( game.items[tag] )
		{
			if( ( game.items[tag]->flags & ITFLAG_USABLE ) && ( game.items[tag]->type & itflags )
			   && cl->inventory[tag] )
			{
				cl->selected_item = tag;
				return;
			}
		}
		tag--;
		if( tag <= 0 )
			tag = game.numItems - 1;
	}

	cl->selected_item = -1;
}

void ValidateSelectedItem( edict_t *ent )
{
	gclient_t *cl;

	cl = ent->r.client;

	if( cl->selected_item >= 0 )
		if( cl->inventory[cl->selected_item] )
			return; // valid

	SelectNextItem( ent, -1 );
}


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

//==================
//Cmd_Give_f
//
//Give items to a client
//==================
static void Cmd_Give_f( edict_t *ent )
{
	char *name;
	gitem_t	*it;
	int i;
	qboolean give_all;

	if( !sv_cheats->integer )
	{
		G_PrintMsg( ent, "Cheats are not enabled on this server.\n" );
		return;
	}

	name = trap_Cmd_Args();

	if( !Q_stricmp( name, "all" ) )
		give_all = qtrue;
	else
		give_all = qfalse;

	if( give_all || !Q_stricmp( trap_Cmd_Argv( 1 ), "health" ) )
	{
		if( trap_Cmd_Argc() == 3 )
			ent->health = atoi( trap_Cmd_Argv( 2 ) );
		else
			ent->health = ent->max_health;
		if( !give_all )
			return;
	}

	if( give_all || !Q_stricmp( name, "weapons" ) )
	{
		for( i = 0; i < game.numItems; i++ )
		{
			it = game.items[i];
			if( !it )
				continue;

			if( !( it->flags & ITFLAG_PICKABLE ) )
				continue;

			if( !( it->type & IT_WEAPON ) )
				continue;

			ent->r.client->inventory[i] += 1;
		}
		if( !give_all )
			return;
	}

	if( give_all || !Q_stricmp( name, "ammo" ) )
	{
		for( i = 0; i < game.numItems; i++ )
		{
			it = game.items[i];
			if( !it )
				continue;

			if( !( it->flags & ITFLAG_PICKABLE ) )
				continue;

			if( !( it->type & IT_AMMO ) )
				continue;

			Add_Ammo( ent, it, 1000, qtrue );
		}
		if( !give_all )
			return;
	}

	if( give_all || !Q_stricmp( name, "armor" ) )
	{
		gitem_armor_t *info;

		info = (gitem_armor_t *)game.items[ARMOR_RA]->info;

		ent->r.client->armor = info->max_count;
		ent->r.client->armortag = ARMOR_RA;

		if( !give_all )
			return;
	}

	if( give_all )
	{
		for( i = 0; i < game.numItems; i++ )
		{
			it = game.items[i];
			if( !it )
				continue;

			if( !( it->flags & ITFLAG_PICKABLE ) )
				continue;

			if( it->type & ( IT_ARMOR|IT_WEAPON|IT_AMMO|IT_FLAG ) )
				continue;

			ent->r.client->inventory[i] = 1;
		}
		return;
	}

	it = GS_FindItemByName( name );
	if( !it )
	{
		name = trap_Cmd_Argv( 1 );
		it = GS_FindItemByName( name );
		if( !it )
		{
			G_PrintMsg( ent, "unknown item\n" );
			return;
		}
	}

	if( !( it->flags & ITFLAG_PICKABLE ) || ( it->type & IT_FLAG ) )
	{
		G_PrintMsg( ent, "non-pickup (givable) item\n" );
		return;
	}

	if( it->type & IT_AMMO )
	{
		if( trap_Cmd_Argc() == 3 )
			ent->r.client->inventory[it->tag] = atoi( trap_Cmd_Argv( 2 ) );
		else
			ent->r.client->inventory[it->tag] += it->quantity;
	}
	else
	{
		if( it->tag && ( it->tag > 0 ) && ( it->tag < game.numItems ) )
		{
			if( game.items[it->tag] != NULL )
				ent->r.client->inventory[it->tag]++;
		}
		else
			G_PrintMsg( ent, "non-pickup (givable) item\n" );
	}
}

//==================
//Cmd_God_f
//Sets client to godmode
//argv(0) god
//==================
static void Cmd_God_f( edict_t *ent )
{
	char *msg;

	if( !sv_cheats->integer )
	{
		G_PrintMsg( ent, "Cheats are not enabled on this server.\n" );
		return;
	}

	ent->flags ^= FL_GODMODE;
	if( !( ent->flags & FL_GODMODE ) )
		msg = "godmode OFF\n";
	else
		msg = "godmode ON\n";

	G_PrintMsg( ent, msg );
}

//==================
//Cmd_Noclip_f
//
//argv(0) noclip
//==================
static void Cmd_Noclip_f( edict_t *ent )
{
	char *msg;

	if( !sv_cheats->integer )
	{
		G_PrintMsg( ent, "Cheats are not enabled on this server.\n" );
		return;
	}

	if( ent->movetype == MOVETYPE_NOCLIP )
	{
		ent->movetype = MOVETYPE_PLAYER;
		msg = "noclip OFF\n";
	}
	else
	{
		ent->movetype = MOVETYPE_NOCLIP;
		msg = "noclip ON\n";
	}

	G_PrintMsg( ent, msg );
}

//==================
//Cmd_Use_f
//Use an inventory item
//==================
static void Cmd_Use_f( edict_t *ent )
{
	gitem_t	*it;
	char *s;

	s = trap_Cmd_Args();
	it = GS_FindItemByName( s );
	if( !it )
	{
		G_PrintMsg( ent, "unknown item: %s\n", s );
		return;
	}

	if( !( it->flags & ITFLAG_USABLE ) )
	{
		G_PrintMsg( ent, "Item is not usable.\n" );
		return;
	}

	if( !ent->r.client->inventory[it->tag] )
	{
		return;
	}

	G_UseItem( ent, it );
}

//==================
//Cmd_DropItem
//Drop an inventory item
//(helper function for Cmd_Drop_f)
//==================
static void Cmd_DropItem( edict_t *ent, char *item )
{
	gitem_t	*it;
	int i;

	// "weapon" acts as a shortcut for current weapon
	if( !Q_stricmp( item, "weapon" ) )
	{
		if( !ent->s.weapon )
		{
			G_PrintMsg( ent, "No weapon to drop\n", item );
			return;
		}
		it = game.items[ent->s.weapon];
	}
	// "strong" acts as a shortcut for the current strong ammo
	else if( !Q_stricmp( item, "strong" ) )
	{
		if( !ent->s.weapon )
		{
			G_PrintMsg( ent, "No ammo to drop\n", item );
			return;
		}
		it = game.items[game.items[ent->s.weapon]->ammo_tag];
	}
	// "fullweapon" acts as a shortcut for the weapon with all ammo
	else if( !Q_stricmp( item, "fullweapon" ) )
	{
		it = game.items[ent->s.weapon];
		while( ent->r.client->inventory[it->ammo_tag] )
		{
			G_DropItem( ent, game.items[it->ammo_tag] );
		}
		if( !ent->s.weapon )
		{
			G_PrintMsg( ent, "No weapon to drop\n", item );
			return;
		}
	}
	// "discard" acts as a shortcut for the ammo without a weapon
	else if( !Q_stricmp( item, "discard" ) )
	{
		for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ )
		{
			it = GS_FindItemByTag( i );
			if( !ent->r.client->inventory[i] )
			{
				while( ent->r.client->inventory[it->ammo_tag] )
				{
					G_DropItem( ent, game.items[it->ammo_tag] );
				}
			}
		}
		return;
	}
	else if( !Q_stricmp( item, "flag" ) )
	{
		if( ent->s.team == TEAM_BETA )
			it = G_Gametype_CTF_FlagItem( TEAM_ALPHA );
		else if( ent->s.team == TEAM_ALPHA )
			it = G_Gametype_CTF_FlagItem( TEAM_BETA );
		else
			it = GS_FindItemByName( item );
	}
	else
	{
		it = GS_FindItemByName( item );
	}

	if( !it )
	{
		G_PrintMsg( ent, "unknown item: %s\n", item );
		return;
	}

	if( !( it->flags & ITFLAG_DROPABLE ) )
	{
		G_PrintMsg( ent, "Item is not dropable.\n" );
		return;
	}

	if( !ent->r.client->inventory[it->tag] )
	{
		G_PrintMsg( ent, "Out of item: %s\n", item );
		return;
	}

	G_AddEvent( ent, EV_DROP, 0, qtrue );

	G_DropItem( ent, it );
}

//==================
//Cmd_Drop_f
//Drops inventory items
//==================
static void Cmd_Drop_f( edict_t *ent )
{
	char *item;
	int i = 1;
	while( ( item = trap_Cmd_Argv( i ) ) )
	{
		if( !item[0] ) break;
		Cmd_DropItem( ent, item );
		i++;
	}
}

//=================
//Cmd_InvUse_f
//=================
static void Cmd_InvUse_f( edict_t *ent )
{
	gitem_t	*it;
	gclient_t *cl;

	cl = ent->r.client;

	ValidateSelectedItem( ent );

	if( cl->selected_item == -1 )
	{
		G_PrintMsg( ent, "No item to use.\n" );
		return;
	}

	it = game.items[cl->selected_item];

	if( !( it->flags & ITFLAG_USABLE ) )
	{
		G_PrintMsg( ent, "Item is not usable.\n" );
		return;
	}

	G_UseItem( ent, it );
}

//=================
//Cmd_InvDrop_f
//=================
static void Cmd_InvDrop_f( edict_t *ent )
{
	gclient_t *cl;
	gitem_t	*it;

	cl = ent->r.client;

	ValidateSelectedItem( ent );

	if( cl->selected_item == -1 )
	{
		G_PrintMsg( ent, "No item to drop.\n" );
		return;
	}

	it = game.items[cl->selected_item];
	if( !( it->flags & ITFLAG_DROPABLE ) )
	{
		G_PrintMsg( ent, "Item is not dropable.\n" );
		return;
	}

	G_AddEvent( ent, EV_DROP, 0, qtrue );

	G_DropItem( ent, it );
}

//=================
//Cmd_Kill_f
//=================
static void Cmd_Kill_f( edict_t *ent )
{
	if( ent->r.solid == SOLID_NOT )
		return;

	// can suicide after 5 seconds
	if( level.time < ent->r.client->respawn_timestamp + ( game.gametype == GAMETYPE_RACE ? 1000 : 5000 ) )
		return;

	ent->flags &= ~FL_GODMODE;
	ent->health = 0;
	meansOfDeath = MOD_SUICIDE;

	// wsw : pb : fix /kill command
	Killed( ent, ent, ent, 100000, vec3_origin, MOD_SUICIDE );
}

void Cmd_ChaseNext_f( edict_t *ent )
{
	G_ChaseStep( ent, 1 );
}

void Cmd_ChasePrev_f( edict_t *ent )
{
	G_ChaseStep( ent, -1 );
}

//=================
//Cmd_PutAway_f
//=================
static void Cmd_PutAway_f( edict_t *ent )
{
	ent->r.client->showscores = qfalse;
}

//==================
//Cmd_Score_f
//==================
static void Cmd_Score_f( edict_t *ent )
{
	qboolean newvalue;

	if( trap_Cmd_Argc() == 2 )
	{
		newvalue = ( atoi( trap_Cmd_Argv( 1 ) ) != 0 );
	}
	else
	{
		newvalue = !ent->r.client->showscores;
	}

	ent->r.client->showscores = newvalue;
}

//==================
//Cmd_GameOperator_f
//==================
static void Cmd_GameOperator_f( edict_t *ent )
{
	if( !g_operator_password->string[0] )
	{
		G_PrintMsg( ent, "Operator is disabled in this server\n" );
		return;
	}

	if( trap_Cmd_Argc() < 2 )
	{
		G_PrintMsg( ent, "Usage: 'operator <password>' or 'op <password>'\n" );
		return;
	}

	if( !Q_stricmp( trap_Cmd_Argv( 1 ), g_operator_password->string ) )
	{
		if( !ent->r.client->pers.isoperator )
			G_PrintMsg( NULL, "%s"S_COLOR_WHITE" is now a game operator\n", ent->r.client->pers.netname );

		ent->r.client->pers.isoperator = qtrue;
		return;
	}

	G_PrintMsg( ent, "Incorrect operator password.\n" );
}

//==================
//Cmd_Position_f
//==================
static void Cmd_Position_f( edict_t *ent )
{
	char *action;

	if( !sv_cheats->integer && match.state != MATCH_STATE_WARMUP && ent->r.client->ps.pmove.pm_type != PM_SPECTATOR )
	{
		G_PrintMsg( ent, "Position command is only available in warmup and in spectator mode.\n" );
		return;
	}

	// flood protect
	if( ent->r.client->teamchange.position_lastcmd + 500 > game.realtime )
		return;
	ent->r.client->teamchange.position_lastcmd = game.realtime;

	action = trap_Cmd_Argv( 1 );

	if( !Q_stricmp( action, "save" ) )
	{
		ent->r.client->teamchange.position_saved = qtrue;
		VectorCopy( ent->s.origin, ent->r.client->teamchange.position_origin );
		VectorCopy( ent->s.angles, ent->r.client->teamchange.position_angles );
		G_PrintMsg( ent, "Position saved.\n" );
	}
	else if( !Q_stricmp( action, "load" ) )
	{
		if( !ent->r.client->teamchange.position_saved )
		{
			G_PrintMsg( ent, "No position saved.\n" );
		}
		else
		{
			if( ent->r.client->chase.active )
				G_SpectatorMode( ent );

			if( G_Teleport( ent, ent->r.client->teamchange.position_origin, ent->r.client->teamchange.position_angles ) )
				G_PrintMsg( ent, "Position loaded.\n" );
			else
				G_PrintMsg( ent, "Position not available.\n" );
		}
	}
	else if( !Q_stricmp( action, "set" ) && trap_Cmd_Argc() == 7 )
	{
		vec3_t origin, angles;
		int i, argnumber = 2;

		for( i = 0; i < 3; i++ )
			origin[i] = atof( trap_Cmd_Argv( argnumber++ ) );
		for( i = 0; i < 2; i++ )
			angles[i] = atof( trap_Cmd_Argv( argnumber++ ) );
		angles[2] = 0;

		if( ent->r.client->chase.active )
			G_SpectatorMode( ent );

		if( G_Teleport( ent, origin, angles ) )
			G_PrintMsg( ent, "Position not available.\n" );
		else
			G_PrintMsg( ent, "Position set.\n" );
	}
	else
	{
		char msg[MAX_STRING_CHARS];

		msg[0] = 0;
		Q_strncatz( msg, "Usage:\nposition save - Save current position\n", sizeof( msg ) );
		Q_strncatz( msg, "position load - Teleport to saved position\n", sizeof( msg ) );
		Q_strncatz( msg, "position set <x> <y> <z> <pitch> <yaw> - Teleport to specified position\n", sizeof( msg ) );
		Q_strncatz( msg, va( "Current position: %.4f %.4f %.4f %.4f %.4f\n", ent->s.origin[0], ent->s.origin[1],
		                     ent->s.origin[2], ent->s.angles[0], ent->s.angles[1] ), sizeof( msg ) );
		G_PrintMsg( ent, msg );
	}
}

//=================
//Cmd_Players_f
//=================
static void Cmd_Players_f( edict_t *ent )
{
	int i;
	int count = 0;
	char line[64];
	char msg[1024];

	// print information
	msg[0] = 0;
	Q_strncatz( msg, "num name\n", sizeof( msg ) );
	Q_strncatz( msg, "--- ---------------\n", sizeof( msg ) );

	for( i = 0; i < game.maxclients; i++ )
	{
		if( game.clients[i].pers.connected )
		{
			Q_snprintfz( line, sizeof( line ), "%3i %s%s\n", i, game.clients[i].pers.netname,
				game.clients[i].pers.isoperator ? " op" : "" );
			if( strlen( line ) + strlen( msg ) > sizeof( msg ) - 100 )
			{ // can't print all of them in one packet
				Q_strncatz( msg, "...\n", sizeof( msg ) );
				break;
			}
			Q_strncatz( msg, line, sizeof( msg ) );
			count++;
		}
	}

	Q_strncatz( msg, "--- ---------------\n", sizeof( msg ) );
	Q_strncatz( msg, va( "%3i players\n", count ), sizeof( msg ) );
	G_PrintMsg( ent, msg );
}

qboolean CheckFlood( edict_t *ent, qboolean teamonly )
{
	int i;
	gclient_t *client = ent->r.client;

	assert( ent && client );

	if( g_floodprotection_messages->modified )
	{
		if( g_floodprotection_messages->integer < 0 )
			trap_Cvar_Set( "g_floodprotection_messages", "0" );
		if( g_floodprotection_messages->integer > MAX_FLOOD_MESSAGES )
			trap_Cvar_Set( "g_floodprotection_messages", va( "%i", MAX_FLOOD_MESSAGES ) );
		g_floodprotection_messages->modified = qfalse;
	}

	if( g_floodprotection_team->modified )
	{
		if( g_floodprotection_team->integer < 0 )
			trap_Cvar_Set( "g_floodprotection_team", "0" );
		if( g_floodprotection_team->integer > MAX_FLOOD_MESSAGES )
			trap_Cvar_Set( "g_floodprotection_team", va( "%i", MAX_FLOOD_MESSAGES ) );
		g_floodprotection_team->modified = qfalse;
	}

	if( g_floodprotection_seconds->modified )
	{
		if( g_floodprotection_seconds->value <= 0 )
			trap_Cvar_Set( "g_floodprotection_seconds", "4" );
		g_floodprotection_seconds->modified = qfalse;
	}

	if( g_floodprotection_penalty->modified )
	{
		if( g_floodprotection_penalty->value < 0 )
			trap_Cvar_Set( "g_floodprotection_penalty", "10" );
		g_floodprotection_penalty->modified = qfalse;
	}

	// old protection still active
	if( !teamonly || g_floodprotection_team->integer )
	{
		if( game.realtime < client->teamchange.flood_locktill )
		{
			G_PrintMsg( ent, "You can't talk for %d more seconds\n",
			            (int)( ( client->teamchange.flood_locktill - game.realtime ) / 1000.0f ) + 1 );
			return qtrue;
		}
	}


	if( teamonly )
	{
		if( g_floodprotection_team->integer && g_floodprotection_penalty->value > 0 )
		{
			i = client->teamchange.flood_team_whenhead - g_floodprotection_team->integer + 1;
			if( i < 0 )
				i = MAX_FLOOD_MESSAGES + i;

			if( client->teamchange.flood_team_when[i] && client->teamchange.flood_team_when[i] <= game.realtime &&
			   ( game.realtime - client->teamchange.flood_team_when[i] < g_floodprotection_seconds->integer * 1000 ) )
			{
				client->teamchange.flood_locktill = game.realtime + g_floodprotection_penalty->value * 1000;
				G_PrintMsg( ent, "Flood protection: You can't talk for %d seconds.\n", g_floodprotection_penalty->integer );
				return qtrue;
			}
		}

		client->teamchange.flood_team_whenhead = ( client->teamchange.flood_team_whenhead + 1 ) % MAX_FLOOD_MESSAGES;
		client->teamchange.flood_team_when[client->teamchange.flood_team_whenhead] = game.realtime;
	}
	else
	{
		if( g_floodprotection_messages->integer && g_floodprotection_penalty->value > 0 )
		{
			i = client->teamchange.flood_whenhead - g_floodprotection_messages->integer + 1;
			if( i < 0 )
				i = MAX_FLOOD_MESSAGES + i;

			if( client->teamchange.flood_when[i] && client->teamchange.flood_when[i] <= game.realtime &&
			   ( game.realtime - client->teamchange.flood_when[i] < g_floodprotection_seconds->integer * 1000 ) )
			{
				client->teamchange.flood_locktill = game.realtime + g_floodprotection_penalty->value * 1000;
				G_PrintMsg( ent, "Flood protection: You can't talk for %d seconds.\n", g_floodprotection_penalty->integer );
				return qtrue;
			}
		}

		client->teamchange.flood_whenhead = ( client->teamchange.flood_whenhead + 1 ) % MAX_FLOOD_MESSAGES;
		client->teamchange.flood_when[client->teamchange.flood_whenhead] = game.realtime;
	}

	return qfalse;
}

static void Cmd_CoinToss_f( edict_t *ent )
{
	qboolean qtails;
	char *s;
	char upper[MAX_STRING_CHARS];

	if( match.state > MATCH_STATE_WARMUP && !gtimeout.active )
	{
		G_PrintMsg( ent, "You can only toss coins during warmup or timeouts\n" );
		return;
	}
	if( CheckFlood( ent, qfalse ) )
		return;

	if( trap_Cmd_Argc() < 2 || ( Q_stricmp( "heads", trap_Cmd_Argv( 1 ) ) && Q_stricmp( "tails", trap_Cmd_Argv( 1 ) ) ) )
	{
		//it isn't a valid token
		G_PrintMsg( ent, "You have to choose heads or tails when tossing a coin\n" );
		return;
	}

	Q_strncpyz( upper, trap_Cmd_Argv( 1 ), sizeof( upper ) );
	s = upper;
	while( *s )
	{
		*s = toupper( *s );
		s++;
	}

	qtails = ( Q_stricmp( "heads", trap_Cmd_Argv( 1 ) ) != 0 );
	if( qtails == ( rand() & 1 ) )
	{
		G_PrintMsg( NULL, S_COLOR_YELLOW "COINTOSS %s: " S_COLOR_WHITE "It was %s! %s " S_COLOR_WHITE "tossed a coin and " S_COLOR_GREEN "won!\n", upper, trap_Cmd_Argv( 1 ), ent->r.client->pers.netname );
		return;
	}

	G_PrintMsg( NULL, S_COLOR_YELLOW "COINTOSS %s: " S_COLOR_WHITE "It was %s! %s " S_COLOR_WHITE "tossed a coin and " S_COLOR_RED "lost!\n", upper, qtails ? "heads" : "tails", ent->r.client->pers.netname );
}

//==================
//Cmd_Say_f
//==================
void Cmd_Say_f( edict_t *ent, qboolean arg0, qboolean checkflood )
{
	char *p;
	char text[2048];

	if( checkflood )
	{
		if( CheckFlood( ent, qfalse ) )
			return;
	}

	if( ent->r.client && ent->r.client->pers.muted & 1 )
		return;

	if( trap_Cmd_Argc() < 2 && !arg0 )
		return;

	Q_snprintfz( text, sizeof( text ), "%s%s: ", ent->r.client->pers.netname, S_COLOR_GREEN );

	if( arg0 )
	{
		Q_strncatz( text, trap_Cmd_Argv( 0 ), sizeof( text ) );
		Q_strncatz( text, " ", sizeof( text ) );
		Q_strncatz( text, trap_Cmd_Args(), sizeof( text ) );
	}
	else
	{
		p = trap_Cmd_Args();

		if( *p == '"' )
		{
			if( p[strlen( p )-1] == '"' )
				p[strlen( p )-1] = 0;
			p++;
		}
		Q_strncatz( text, p, sizeof( text ) );
	}

	// don't let text be too long for malicious reasons
	if( strlen( text ) > 150 )
		text[150] = 0;

	Q_strncatz( text, "\n", sizeof( text ) );

	G_ChatMsg( NULL, "%s", text );
}

//==================
//Cmd_SayCmd_f
//==================
static void Cmd_SayCmd_f( edict_t *ent )
{
	Cmd_Say_f( ent, qfalse, qtrue );
}

//==================
//Cmd_SayTeam_f
//==================
static void Cmd_SayTeam_f( edict_t *ent )
{
	G_Say_Team( ent, trap_Cmd_Args(), qtrue );
}

typedef struct
{
	char *name;
	int id;
	char *message;
} g_vsays_t;

static g_vsays_t g_vsays[] = {
	{ "needhealth", VSAY_NEEDHEALTH, "Need health!" },
	{ "needweapon", VSAY_NEEDWEAPON, "Need weapon!" },
	{ "needarmor", VSAY_NEEDARMOR, "Need armor!" },
	{ "affirmative", VSAY_AFFIRMATIVE, "Affirmative!" },
	{ "negative", VSAY_NEGATIVE, "Negative!" },
	{ "yes", VSAY_YES, "Yes!" },
	{ "no", VSAY_NO, "No!" },
	{ "ondefense", VSAY_ONDEFENSE, "I'm on defense!" },
	{ "onoffense", VSAY_ONOFFENSE, "I'm on offense!" },
	{ "oops", VSAY_OOPS, "Oops!" },
	{ "sorry", VSAY_SORRY, "Sorry!" },
	{ "thanks", VSAY_THANKS, "Thanks!" },
	{ "noproblem", VSAY_NOPROBLEM, "No problem!" },
	{ "yeehaa", VSAY_YEEHAA, "Yeehaa!" },
	{ "goodgame", VSAY_GOODGAME, "Good game!" },
	{ "defend", VSAY_DEFEND, "Defend!" },
	{ "attack", VSAY_ATTACK, "Attack!" },
	{ "needbackup", VSAY_NEEDBACKUP, "Need backup!" },
	{ "booo", VSAY_BOOO, "Booo!" },
	{ "needdefense", VSAY_NEEDDEFENSE, "Need defense!" },
	{ "needoffense", VSAY_NEEDOFFENSE, "Need offense!" },
	{ "needhelp", VSAY_NEEDHELP, "Need help!" },
	{ "roger", VSAY_ROGER, "Roger!" },
	{ "armorfree", VSAY_ARMORFREE, "Armor free!" },
	{ "areasecured", VSAY_AREASECURED, "Area secured!" },
	{ "shutup", VSAY_SHUTUP, "Shut up!" },
	{ "boomstick", VSAY_BOOMSTICK, "Need a weapon!" },
	{ "gotopowerup", VSAY_GOTOPOWERUP, "Go to main powerup!" },
	{ "gotoquad", VSAY_GOTOQUAD, "Go to quad!" },
	{ "ok", VSAY_OK, "Ok!" },
	{ NULL, 0, NULL }
};

void G_BOTvsay_f( edict_t *ent, char *msg, qboolean team )
{
	edict_t	*event = NULL;
	g_vsays_t *vsay;
	char *text = NULL;

	if( !( ent->r.svflags & SVF_FAKECLIENT ) )
		return;

	if( ent->r.client && ( ent->r.client->pers.muted & 2 ) )
		return;

	//	if( (!GS_Gametype_IsTeamBased(game.gametype) || game.gametype == GAMETYPE_DUEL) && ent->s.team != TEAM_SPECTATOR )
	//		team = qfalse;

	for( vsay = g_vsays; vsay->name; vsay++ )
	{
		if( !Q_stricmp( msg, vsay->name ) )
		{
			event = G_SpawnEvent( EV_VSAY, vsay->id, NULL );
			text = vsay->message;
			break;
		}
	}

	if( event && text )
	{
		event->r.svflags |= SVF_BROADCAST; // force sending even when not in PVS
		event->s.ownerNum = ent->s.number;
		if( team )
		{
			event->s.team = ent->s.team;
			event->r.svflags |= SVF_ONLYTEAM; // send only to clients with the same ->s.team value
		}

		if( team )
			G_Say_Team( ent, va( "(v) %s", text ), qfalse );
		else
			G_ChatMsg( NULL, "%s%s: (v) %s\n", ent->r.client->pers.netname, S_COLOR_GREEN, text );
	}
}

//==================
//G_vsay_f
//==================
static void G_vsay_f( edict_t *ent, qboolean team )
{
	edict_t	*event = NULL;
	g_vsays_t *vsay;
	char *text = NULL;
	char *msg = trap_Cmd_Argv( 1 );

	if( ent->r.client && ent->r.client->pers.muted & 2 )
		return;

	if( ( !GS_Gametype_IsTeamBased( game.gametype ) || game.gametype == GAMETYPE_DUEL ) && ent->s.team != TEAM_SPECTATOR )
		team = qfalse;

	if( !( ent->r.svflags & SVF_FAKECLIENT ) )
	{                                      // ignore flood checks on bots
		if( ent->r.client->last_vsay > game.realtime - 500 )
			return; // ignore silently vsays in that come in rapid succession
		ent->r.client->last_vsay = game.realtime;

		if( CheckFlood( ent, qfalse ) )
			return;
	}

	for( vsay = g_vsays; vsay->name; vsay++ )
	{
		if( !Q_stricmp( msg, vsay->name ) )
		{
			event = G_SpawnEvent( EV_VSAY, vsay->id, NULL );
			text = vsay->message;
			break;
		}
	}

	if( event && text )
	{
		event->r.svflags |= SVF_BROADCAST; // force sending even when not in PVS
		event->s.ownerNum = ent->s.number;
		if( team )
		{
			event->s.team = ent->s.team;
			event->r.svflags |= SVF_ONLYTEAM; // send only to clients with the same ->s.team value
		}

		if( trap_Cmd_Argc() > 2 )
		{
			char saystring[256];
			int i;

			saystring[0] = 0;
			for( i = 2; i < trap_Cmd_Argc(); i++ )
			{
				Q_strncatz( saystring, trap_Cmd_Argv( i ), sizeof( saystring ) );
				Q_strncatz( saystring, " ", sizeof( saystring ) );
			}
			if( team )
				G_Say_Team( ent, va( "(v) %s", saystring ), qfalse );
			else
				G_ChatMsg( NULL, "%s%s: (v) %s\n", ent->r.client->pers.netname, S_COLOR_GREEN, saystring );
		}
		else
		{
			if( team )
				G_Say_Team( ent, va( "(v) %s", text ), qfalse );
			else
				G_ChatMsg( NULL, "%s%s: (v) %s\n", ent->r.client->pers.netname, S_COLOR_GREEN, text );
		}

		return;
	}

	// unknown token, print help
	{
		char string[MAX_STRING_CHARS];

		// print information
		string[0] = 0;
		if( msg && strlen( msg ) > 0 )
			Q_strncatz( string, va( "%sUnknown vsay token%s \"%s\"\n", S_COLOR_YELLOW, S_COLOR_WHITE, msg ), sizeof( string ) );
		Q_strncatz( string, va( "%svsays:%s\n", S_COLOR_YELLOW, S_COLOR_WHITE ), sizeof( string ) );
		for( vsay = g_vsays; vsay->name; vsay++ )
		{
			if( strlen( vsay->name ) + strlen( string ) < sizeof( string ) - 6 )
			{
				Q_strncatz( string, va( "%s ", vsay->name ), sizeof( string ) );
			}
		}
		Q_strncatz( string, "\n", sizeof( string ) );
		G_PrintMsg( ent, string );
	}
}

//==================
//G_vsay_Cmd
//==================
static void G_vsay_Cmd( edict_t *ent )
{
	G_vsay_f( ent, qfalse );
}

//==================
//G_Teams_vsay_Cmd
//==================
static void G_Teams_vsay_Cmd( edict_t *ent )
{
	G_vsay_f( ent, qtrue );
}

//==================
//Cmd_InvNext_f
//==================
static void Cmd_InvNext_f( edict_t *ent )
{
	SelectNextItem( ent, -1 );
}

//==================
//Cmd_InvPrev_f
//==================
static void Cmd_InvPrev_f( edict_t *ent )
{
	SelectPrevItem( ent, -1 );
}

//==================
//Cmd_InvNextWeap_f
//==================
static void Cmd_InvNextWeap_f( edict_t *ent )
{
	SelectNextItem( ent, IT_WEAPON );
}

//==================
//Cmd_InvPrevWeap_f
//==================
static void Cmd_InvPrevWeap_f( edict_t *ent )
{
	SelectPrevItem( ent, IT_WEAPON );
}

//==================
//Cmd_Join_f
//==================
static void Cmd_Join_f( edict_t *ent )
{
	if( CheckFlood( ent, qfalse ) )
		return;

	G_Teams_Join_Cmd( ent );
}

//====================
//Cmd_Timeout_f
//====================
static void Cmd_Timeout_f( edict_t *ent )
{
	int num;

	if( ent->s.team == TEAM_SPECTATOR || match.state != MATCH_STATE_PLAYTIME )
		return;

	if( GS_Gametype_IsTeamBased( game.gametype ) )
		num = ent->s.team;
	else
		num = ENTNUM( ent )-1;

	if( gtimeout.active && ( gtimeout.endtime - gtimeout.time ) >= 2*TIMEIN_TIME )
	{
		G_PrintMsg( ent, "Timeout already in progress\n" );
		return;
	}

	if( g_maxtimeouts->integer != -1 && gtimeout.used[num] >= g_maxtimeouts->integer )
	{
		if( g_maxtimeouts->integer == 0 )
			G_PrintMsg( ent, "Timeouts are not allowed on this server\n" );
		else if( GS_Gametype_IsTeamBased( game.gametype ) )
			G_PrintMsg( ent, "Your team doesn't have any timeouts left\n" );
		else
			G_PrintMsg( ent, "You don't have any timeouts left\n" );
		return;
	}

	G_PrintMsg( NULL, "%s%s called a timeout\n", ent->r.client->pers.netname, S_COLOR_WHITE );
	if( !gtimeout.active )
		G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_TIMEOUT_TIMEOUT_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );

	gtimeout.used[num]++;
	gtimeout.active = qtrue;
	gtimeout.caller = num;
	gtimeout.endtime = gtimeout.time + TIMEOUT_TIME + FRAMETIME;
}

//====================
//Cmd_Timeout_f
//====================
static void Cmd_Timein_f( edict_t *ent )
{
	int num;

	if( ent->s.team == TEAM_SPECTATOR )
		return;

	if( !gtimeout.active )
	{
		G_PrintMsg( ent, "No timeout in progress.\n" );
		return;
	}

	if( gtimeout.endtime - gtimeout.time <= 2 * TIMEIN_TIME )
	{
		G_PrintMsg( ent, "The timeout is about to end already.\n" );
		return;
	}

	if( GS_Gametype_IsTeamBased( game.gametype ) )
		num = ent->s.team;
	else
		num = ENTNUM( ent )-1;

	if( gtimeout.caller != num )
	{
		if( GS_Gametype_IsTeamBased( game.gametype ) )
			G_PrintMsg( ent, "Your team didn't call this timeout.\n" );
		else
			G_PrintMsg( ent, "You didn't call this timeout.\n" );
		return;
	}

	gtimeout.endtime = gtimeout.time + TIMEIN_TIME + FRAMETIME;

	G_AnnouncerSound( NULL, trap_SoundIndex( va( S_ANNOUNCER_TIMEOUT_TIMEIN_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, qtrue );
	G_PrintMsg( NULL, "%s%s called a timein\n", ent->r.client->pers.netname, S_COLOR_WHITE );
}

//====================
//G_StatsMessage
//
// Generates stats message for the entity
// The returned string must be freed by the caller using G_Free
// Note: This string must never contain " characters
//====================
char *G_StatsMessage( edict_t *ent )
{
	gclient_t *client;
	gitem_t *item;
	int i, shot_weak, hit_weak, shot_strong, hit_strong, shot_total, hit_total;
	static char entry[MAX_TOKEN_CHARS];

	assert( ent && ent->r.client );
	client = ent->r.client;

	// message header
	Q_snprintfz( entry, sizeof( entry ), "%d", PLAYERNUM( ent ) );

	for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ )
	{
		if( i == WEAP_SHOCKWAVE )
			continue;

		item = GS_FindItemByTag( i );
		assert( item );

		hit_weak = hit_strong = 0;
		shot_weak = shot_strong = 0;

		if( item->weakammo_tag != AMMO_NONE )
		{
			hit_weak = client->resp.accuracy_hits[item->weakammo_tag - AMMO_CELLS];
			shot_weak = client->resp.accuracy_shots[item->weakammo_tag - AMMO_CELLS];
		}

		if( item->ammo_tag != AMMO_NONE )
		{
			hit_strong = client->resp.accuracy_hits[item->ammo_tag - AMMO_CELLS];
			shot_strong = client->resp.accuracy_shots[item->ammo_tag - AMMO_CELLS];
		}

		hit_total = hit_weak + hit_strong;
		shot_total = shot_weak + shot_strong;

		Q_strncatz( entry, va( " %d", shot_total ), sizeof( entry ) );
		if( shot_total < 1 )
			continue;
		Q_strncatz( entry, va( " %d", hit_total ), sizeof( entry ) );

		// strong
		Q_strncatz( entry, va( " %d", shot_strong ), sizeof( entry ) );
		if( shot_strong != shot_total )
			Q_strncatz( entry, va( " %d", hit_strong ), sizeof( entry ) );
	}

	Q_strncatz( entry, va( " %d %d", client->resp.total_damage_given, client->resp.total_damage_received ), sizeof( entry ) );
	Q_strncatz( entry, va( " %d %d", client->resp.health_taken, client->resp.armor_taken ), sizeof( entry ) );

	// add enclosing quote
	Q_strncatz( entry, "\"", sizeof( entry ) );

	return entry;
}

//=================
//Cmd_ShowStats_f
//=================
static void Cmd_ShowStats_f( edict_t *ent )
{
	edict_t *target;

	if( trap_Cmd_Argc() > 2 )
	{
		G_PrintMsg( ent, "Usage: stats [player]\n" );
		return;
	}

	if( trap_Cmd_Argc() == 2 )
	{
		target = G_PlayerForText( trap_Cmd_Argv( 1 ) );
		if( target == NULL )
		{
			G_PrintMsg( ent, "No such player\n" );
			return;
		}
	}
	else
	{
		if( ent->r.client->chase.active && game.edicts[ent->r.client->chase.target].r.client )
			target = &game.edicts[ent->r.client->chase.target];
		else
			target = ent;
	}

	if( target->s.team == TEAM_SPECTATOR )
	{
		G_PrintMsg( ent, "No stats for spectators\n" );
		return;
	}

	trap_GameCmd( ent, va( "plstats 1 \"%s\"", G_StatsMessage( target ) ) );
}

//===========================================================
//	client commands
//===========================================================
typedef struct
{
	char name[MAX_QPATH];
	void ( *func )( edict_t *ent );
} g_gamecommands_t;

g_gamecommands_t g_Commands[MAX_GAMECOMMANDS];

// FIXME
void Cmd_ShowPLinks_f( edict_t *ent );
void Cmd_deleteClosestNode_f( edict_t *ent );

//==================
//G_AddCommand
//==================
void G_AddCommand( char *name, void *callback )
{
	int i;
	char temp[MAX_QPATH];

	Q_strncpyz( temp, name, sizeof( temp ) );

	// see if we already had it in game side
	for( i = 0; i < MAX_GAMECOMMANDS; i++ )
	{
		if( !g_Commands[i].name[0] )
			break;
		if( !Q_stricmp( g_Commands[i].name, temp ) )
		{
			// update func if different
			if( g_Commands[i].func != callback )
				g_Commands[i].func = callback;
			return;
		}
	}

	if( i == MAX_GAMECOMMANDS )
	{
		G_Error( "G_AddCommand: Couldn't find a free g_Commands spot for the new command. (increase MAX_GAMECOMMANDS)\n" );
		return;
	}

	// we don't have it, add it
	g_Commands[i].func = callback;
	strcpy( g_Commands[i].name, temp );
	trap_ConfigString( CS_GAMECOMMANDS + i, g_Commands[i].name );
}

//==================
//G_InitGameCommands
//==================
void G_InitGameCommands( void )
{
	int i;

	for( i = 0; i < MAX_GAMECOMMANDS; i++ )
	{
		g_Commands[i].func = NULL;
		g_Commands[i].name[0] = 0;
	}

	G_AddCommand( "position", Cmd_Position_f );
	G_AddCommand( "players", Cmd_Players_f );
	G_AddCommand( "stats", Cmd_ShowStats_f );
	G_AddCommand( "say", Cmd_SayCmd_f );
	G_AddCommand( "say_team", Cmd_SayTeam_f );
	G_AddCommand( "svscore", Cmd_Score_f );
	G_AddCommand( "drop", Cmd_Drop_f );
	G_AddCommand( "give", Cmd_Give_f );
	G_AddCommand( "god", Cmd_God_f );
	//	G_AddCommand( "notarget", Cmd_Notarget_f );	// useless. We have no monsters
	G_AddCommand( "noclip", Cmd_Noclip_f );
	G_AddCommand( "invnext", Cmd_InvNext_f );
	G_AddCommand( "invprev", Cmd_InvPrev_f );
	G_AddCommand( "invnextw", Cmd_InvNextWeap_f );
	G_AddCommand( "invprevw", Cmd_InvPrevWeap_f );
	G_AddCommand( "invuse", Cmd_InvUse_f );
	G_AddCommand( "invdrop", Cmd_InvDrop_f );
	G_AddCommand( "use", Cmd_Use_f );
	G_AddCommand( "chaseprev", Cmd_ChasePrev_f );
	G_AddCommand( "chasenext", Cmd_ChaseNext_f );
	G_AddCommand( "kill", Cmd_Kill_f );
	G_AddCommand( "putaway", Cmd_PutAway_f );
	G_AddCommand( "chase", Cmd_ChaseCam_f );
	G_AddCommand( "spec", Cmd_Spec_f );
	G_AddCommand( "enterqueue", G_Teams_JoinChallengersQueue );
	G_AddCommand( "leavequeue", G_Teams_LeaveChallengersQueue );
	G_AddCommand( "camswitch", Cmd_SwitchChaseCamMode_f );
	G_AddCommand( "timeout", Cmd_Timeout_f );
	G_AddCommand( "timein", Cmd_Timein_f );
	G_AddCommand( "cointoss", Cmd_CoinToss_f );

	// callvotes commands
	G_AddCommand( "callvote", G_CallVote_Cmd );
	G_AddCommand( "opcall", G_OperatorVote_Cmd );
	G_AddCommand( "vote", G_CallVotes_CmdVote );

	G_AddCommand( "operator", Cmd_GameOperator_f );
	G_AddCommand( "op", Cmd_GameOperator_f );

	// teams commands
	G_AddCommand( "ready", G_Match_Ready );
	G_AddCommand( "unready", G_Match_NotReady );
	G_AddCommand( "notready", G_Match_NotReady );
	G_AddCommand( "toggleready", G_Match_ToggleReady );
	G_AddCommand( "join", Cmd_Join_f );

	// CA commands
	G_AddCommand( "playerclass", G_Gametype_CA_PlayerClass_Cmd );

	// coach commands
	G_AddCommand( "coach", G_Teams_Coach );
	G_AddCommand( "lockteam", G_Teams_CoachLockTeam );
	G_AddCommand( "unlockteam", G_Teams_CoachUnLockTeam );
	G_AddCommand( "remove", G_Teams_CoachRemovePlayer );
	G_AddCommand( "invite", G_Teams_Invite_f );

	G_AddCommand( "vsay", G_vsay_Cmd );
	G_AddCommand( "vsay_team", G_Teams_vsay_Cmd );

	// bot commands
	G_AddCommand( "showclosestnode", Cmd_ShowPLinks_f );
	G_AddCommand( "deleteclosestnode", Cmd_deleteClosestNode_f );
	G_AddCommand( "botnotarget", AI_Cheat_NoTarget );
}

//=================
//ClientCommand
//=================
void ClientCommand( edict_t *ent )
{
	char *cmd;
	int i;

	if( !ent->r.client )
		return; // not fully in game yet

	if( trap_GetClientState( PLAYERNUM( ent ) ) < CS_SPAWNED )
		return;

	cmd = trap_Cmd_Argv( 0 );

	for( i = 0; i < MAX_GAMECOMMANDS; i++ )
	{
		if( !g_Commands[i].name[0] )
			break;
		if( !Q_stricmp( g_Commands[i].name, cmd ) )
		{
			if( g_Commands[i].func )
				g_Commands[i].func( ent );
			return;
		}
	}

	G_PrintMsg( ent, "Bad user command: %s\n", cmd );
}
