/*
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.
//==================
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;

		trap_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_NOOLDORIGIN;
		event->s.ownerNum = ENTNUM( ent );
	}

	VectorCopy( origin, ent->s.origin );
	VectorCopy( origin, ent->s.old_origin );
	G_AddEvent( ent, EV_TELEPORT, 0, 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_NOOLDORIGIN;
		event->s.ownerNum = ENTNUM( ent );
	}

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

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

char *ClientTeam (edict_t *ent)
{
	char		*p;
	static char	value[512];

	value[0] = 0;

	if (!ent->r.client)
		return value;

	Q_strncpyz(value, Info_ValueForKey (ent->r.client->pers.userinfo, "skin"), sizeof(value));
	p = strchr(value, '/');
	if (!p)
		return value;
	
	return ++p;
}

qboolean OnSameTeam( edict_t *ent1, edict_t *ent2 )
{
	return ( ent1->s.team == ent2->s.team );
}


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

	cl = ent->r.client;
	
	if( cl->chase.active ) {
		ChaseNext(ent);
	}

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

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

	cl = ent->r.client;
	
	if( cl->chase.active ) {
		ChasePrev(ent);
	}

	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
//==================
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
//==================
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_Notarget_f
//Sets client to notarget
//argv(0) notarget
//==================
void Cmd_Notarget_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_NOTARGET;
	if (!(ent->flags & FL_NOTARGET) )
		msg = "notarget OFF\n";
	else
		msg = "notarget ON\n";

	G_PrintMsg( ent, msg );
}

//==================
//Cmd_Noclip_f
//
//argv(0) noclip
//==================
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_WALK;
		msg = "noclip OFF\n";
	}
	else
	{
		ent->movetype = MOVETYPE_NOCLIP;
		msg = "noclip ON\n";
	}

	G_PrintMsg( ent, msg );
}

//==================
//Cmd_Use_f
//Use an inventory item
//==================
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_Drop_f
//Drop an inventory item
//==================
void Cmd_Drop_f( edict_t *ent )
{
	gitem_t		*it;
	char		*s;
	
	s = trap_Cmd_Args();

	// "weapon" acts as a shortcut for current weapon
	if( !Q_stricmp( trap_Cmd_Argv(1), "weapon") ) {
		if( !ent->s.weapon ) {
			G_PrintMsg( ent, "No weapon to drop\n", s );
			return;
		}
		it = game.items[ent->s.weapon];
	} 
	else if( !Q_stricmp( trap_Cmd_Argv(1), "flag") ) {
		if( ent->s.team == TEAM_BLUE )
			it = G_Gametype_CTF_FlagItem( TEAM_RED );
		else if( ent->s.team == TEAM_RED )
			it = G_Gametype_CTF_FlagItem( TEAM_BLUE );
		else
			it = GS_FindItemByName(s);
	}
	else {
		it = GS_FindItemByName(s);
	}

	if( !it )
	{
		G_PrintMsg( ent, "unknown item: %s\n", s );
		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", s );
		return;
	}

	//splitmodels (drop)
	if( ent->pmAnim.anim_priority[UPPER] <= ANIM_WAVE )
		G_AddEvent( ent, EV_DROP, 0, qtrue );

	G_DropItem( ent, it );
}

//=================
//Cmd_Inven_f
//=================
void Cmd_Inven_f (edict_t *ent)
{
	int			i;
	char		s[1024];
	int			row[MAX_ITEMS * 2], rowsize, rep;
	gclient_t	*cl;

	cl = ent->r.client;

	cl->showscores = qfalse;
	if (cl->showinventory)
	{
		cl->showinventory = qfalse;
		return;
	}

	cl->showinventory = qtrue;

	// RLE compression
	for (i=1,rowsize=0,row[0] = 0 ; i<game.numItems ; i++)
	{
		row[rowsize++] = cl->inventory[i];
		if (cl->inventory[i])
			continue;

		for (i++, rep = 1; !cl->inventory[i] && (i < game.numItems); i++, rep++);
		row[rowsize++] = rep;
		i--;
	}

	// item 0 is never used
	Q_strncpyz (s, "inv \"", sizeof(s));
	for (i=0 ; i<rowsize-1 ; i++)
		Q_strncatz (s, va("%i ", row[i]), sizeof(s));
	Q_strncatz (s, va("%i\"", row[i]), sizeof(s));

	trap_ServerCmd (ent, s);
}

//=================
//Cmd_InvUse_f
//=================
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_ChasePrev_f
//=================
void Cmd_ChasePrev_f( edict_t *ent )
{
	if( ent->r.client->chase.active ) {
		ChasePrev(ent);
	} else {
		Cmd_ChaseCam_f(ent);
	}
}

//=================
//Cmd_ChaseNext_f
//=================
void Cmd_ChaseNext_f( edict_t *ent )
{
	if( ent->r.client->chase.active ) {
		ChaseNext(ent);
	} else {
		Cmd_ChaseCam_f(ent);
	}
}

//=================
//Cmd_InvDrop_f
//=================
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;
	}

	//splitmodels (Inventory drop)
	if( ent->pmAnim.anim_priority[UPPER] <= ANIM_WAVE )
		G_AddEvent( ent, EV_DROP, 0, qtrue );

	G_DropItem( ent, it );
}

extern void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mod);

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

	if((level.timemsec - ent->r.client->respawn_timestamp) < 5000) // can suicide after 5 seconds
		return;
	ent->flags &= ~FL_GODMODE;
	ent->health = 0;
	meansOfDeath = MOD_SUICIDE;
	//player_die (ent, ent, ent, 100000, vec3_origin);
	
	// wsw : pb : fix /kill command
	Killed(ent, ent, ent, 100000, vec3_origin, MOD_SUICIDE );
}

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

//==================
//Cmd_Score_f
//==================
void Cmd_Score_f (edict_t *ent)
{
	ent->r.client->showinventory = qfalse;
	ent->r.client->showscores = !ent->r.client->showscores;	
}

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

	if( 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
//=================
void Cmd_Players_f (edict_t *ent)
{
	int		i;
	int		count = 0;
	char		line[64];
	char		msg[1024];


	// print information
	msg[0] = 0;
#ifdef BATTLEYE
	if (sv_battleye->integer)
		Q_strncatz (msg, "num BE  name\n", sizeof(msg));
	else
		Q_strncatz (msg, "num name\n", sizeof(msg));
#else
	Q_strncatz (msg, "num name\n", sizeof(msg));
#endif
	Q_strncatz (msg, "--- ---------------\n", sizeof(msg));
	
	for (i = 0 ; i < game.maxclients ; i++) {
		if (game.clients[i].pers.connected) {
#ifdef BATTLEYE
			if (sv_battleye->integer)
				Q_snprintfz (line, sizeof(line), "%3i %3s %s\n", i, game.clients[i].pers.battleye?"on":"off", game.clients[i].pers.netname);
			else
				Q_snprintfz (line, sizeof(line), "%3i %s\n", i, game.clients[i].pers.netname);
#else
			Q_snprintfz (line, sizeof(line), "%3i %s\n", i, game.clients[i].pers.netname);
#endif
			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);
}

//=================
//Cmd_Wave_f	- SPLITMODELS
//=================
void Cmd_Wave_f (edict_t *ent)
{
	int		i;

	i = atoi (trap_Cmd_Argv(1));

	if (ent->pmAnim.anim_priority[UPPER] > ANIM_WAVE)
		return;

	ent->pmAnim.anim_priority[UPPER] = ANIM_WAVE;

	switch (i)
	{
	case 0:
	default:
		G_AddEvent (ent, EV_GESTURE, 0, qtrue);
		break;
	}
}

qboolean CheckFlood(edict_t *ent)
{
	int		i;
	gclient_t *cl;

	if (flood_msgs->integer) {
		cl = ent->r.client;

		if (game.realtime < cl->flood_locktill) {
			G_PrintMsg (ent, "You can't talk for %d more seconds\n",
					(int)(cl->flood_locktill - game.realtime));
			return qtrue;
		}
		i = cl->flood_whenhead - flood_msgs->integer + 1;
		if (i < 0)
			i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i;
		if (cl->flood_when[i] && game.realtime - cl->flood_when[i] < flood_persecond->integer) {
			cl->flood_locktill = game.realtime + flood_waitdelay->value;
			G_PrintMsg (ent, "Flood protection:  You can't talk for %d seconds.\n",
					flood_waitdelay->integer);
			return qtrue;
		}
		cl->flood_whenhead = (cl->flood_whenhead + 1) %
			(sizeof(cl->flood_when)/sizeof(cl->flood_when[0]));
		cl->flood_when[cl->flood_whenhead] = game.realtime;
	}
	return qfalse;
}

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

	if( CheckFlood(ent) )
		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 == '"' )
		{
			p++;
			p[strlen(p)-1] = 0;
		}
		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 );
}

// wsw : jal : gamecommandscompletion[start]

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

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

#ifdef VSAYS
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 }
};

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) ) { // ignore flood checks on bots
		return;
	}

	if( ent->r.client && ent->r.client->pers.muted & 2 )
		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( 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
//==================
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( !(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) )
			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
//==================
void G_vsay_Cmd( edict_t *ent ) {
	G_vsay_f( ent, qfalse );
}

//==================
//G_Teams_vsay_Cmd
//==================
void G_Teams_vsay_Cmd( edict_t *ent ) {
	G_vsay_f( ent, qtrue );
}
#endif // VSAYS

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

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

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

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

//====================
//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 );
}

//=================
//Cmd_ShowAccuracies_f
//=================
void Cmd_ShowAccuracies_f(edict_t *ent)
{
	gitem_t *it;
	int i;
	int weakhit,weakshot;
	int hit,shot;
	float p;
	gclient_t *client;

	// when chasing generate from target
	client = ent->r.client;
	if( client->chase.active && game.edicts[client->chase.target].r.client )
		client = game.edicts[client->chase.target].r.client;

	G_PrintMsg (ent, "%2s%s", " ", " Weak               Strong\n");
	G_PrintMsg (ent, "%2s%s", " ", "  hit/shot percent");
	G_PrintMsg (ent, "   hit/shot percent\n");
	//G_PrintMsg (ent, "-------------------------\n");

	for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ )
	{
		it = GS_FindItemByTag( i ); 

		if( it->weakammo_tag != AMMO_NONE ) {
			weakhit = client->resp.accuracy_hits[it->weakammo_tag-AMMO_CELLS];
			weakshot = client->resp.accuracy_shots[it->weakammo_tag-AMMO_CELLS];
		} else {
			weakhit = 0;
			weakshot = 0;
		}
		if( it->ammo_tag != AMMO_NONE ) {
			hit = client->resp.accuracy_hits[it->ammo_tag-AMMO_CELLS];
			shot = client->resp.accuracy_shots[it->ammo_tag-AMMO_CELLS];
		} else {
			hit = 0;
			shot = 0;
		}

		if(weakshot>0 || shot>0) // only continue with registered shots
		{ 
			if(weakshot!=0)
				p=(100.0f*weakhit)/((float)weakshot);
			else
				p=0.0f;

			G_PrintMsg (ent, "%s%2s%s: %s%3i%s/%s%3i      %s%2.1f", // weak
				it->color,it->short_name,S_COLOR_WHITE, 
				S_COLOR_GREEN, weakhit, S_COLOR_WHITE,
				S_COLOR_CYAN, weakshot, 
				S_COLOR_YELLOW, p);

			if(shot!=0)
				p=(100.0f*hit)/((float)shot);
			else
				p=0.0f;

			G_PrintMsg (ent, "   %s%3i%s/%s%3i      %s%2.1f\n",      // strong
				S_COLOR_GREEN, hit, S_COLOR_WHITE,
				S_COLOR_CYAN, shot, 
				S_COLOR_YELLOW, p);
		}
	}
}

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

g_gamecommands_t	g_Commands[MAX_GAMECOMMANDS];

void Cmd_ShowPLinks_f( edict_t *ent ); //MbotGame

//==================
//G_AddCommand
//==================
void G_AddCommand( char *name, void *callback )
{
	int	i;

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

	// we didn't have it. Find a free one and add it
	for( i = 0; i < MAX_GAMECOMMANDS; i++ )
	{
		if( g_Commands[i].index == -1 ) {
			g_Commands[i].index = i;
			g_Commands[i].func = callback;
			Q_strncpyz( g_Commands[i].name, name, sizeof(g_Commands[i].name) );
			trap_ConfigString( CS_GAMECOMMANDS + i, name );
			return;
		}
	}

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

//==================
//G_InitGameCommands
//==================
void G_InitGameCommands( void )
{
	int	i;
	for( i = 0; i < MAX_GAMECOMMANDS; i++ )
	{
		g_Commands[i].index = -1;
		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_ShowAccuracies_f );
	G_AddCommand( "say", Cmd_SayCmd_f );
	G_AddCommand( "say_team", Cmd_SayTeam_f );
	G_AddCommand( "score", 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( "inven", Cmd_Inven_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( "svuse", 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( "wave", Cmd_Wave_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 );

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

	// teams commands
	G_AddCommand( "ready", G_Match_Ready );
	G_AddCommand( "unready", G_Match_NotReady );
	G_AddCommand( "notready", G_Match_NotReady );
	G_AddCommand( "join", G_Teams_Join_Cmd );
#ifdef VSAYS
	G_AddCommand( "vsay", G_vsay_Cmd );
	G_AddCommand( "vsay_team", G_Teams_vsay_Cmd );
#endif
	G_AddCommand( "lock", G_Teams_Lock_f );
	G_AddCommand( "unlock", G_Teams_UnLock_f );
	G_AddCommand( "invite", G_Teams_Invite_f );

	// bot commands
	G_AddCommand( "showplinks", Cmd_ShowPLinks_f );	//MbotGame
}

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

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

	cmd = trap_Cmd_Argv(0);

	for( i = 0; i < MAX_GAMECOMMANDS; i++ )
	{
		if( g_Commands[i].index == -1 ) 
			continue;
		if( !Q_stricmp( g_Commands[i].name, cmd) ) {
			if( g_Commands[i].func )
				g_Commands[i].func(ent);

			return;
		}
	}
	
	// unknown as a command. Say it as chat
	Cmd_Say_f (ent, qtrue);
}

// wsw : jal : gamecommandscompletion[end]

