/*
   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"


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


void player_pain( edict_t *self, edict_t *other, float kick, int damage )
{
	// player pain is handled at the end of the frame in P_DamageFeedback
}

void player_think( edict_t *self )
{
	// player entities do not think
}

static void ClientObituary( edict_t *self, edict_t *inflictor, edict_t *attacker )
{
	int mod;
	char message[64];
	char message2[64];

	mod = meansOfDeath;

	GS_Obituary( self, G_PlayerGender( self ), attacker, mod, message, message2 );

	// duplicate message at server console for logging
	if( attacker && attacker->r.client )
	{
		if( attacker != self )
		{                       // regular death message
			self->enemy = attacker;

			if( dedicated->integer )
				G_Printf( "%s %s %s%s\n", self->r.client->pers.netname, message, attacker->r.client->pers.netname, message2 );
		}
		else
		{           // suicide
			self->enemy = NULL;
			if( dedicated->integer )
				G_Printf( "%s %s%s\n", self->r.client->pers.netname, S_COLOR_WHITE, message );
		}

		G_Obituary( self, attacker, mod );
	}
	else
	{           // wrong place, suicide, etc.
		self->enemy = NULL;
		if( dedicated->integer )
			G_Printf( "%s %s%s\n", self->r.client->pers.netname, S_COLOR_WHITE, message );

		G_Obituary( self, ( attacker == self ) ? self : world, mod );
	}
}


//=======================================================
// DEAD BODIES
//=======================================================


//=============
//G_Client_UnlinkBodies
//=============
static void G_Client_UnlinkBodies( edict_t *ent )
{
	edict_t	*body;
	int i;

	// find bodies linked to us
	body = &game.edicts[game.maxclients + 1];
	for( i = 0; i < BODY_QUEUE_SIZE; body++, i++ )
	{
		if( !body->r.inuse )
			continue;

		if( body->activator == ent )
		{
			// this is our body
			body->activator = NULL;
		}
	}
}

//==================
//InitBodyQue
//==================
void G_InitBodyQueue( void )
{
	int i;
	edict_t	*ent;

	level.body_que = 0;
	for( i = 0; i < BODY_QUEUE_SIZE; i++ )
	{
		ent = G_Spawn();
		ent->classname = "bodyque";
	}
}

//==================
//body_die
//==================
static void body_die( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point )
{
	if( self->health >= GIB_HEALTH )
		return;

	ThrowSmallPileOfGibs( self, 2, damage );
	self->s.origin[2] -= 48;
	ThrowClientHead( self, damage );
}

//==================
//body_ready
//autodestruct the body
//==================
static void body_think( edict_t *self )
{
	self->health = GIB_HEALTH - 1;

	//effect: small gibs, and only when it is still a body, not a gibbed head.
	if( self->s.type == ET_CORPSE )
	{
		ThrowSmallPileOfGibs( self, 2, 25 );
	}

	//disallow interaction with the world.
	self->takedamage = DAMAGE_NO;
	self->r.solid = SOLID_NOT;
	self->s.sound = 0;
	self->flags |= FL_NO_KNOCKBACK;
	self->s.type = ET_GENERIC;
	self->r.svflags &= ~SVF_CORPSE;
	self->r.svflags |= SVF_NOCLIENT;
	self->s.modelindex = 0;
	self->s.modelindex2 = 0;
	VectorClear( self->velocity );
	VectorClear( self->avelocity );
	self->movetype = MOVETYPE_NONE;
	self->think = NULL;
	//memset( &self->snap, 0, sizeof(self->snap) );
	GClip_UnlinkEntity( self );
}

static void body_ready( edict_t *body )
{
	body->takedamage = DAMAGE_YES;
	body->r.solid = SOLID_BBOX;
	body->think = body_think; // body self destruction countdown
	if( g_deadbody_filter->integer )
	{
		body->nextthink = level.time + 2000; // explode it after 1 secs ( + 2 of being ready )
	}
	else
	{
		body->nextthink = level.time + 8000 + random()*10000;
	}

	GClip_LinkEntity( body );
}

//==================
//CopyToBodyQue
//create a indexed player model for the corpse based on client's
//==================
static edict_t *CopyToBodyQue( edict_t *ent, edict_t *attacker, int damage )
{
	edict_t	*body;
	int contents;

	if( game.gametype == GAMETYPE_RACE )
		return NULL;

	contents = G_PointContents( ent->s.origin );
	if( contents & CONTENTS_NODROP )
		return NULL;

	G_Client_UnlinkBodies( ent );

	// grab a body que and cycle to the next one
	body = &game.edicts[game.maxclients + level.body_que + 1];
	level.body_que = ( level.body_que + 1 ) % BODY_QUEUE_SIZE;

	// send an effect on the removed body
	if( body->s.modelindex && body->s.type == ET_CORPSE )
		ThrowSmallPileOfGibs( body, 2, 10 );

	GClip_UnlinkEntity( body );

	memset( body, 0, sizeof( edict_t ) ); //clean up garbage

	//init body edict
	G_InitEdict( body );
	body->classname = "body";
	body->health = ent->health;
	body->mass = ent->mass;
	body->deadflag = DEAD_DEAD;
	body->r.owner = ent->r.owner;
	body->s.type = ent->s.type;
	body->s.team = ent->s.team;
	body->s.effects = 0;
	body->s.renderfx = 0;
	body->r.svflags = SVF_CORPSE;
	body->r.svflags &= ~SVF_NOCLIENT;
	body->activator = ent;
	if( g_deadbody_followkiller->integer )
		body->enemy = attacker;

	//use flat yaw
	body->s.angles[PITCH] = 0;
	body->s.angles[ROLL] = 0;
	body->s.angles[YAW] = ent->s.angles[YAW];
	body->s.modelindex2 = 0; // <-  is bodyOwner when in ET_CORPSE, but not in ET_GENERIC or ET_PLAYER
	body->s.weapon = 0;

	//copy player position and box size
	VectorCopy( ent->s.old_origin, body->s.old_origin );
	VectorCopy( ent->s.origin, body->s.origin );
	VectorCopy( ent->s.origin, body->olds.origin );
	VectorCopy( ent->r.mins, body->r.mins );
	VectorCopy( ent->r.maxs, body->r.maxs );
	VectorCopy( ent->r.absmin, body->r.absmin );
	VectorCopy( ent->r.absmax, body->r.absmax );
	VectorCopy( ent->r.size, body->r.size );
	body->r.maxs[2] = body->r.mins[2] + 8;

	//body->r.solid = ent->r.solid;
	body->r.solid = SOLID_BBOX;
	body->takedamage = DAMAGE_YES;
	body->r.clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
	body->movetype = MOVETYPE_TOSS;
	body->die = body_die;
	body->think = body_think; // body self destruction countdown

	if( ent->health < GIB_HEALTH )
	{
		ThrowSmallPileOfGibs( body, 3, damage );
		ThrowClientHead( body, damage ); // sets ET_GIB
		body->s.frame = 0;
		body->nextthink = level.time + 5000 + random()*10000;
	}
	else if( ent->s.type == ET_PLAYER )
	{
		// copy the model
		body->s.type = ET_CORPSE;
		body->s.modelindex = ent->s.modelindex;
		body->s.bodyOwner = ent->s.number; // bodyOwner is the same as modelindex2
		body->s.skinnum = ent->s.skinnum;
		body->s.teleported = qtrue;

		// when it's not a gib (they get their own impulse) copy player's velocity (knockback deads)
		VectorCopy( ent->velocity, body->velocity );

		// launch the death animation on the body
		{
			static int i;
			i = ( i+1 )%3;
			G_AddEvent( body, EV_DIE, i, qtrue );
			switch( i )
			{
			default:
			case 0:
				body->s.frame = ( ( BOTH_DEAD1&0x3F )|( BOTH_DEAD1&0x3F )<<6|( 0 &0xF )<<12 );
				break;
			case 1:
				body->s.frame = ( ( BOTH_DEAD2&0x3F )|( BOTH_DEAD2&0x3F )<<6|( 0 &0xF )<<12 );
				break;
			case 2:
				body->s.frame = ( ( BOTH_DEAD3&0x3F )|( BOTH_DEAD3&0x3F )<<6|( 0 &0xF )<<12 );
				break;
			}
		}

		body->think = body_ready;
		body->takedamage = DAMAGE_NO;
		body->r.solid = SOLID_NOT;
		body->nextthink = level.time + 500; // make damageable in 0.5 seconds
	}
	else // wasn't a player, just copy it's model
	{
		body->s.modelindex = ent->s.modelindex;
		body->s.frame = ent->s.frame;
		body->nextthink = level.time + 5000 + random()*10000;
	}

	GClip_LinkEntity( body );
	return body;
}

//======================================================
// DEATH DROPS
//======================================================

//==================
//TossClientWeapon
//==================
void TossClientWeapon( edict_t *self )
{
	gitem_t	*item;
	edict_t	*drop;
	qboolean quad, shell;
	float spread;

	item = NULL;
	if( self->s.weapon > WEAP_GUNBLADE )
		item = game.items[self->s.weapon];
	if( !self->r.client->inventory[self->r.client->ammo_weak_index] )
		item = NULL;

	if( !( dmflags->integer & DF_QUAD_DROP ) )
		quad = qfalse;
	else
		quad = ( self->r.client->quad_timeout > ( level.time + 1000 ) );

	if( !( dmflags->integer & DF_QUAD_DROP ) )
		shell = qfalse;
	else
		shell = ( self->r.client->shell_timeout > ( level.time + 1000 ) );

	if( item && quad )
		spread = 22.5;
	else
		spread = 0.0;

	if( item )
	{
		self->r.client->ps.viewangles[YAW] -= spread;
		drop = Drop_Item( self, item );
		self->r.client->ps.viewangles[YAW] += spread;
		if( drop )
		{
			drop->spawnflags |= DROPPED_PLAYER_ITEM;
			//wsw: count of weak ammos to give when picking it up
			drop->count = self->r.client->inventory[self->r.client->ammo_weak_index];
		}
	}

	if( quad )
	{
		self->r.client->ps.viewangles[YAW] += spread;
		drop = Drop_Item( self, game.items[POWERUP_QUAD] );
		self->r.client->ps.viewangles[YAW] -= spread;
		if( drop )
		{
			drop->spawnflags |= DROPPED_PLAYER_ITEM;
			drop->touch = Touch_Item;
			drop->nextthink = level.time + ( self->r.client->quad_timeout - level.time );
			drop->think = G_FreeEdict;
		}
	}

	if( shell )
	{
		self->r.client->ps.viewangles[YAW] += spread;
		drop = Drop_Item( self, game.items[POWERUP_SHELL] );
		self->r.client->ps.viewangles[YAW] -= spread;
		if( drop )
		{
			drop->spawnflags |= DROPPED_PLAYER_ITEM;
			drop->touch = Touch_Item;
			drop->nextthink = level.time + ( self->r.client->shell_timeout - level.time );
			drop->think = G_FreeEdict;
		}
	}
}

//==================
//G_DropClientBackPack
//==================
void G_DropClientBackPack( edict_t *self )
{
	gitem_t	*item;
	int active_ammo_tag;
	edict_t	*pack;
	float yawoffset;

	item = GS_FindItemByClassname( "item_ammopack" );
	if( !item )
		return;

	if( !G_Gametype_CanDropItem( item, qfalse ) )
		return;

	// find the tags of active weapon ammo
	if( self->s.weapon )
		active_ammo_tag = game.items[self->s.weapon]->ammo_tag;
	else
		active_ammo_tag = 0;

	if( active_ammo_tag == AMMO_CELLS )
		active_ammo_tag = 0;

	// nothing to drop?
	if( !active_ammo_tag || self->r.client->inventory[active_ammo_tag] )
		return;

	//create the entity
	yawoffset = random() * self->r.client->ps.viewangles[YAW] * 0.5;
	self->r.client->ps.viewangles[YAW] -= yawoffset;
	pack = Drop_Item( self, item );
	self->r.client->ps.viewangles[YAW] += yawoffset;
	if( pack )
	{
		pack->spawnflags |= DROPPED_PLAYER_ITEM;
		pack->invpak[AMMO_CELLS] = 0;
		if( active_ammo_tag )
			pack->invpak[active_ammo_tag] = self->r.client->inventory[active_ammo_tag];
	}
}

//==================
//player_die
//==================
void player_die( edict_t *ent, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point )
{
	int contents;
	edict_t	*body;

	VectorClear( ent->avelocity );

	ent->s.angles[0] = 0;
	ent->s.angles[2] = 0;
	ent->s.sound = 0;

	ent->r.solid = SOLID_NOT;

	// player death
	if( !ent->deadflag )
	{
		contents = G_PointContents( ent->s.origin );

		ent->s.angles[YAW] = ent->r.client->ps.viewangles[YAW] = LookAtKillerYAW( ent, inflictor, attacker );
		ent->r.client->ps.pmove.pm_type = PM_DEAD;
		ClientObituary( ent, inflictor, attacker );

		// check if player is in a nodrop area and
		// reset flags and techs, otherwise drop items
		if( !( contents & CONTENTS_NODROP ) )
		{
			G_Gametype_CTF_DeadDropFlag( ent );
		}
		else
		{
			G_Gametype_CTF_ResetClientFlag( ent );
		}

		// create a body
		body = CopyToBodyQue( ent, attacker, damage );
		ent->enemy = NULL;
	}

	// hide laser
	if( ent->s.weapon == WEAP_LASERGUN )
		G_HideClientLaser( ent );

	// clear his combo stats
	G_AwardResetPlayerComboStats( ent );

	// clear inventory
	memset( ent->r.client->inventory, 0, sizeof( ent->r.client->inventory ) );

	ent->r.client->ps.pmove.pm_type = PM_FREEZE;
	ent->r.client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
	ent->r.client->ps.POVnum = ENTNUM( ent );

	// clean up powerup info
	ent->r.client->quad_timeout = 0;
	ent->r.client->shell_timeout = 0;
	ent->r.client->weapon_sound = 0;
	memset( &ent->r.client->weaponstate, 0, sizeof( ent->r.client->weaponstate ) );

	ent->viewheight = 0;
	ent->s.modelindex = 0;
	ent->s.modelindex2 = 0;
	ent->s.effects = 0;
	ent->s.weapon = 0;
	ent->s.sound = 0;
	ent->s.light = 0;
	ent->r.solid = SOLID_NOT;
	ent->takedamage = DAMAGE_NO;
	ent->movetype = MOVETYPE_NONE;
	ent->deathTimeStamp = level.time;

	VectorClear( ent->velocity );
	VectorClear( ent->avelocity );

	ent->r.client->buttons = 0;
	ent->snap.buttons = 0;

	ent->deadflag = DEAD_DEAD;
	GClip_LinkEntity( ent );
}

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

//==============
//InitClientPersistant
//
//This is only called when the game first initializes in single player,
//but is called after each death and level change in deathmatch
//==============
void InitClientPersistant( gclient_t *client )
{
	memset( &client->pers, 0, sizeof( client->pers ) );

	client->pers.connected = qtrue;
}


void InitClientResp( gclient_t *client )
{
	memset( &client->resp, 0, sizeof( client->resp ) );
}

void InitClientTeamChange( gclient_t *client )
{
	memset( &client->teamchange, 0, sizeof( client->teamchange ) );
}

qboolean ClientRespawn( edict_t *self )
{
	int index, i;
	edict_t *spawnpoint;
	vec3_t spawn_origin, spawn_angles;
	gclient_t *client;
	client_respawn_t resp;
	client_teamchange_t teamchange;
	client_persistant_t pers;
	char userinfo[MAX_INFO_STRING];
	weapon_info_t *weaponinfo;
	int ammocount, weakammocount, spawnradius = 0;

	self->r.svflags &= ~SVF_NOCLIENT;

	//if invalid be spectator
	if( self->s.team < 0 || self->s.team >= GS_MAX_TEAMS )
		self->s.team = TEAM_SPECTATOR;

	GClip_UnlinkEntity( self );

	index = self-game.edicts-1;
	client = self->r.client;

	// deathmatch wipes most client data every spawn
	resp = client->resp;
	teamchange = client->teamchange;
	pers = client->pers;
	memcpy( userinfo, client->pers.userinfo, sizeof( userinfo ) );
	memset( client, 0, sizeof( *client ) );
	client->resp = resp;
	client->teamchange = teamchange;
	client->pers = pers;

	self->deadflag = DEAD_NO;
	ClientUserinfoChanged( self, userinfo ); // it also sets modelindexes and skinnum
	self->s.modelindex2 = 0;

	// clear entity values
	memset( &self->snap, 0, sizeof( self->snap ) );
	self->groundentity = NULL;
	self->r.client = &game.clients[index];
	self->takedamage = DAMAGE_AIM;
	self->movetype = MOVETYPE_PLAYER;
	self->think = player_think;
	self->pain = player_pain;
	self->die = player_die;
	self->viewheight = playerbox_stand_viewheight;
	self->r.inuse = qtrue;
	self->mass = 200;
	self->r.solid = SOLID_BBOX;
	self->air_finished = level.time + ( 12*1000 );
	self->r.clipmask = MASK_PLAYERSOLID;
	self->waterlevel = 0;
	self->watertype = 0;
	self->flags &= ~FL_NO_KNOCKBACK;
	self->r.svflags &= ~SVF_CORPSE;
	//MbotGame[start]

	self->enemy = NULL;
	self->r.owner = NULL;

	if( self->ai.type == AI_ISBOT )
	{
		self->think = AI_Think;
		self->classname = "bot";
	}
	else if( self->r.svflags & SVF_FAKECLIENT )
	{
		self->classname = "fakeclient"; //[end]
	}
	else
	{
		self->classname = "player";
	}

	VectorCopy( playerbox_stand_mins, self->r.mins );
	VectorCopy( playerbox_stand_maxs, self->r.maxs );
	VectorClear( self->velocity );
	VectorClear( self->avelocity );

	// clear playerstate values
	memset( &self->r.client->ps, 0, sizeof( client->ps ) );

	client->ps.POVnum = ENTNUM( self );

	// clear entity state values
	self->s.type = ET_PLAYER;
	self->s.effects = 0;
	self->s.light = 0;

	self->s.frame = 0;
	self->s.weapon = 0;

	self->fall_fatal = qfalse;

	if( self->s.team != TEAM_SPECTATOR )
		spawnradius = G_Gametype_SpawnPointRadius();
	SelectSpawnPoint( self, &spawnpoint, spawn_origin, spawn_angles, spawnradius );
	VectorCopy( spawn_origin, client->ps.pmove.origin );
	client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
	VectorCopy( spawn_origin, self->s.origin );
	VectorCopy( self->s.origin, self->s.old_origin );

	// set angles
	self->s.angles[PITCH] = 0;
	self->s.angles[YAW] = spawn_angles[YAW];
	self->s.angles[ROLL] = 0;
	VectorCopy( self->s.angles, client->ps.viewangles );

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

	//don't put spectators in the game
	if( self->s.team == TEAM_SPECTATOR )
	{
		self->movetype = MOVETYPE_NOCLIP;
		self->r.solid = SOLID_NOT;
		self->r.svflags |= SVF_NOCLIENT;
		client->ps.pmove.pm_time = PM_SPECTATOR;
	}
	else
	{
		if( KillBox( self ) )
		{
		}

		// add a teleportation effect
		G_SpawnTeleportEffect( self );
	}

	self->s.teleported = qtrue;

	// hold in place briefly
	client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
	client->ps.pmove.pm_time = 14;

	BOT_Respawn( self ); //MbotGame

	// wsw: pb set default max health
	self->max_health = 100;
	self->health = self->max_health;

	self->r.client->buttons = 0;

	// resetting award stats
	memset( &self->r.client->awardInfo, 0, sizeof( award_info_t ) );

	//give default items
	memset( &self->r.client->inventory, 0, sizeof( self->r.client->inventory ) );
	if( self->s.team == TEAM_SPECTATOR )
	{
		G_UseTargets( spawnpoint, self );
	}
	else
	{
		if( g_instagib->integer )
		{
			self->r.client->inventory[WEAP_INSTAGUN] = 1;
			self->r.client->inventory[AMMO_INSTAS] = 1;
			self->r.client->inventory[AMMO_WEAK_INSTAS] = 1;
		}
		else
		{
			if( match.state == MATCH_STATE_WARMUP )
			{
				int i;

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

					if( i == WEAP_INSTAGUN )  // dont add instagun...
						continue;

					weaponinfo = GS_FiredefForWeapon( i );
					if( weaponinfo )
					{
						self->r.client->inventory[i] = 1;
						if( weaponinfo->firedef_weak->ammo_id )
							self->r.client->inventory[weaponinfo->firedef_weak->ammo_id] = weaponinfo->firedef_weak->ammo_max;
						if( weaponinfo->firedef->ammo_id )
							self->r.client->inventory[weaponinfo->firedef->ammo_id] = weaponinfo->firedef->ammo_max;
					}
				}
				self->r.client->armortag = ARMOR_YA;
				self->r.client->armor = 100;

			}
			else
			{
				self->r.client->inventory[WEAP_GUNBLADE] = 1;
				if( GS_FiredefForAmmo( AMMO_CELLS ) )
					self->r.client->inventory[AMMO_CELLS] = GS_FiredefForAmmo( AMMO_CELLS )->ammo_max;
				else
					self->r.client->inventory[AMMO_CELLS] = 0;
				self->r.client->inventory[AMMO_WEAK_GUNBLADE] = 0;
			}
		}

		G_UseTargets( spawnpoint, self );

		for( i = WEAP_TOTAL - 1; i >= WEAP_GUNBLADE; i-- )
		{
			if( i == WEAP_SHOCKWAVE )  // FIXME!!!
				continue;

			if( !self->r.client->inventory[i] )
				continue;

			if( g_select_empty->integer )
				break;

			weaponinfo = &gs_weaponInfos[i];

			if( weaponinfo->firedef->usage_count && weaponinfo->firedef->ammo_id )
				ammocount = self->r.client->inventory[weaponinfo->firedef->ammo_id];
			else
				ammocount = weaponinfo->firedef->usage_count;

			if( weaponinfo->firedef_weak->usage_count && weaponinfo->firedef_weak->ammo_id )
				weakammocount = self->r.client->inventory[weaponinfo->firedef_weak->ammo_id];
			else
				weakammocount = weaponinfo->firedef_weak->usage_count;

			if( ammocount >= weaponinfo->firedef->usage_count || weakammocount >= weaponinfo->firedef_weak->usage_count )
				break;
		}
		self->r.client->latched_weapon = i;

		// FIXME: forcing RL to be used if available
		if( self->r.client->inventory[WEAP_ROCKETLAUNCHER] )
		{
			if( g_select_empty->integer )
				self->r.client->latched_weapon = WEAP_ROCKETLAUNCHER;

			weaponinfo = &gs_weaponInfos[WEAP_ROCKETLAUNCHER];

			if( weaponinfo->firedef->usage_count && weaponinfo->firedef->ammo_id )
				ammocount = self->r.client->inventory[weaponinfo->firedef->ammo_id];
			else
				ammocount = weaponinfo->firedef->usage_count;

			if( weaponinfo->firedef_weak->usage_count && weaponinfo->firedef_weak->ammo_id )
				weakammocount = self->r.client->inventory[weaponinfo->firedef_weak->ammo_id];
			else
				weakammocount = weaponinfo->firedef_weak->usage_count;

			if( ammocount >= weaponinfo->firedef->usage_count || weakammocount >= weaponinfo->firedef_weak->usage_count )
				self->r.client->latched_weapon = WEAP_ROCKETLAUNCHER;
		}

		ChangeWeapon( self );
		self->r.client->weaponstate.status = WEAPON_ACTIVATING;
		self->r.client->weaponstate.nexttime = WEAPON_RESPAWN_DELAY;
	}

	GClip_LinkEntity( self );
	return qtrue;
}

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

//=====================
//ClientBeginMultiplayerGame
//
//A client has just connected to the server in
//multiplayer mode, so clear everything out before starting them.
//=====================
void ClientBeginMultiplayerGame( edict_t *ent )
{
	G_InitEdict( ent );

	InitClientResp( ent->r.client );
	InitClientTeamChange( ent->r.client );

	if( match.state >= MATCH_STATE_POSTMATCH )
	{
		G_MoveClientToPostMatchScoreBoards( ent, G_SelectIntermissionSpawnPoint() );
	}
	else if( match.state >= MATCH_STATE_WARMUP )
	{
		G_Gametype_ClientRespawn( ent );
	}

	G_UpdatePlayerMatchMsg( ent );
	G_PrintMsg( NULL, "%s%s entered the game\n", ent->r.client->pers.netname, S_COLOR_WHITE );
}

//===========
//ClientBegin
//
//called when a client has finished connecting, and is ready
//to be placed into the game.  This will happen every level load.
//============
void ClientBegin( edict_t *ent )
{
	//ent->r.client = game.clients + PLAYERNUM(ent);
	G_Gametypes_ClienBegin( ent );
	ent->r.client->resp.respawnCount = 0; //clear respawncount
	// remove reconnecting state
	ent->r.client->pers.connecting = qfalse;

	// schedule the next scoreboard update
	ent->r.client->scoreboard_time = game.realtime + scoreboardInterval - ( game.realtime%scoreboardInterval );

	//MbotGame[start]
	AI_EnemyAdded( ent );
	//[end]

	// make sure all view stuff is valid
	G_ClientEndSnapFrame( ent );
}

// kill all chars with code >= 127
// (127 is not exactly a highchar, but we drop it, too)
static void strip_highchars( char *in )
{
	char *out = in;
	for( ; *in; in++ )
		if( ( unsigned char )*in < 127 )
			*out++ = *in;
	*out = 0;
}

//===========
//G_SetName
//
//Validate and change client's name.
//============
static void G_SetName( edict_t *ent, char *original_name )
{
	const char *invalid_prefixes[] = { "console", "[team]", "[spec]", "[bot]", "[coach]", "[tv]", NULL };
	edict_t *other;
	char name[MAX_NAME_BYTES];
	char colorless[MAX_NAME_BYTES];
	int i, trynum, trylen;
	int c_ascii;
	int maxchars;

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

	// we allow NULL to be passed for name
	if( !original_name )
		original_name = "";

	Q_strncpyz( name, original_name, sizeof( name ) );

	// life is hard, UTF-8 will have to go
	strip_highchars( name );

	COM_SanitizeColorString( va( "%s", name ), name, sizeof( name ), -1, COLOR_WHITE );

	// remove leading whitespace
	while( name[0] == ' ' )
		memmove( name, name + 1, strlen( name ) );

	Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );

	maxchars = MAX_NAME_CHARS;
	if( ent->r.client->pers.tv )
		maxchars = min( maxchars + 10, MAX_NAME_BYTES-1 );

	// require at least one non-whitespace ascii char in the name
	// (this will upset people who would like to have a name entirely in a non-latin
	// script, but it makes damn sure you can't get an empty name by exploiting some
	// utf-8 decoder quirk)
	c_ascii = 0;
	for( i = 0; colorless[i]; i++ )
		if( colorless[i] > 32 && colorless[i] < 127 )
			c_ascii++;

	if( !c_ascii )
	{
		Q_strncpyz( name, "Player", sizeof( name ) );
		Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );
	}

	if( !( ent->r.svflags & SVF_FAKECLIENT ) )
	{
		for( i = 0; invalid_prefixes[i] != NULL; i++ )
		{
			if( !Q_strnicmp( colorless, invalid_prefixes[i], strlen( invalid_prefixes[i] ) ) )
			{
				Q_strncpyz( name, "Player", sizeof( name ) );
				Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );
			}
		}
	}

	if( ent->r.svflags & SVF_FAKECLIENT )
	{
		if( Q_strnicmp( colorless, "[BOT]", strlen( "[BOT]" ) ) )
		{
			Q_snprintfz( colorless, sizeof( name ), "[BOT]%s", name );
			Q_strncpyz( name, colorless, sizeof( colorless ) );
			Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );
		}
	}

	// Limit the name to MAX_NAME_CHARS printable characters
	// (non-ascii utf-8 sequences are currently counted as 2 or more each, sorry)
	COM_SanitizeColorString( va( "%s", name ), name, sizeof( name ),
											maxchars, COLOR_WHITE);
	Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );

	trynum = 1;
	do
	{
		for( i = 0; i < game.maxclients; i++ )
		{
			other = game.edicts + 1 + i;
			if( !other->r.inuse || !other->r.client || other == ent )
				continue;

			// if nick is already in use, try with (number) appended
			if( !Q_stricmp( colorless, COM_RemoveColorTokens( other->r.client->pers.netname ) ) )
			{
				if( trynum != 1 )  // remove last try
					name[strlen( name ) - strlen( va( "%s(%i)", S_COLOR_WHITE, trynum-1 ) )] = 0;

				// make sure there is enough space for the postfix
				trylen = strlen( va( "%s(%i)", S_COLOR_WHITE, trynum ) );
				if( (int)strlen( colorless ) + trylen > maxchars )
				{
					COM_SanitizeColorString( va( "%s", name ), name, sizeof( name ),
						maxchars - trylen, COLOR_WHITE );
					Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );
				}

				// add the postfix
				Q_strncatz( name, va( "%s(%i)", S_COLOR_WHITE, trynum ), sizeof( name ) );
				Q_strncpyz( colorless, COM_RemoveColorTokens( name ), sizeof( colorless ) );

				// go trough all clients again
				trynum++;
				break;
			}
		}
	}
	while( i != game.maxclients && trynum <= MAX_CLIENTS );

	Q_strncpyz( ent->r.client->pers.netname, name, sizeof( ent->r.client->pers.netname ) );
}

/*
* think_MoveTypeSwitcher - Used to add a delay to bunnyhop style changes
*/
void think_MoveTypeSwitcher( edict_t *ent )
{
	edict_t *owner;

	if( ent->s.ownerNum > 0 && ent->s.ownerNum <= game.maxclients )
	{
		owner = &game.edicts[ent->s.ownerNum];
		if( owner->r.client )
		{
			owner->r.client->pers.movestyle = owner->r.client->pers.movestyle_latched;
			ClientUserinfoChanged( owner, owner->r.client->pers.userinfo );
			G_PrintMsg( owner, "Your movement style has been updated to %i\n", owner->r.client->pers.movestyle );
		}
	}

	G_FreeEdict( ent );
}

//===========
//ClientUserInfoChanged
//
//called whenever the player updates a userinfo variable.
//
//The game can override any of the settings in place
//(forcing skins or names, etc) before copying it off.
//============
void ClientUserinfoChanged( edict_t *ent, char *userinfo )
{
	char *s;
	char oldname[MAX_INFO_VALUE];
	gclient_t *cl;
	char playerString[MAX_CONFIGSTRING_CHARS];
	int rgbcolor, i;

	assert( ent && ent->r.client );
	assert( userinfo && Info_Validate( userinfo ) );

	// check for malformed or illegal info strings
	if( !Info_Validate( userinfo ) )
	{
		trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Invalid userinfo" );
		return;
	}

	cl = ent->r.client;

	// ip
	s = Info_ValueForKey( userinfo, "ip" );
	if( !s )
	{
		trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client IP" );
		return;
	}
	Q_strncpyz( cl->pers.ip, s, sizeof( cl->pers.ip ) );

	// socket
	s = Info_ValueForKey( userinfo, "socket" );
	if( !s )
	{
		trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Server didn't provide client socket" );
		return;
	}
	Q_strncpyz( cl->pers.socket, s, sizeof( cl->pers.socket ) );

	// color
	s = Info_ValueForKey( userinfo, "color" );
	if( s )
	{
		rgbcolor = COM_ReadColorRGBString( s );
	}
	else
	{
		rgbcolor = -1;
	}

	if( rgbcolor != -1 )
	{
		Vector4Set( cl->pers.color, COLOR_R( rgbcolor ), COLOR_G( rgbcolor ), COLOR_B( rgbcolor ), 255 );
	}
	else
	{
		//G_PrintMsg( ent, "Warning: Bad 'color' cvar values. Using white\n" );
		Vector4Set( cl->pers.color, 255, 255, 255, 255 );
	}

	// set name, it's validated and possibly changed first
	Q_strncpyz( oldname, cl->pers.netname, sizeof( oldname ) );
	G_SetName( ent, Info_ValueForKey( userinfo, "name" ) );
	if( oldname[0] && Q_stricmp( oldname, cl->pers.netname ) && !cl->pers.tv )
		G_PrintMsg( NULL, "%s%s is now known as %s%s\n", oldname, S_COLOR_WHITE, cl->pers.netname, S_COLOR_WHITE );
	if( !Info_SetValueForKey( userinfo, "name", cl->pers.netname ) )
	{
		trap_DropClient( ent, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (name)" );
		return;
	}

	// handedness
	s = Info_ValueForKey( userinfo, "hand" );
	if( !s )
	{
		cl->pers.hand = 2;
	}
	else
	{
		cl->pers.hand = bound( atoi( s ), 0, 2 );
	}

	s = Info_ValueForKey( userinfo, "cg_oldMovement" );
	if( s )
	{
		i = bound( atoi( s ), 0, GS_MAXBUNNIES - 1 );
		if( trap_GetClientState( PLAYERNUM(ent) ) < CS_SPAWNED )
		{
			if( i != cl->pers.movestyle )
				cl->pers.movestyle = cl->pers.movestyle_latched = i;
		}
		else if( cl->pers.movestyle_latched != cl->pers.movestyle )
		{
			G_PrintMsg( ent, "A movement style change is already in progress. Please wait.\n" );
		}
		else if( i != cl->pers.movestyle_latched )
		{
			cl->pers.movestyle_latched = i;
			if( cl->pers.movestyle_latched != cl->pers.movestyle )
			{
				edict_t *switcher;

				switcher = G_Spawn();
				switcher->think = think_MoveTypeSwitcher;
				switcher->nextthink = level.time + 10000;
				switcher->s.ownerNum = ENTNUM( ent );
				G_PrintMsg( ent, "Movement style will change in 10 seconds.\n" );
			}
		}
	}

	// update client information in cgame
	playerString[0] = 0;
	Info_SetValueForKey( playerString, "name", cl->pers.netname );
	Info_SetValueForKey( playerString, "hand", va( "%i", cl->pers.hand ) );
	Info_SetValueForKey( playerString, "mt", va( "%i", cl->pers.movestyle ) );
	Info_SetValueForKey( playerString, "color",
	                    va( "%i %i %i", cl->pers.color[0], cl->pers.color[1], cl->pers.color[2] ) );
	trap_ConfigString( CS_PLAYERINFOS + PLAYERNUM( ent ), playerString );

	// set skin
	if( ent->r.client->pers.connected )
		G_Teams_AssignTeamSkin( ent, userinfo );

	// fov
	s = Info_ValueForKey( userinfo, "fov" );
	if( !s )
	{
		cl->pers.fov = 90;
	}
	else
	{
		cl->pers.fov = atoi( s );
		clamp( cl->pers.fov, 60, 160 );
	}

	s = Info_ValueForKey( userinfo, "zoomfov" );
	if( !s )
	{
		cl->pers.zoomfov = 30;
	}
	else
	{
		cl->pers.zoomfov = atoi( s );
		clamp( cl->pers.zoomfov, 1, 60 );
	}

#ifdef UCMDTIMENUDGE
	s = Info_ValueForKey( userinfo, "cl_ucmdTimeNudge" );
	if( !s )
	{
		cl->pers.ucmdTimeNudge = 0;
	}
	else
	{
		cl->pers.ucmdTimeNudge = atoi( s );
		clamp( cl->pers.ucmdTimeNudge, -MAX_UCMD_TIMENUDGE, MAX_UCMD_TIMENUDGE );
	}
#endif

	// save off the userinfo in case we want to check something later
	Q_strncpyz( cl->pers.userinfo, userinfo, sizeof( cl->pers.userinfo ) );
}


//===========
//ClientConnect
//
//Called when a player begins connecting to the server.
//The game can refuse entrance to a client by returning false.
//If the client is allowed, the connection process will continue
//and eventually get to ClientBegin()
//Changing levels will NOT cause this to be called again, but
//loadgames will.
//============
qboolean ClientConnect( edict_t *ent, char *userinfo, qboolean fakeClient, qboolean tvClient )
{
	char *value;
	char message[MAX_STRING_CHARS];

	assert( ent );
	assert( userinfo && Info_Validate( userinfo ) );
	assert( Info_ValueForKey( userinfo, "ip" ) && Info_ValueForKey( userinfo, "socket" ) );

	// verify that server gave us valid data
	if( !Info_Validate( userinfo ) )
	{
		Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) );
		Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) );
		Info_SetValueForKey( userinfo, "rejmsg", "Invalid userinfo" );
		return qfalse;
	}

	if( !Info_ValueForKey( userinfo, "ip" ) )
	{
		Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) );
		Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) );
		Info_SetValueForKey( userinfo, "rejmsg", "Error: Server didn't provide client IP" );
		return qfalse;
	}

	if( !Info_ValueForKey( userinfo, "ip" ) )
	{
		Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) );
		Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) );
		Info_SetValueForKey( userinfo, "rejmsg", "Error: Server didn't provide client socket" );
		return qfalse;
	}

	// check to see if they are on the banned IP list
	value = Info_ValueForKey( userinfo, "ip" );
	if( SV_FilterPacket( value ) )
	{
		Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_GENERAL ) );
		Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) );
		Info_SetValueForKey( userinfo, "rejmsg", "You're banned from this server" );
		return qfalse;
	}

	// check for a password
	value = Info_ValueForKey( userinfo, "password" );
	if( !fakeClient && ( *password->string && ( !value || strcmp( password->string, value ) ) ) )
	{
		Info_SetValueForKey( userinfo, "rejtype", va( "%i", DROP_TYPE_PASSWORD ) );
		Info_SetValueForKey( userinfo, "rejflag", va( "%i", 0 ) );
		if( value && value[0] )
		{
			Info_SetValueForKey( userinfo, "rejmsg", "Incorrect password" );
		}
		else
		{
			Info_SetValueForKey( userinfo, "rejmsg", "Password required" );
		}
		return qfalse;
	}

	// they can connect

	// make sure we start with known default
	if( fakeClient )
		ent->r.svflags = SVF_FAKECLIENT;
	else
		ent->r.svflags = SVF_NOCLIENT;
	ent->s.team = TEAM_SPECTATOR;

	//ent->r.client = game.clients + (ent - game.edicts - 1);
	ent->r.client = game.clients + PLAYERNUM( ent );
	memset( ent->r.client, 0, sizeof( gclient_t ) );
	InitClientPersistant( ent->r.client );
	InitClientResp( ent->r.client );
	ClientUserinfoChanged( ent, userinfo );
	ent->r.client->pers.connected = qtrue;
	ent->r.client->pers.connecting = qtrue;
	ent->r.client->pers.tv = tvClient;
	ent->r.client->ps.pmove.pm_type = PM_SPECTATOR;

	Q_snprintfz( message, sizeof( message ), "%s%s connected", ent->r.client->pers.netname, S_COLOR_WHITE );
	G_PrintMsg( NULL, "%s\n", message );
#ifdef TCP_SUPPORT
	G_Printf( "%s%s connected from %s (%s)\n", ent->r.client->pers.netname, S_COLOR_WHITE,
	          ent->r.client->pers.ip, ent->r.client->pers.socket );
#else
	G_Printf( "%s%s connected from %s\n", ent->r.client->pers.netname, S_COLOR_WHITE, ent->r.client->pers.ip );
#endif

	return qtrue;
}

//===========
//ClientDisconnect
//
//Called when a player drops from the server.
//Will not be called between levels.
//============
void ClientDisconnect( edict_t *ent, const char *reason )
{
	int team;

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

	// hide laser
	if( ent->s.weapon == WEAP_LASERGUN )
		G_HideClientLaser( ent );

	//if( G_Teams_Coach_IsCoach( ent ) )
	//	G_Teams_Coach_Resign( ent );

	for( team = TEAM_PLAYERS; team < GS_MAX_TEAMS; team++ )
		G_Teams_UnInvitePlayer( team, ent );

	if( !reason )
	{
		G_PrintMsg( NULL, "%s %sdisconnected\n", ent->r.client->pers.netname, S_COLOR_WHITE );
	}
	else
	{
		G_PrintMsg( NULL, "%s %sdisconnected (%s%s)\n", ent->r.client->pers.netname, S_COLOR_WHITE, reason,
		            S_COLOR_WHITE );
	}

	G_Gametype_CTF_DeadDropFlag( ent );

	// send effect
	if( ent->s.team > TEAM_SPECTATOR )
		G_SpawnTeleportEffect( ent );

	//MbotGame[start]
	G_FreeAI( ent );
	AI_EnemyRemoved( ent );
	//G_FakeClientDisconnect(ent); // remove the svflag (and more)
	//[end]

	ent->s.modelindex = ent->s.modelindex2 = 0;
	ent->r.solid = SOLID_NOT;
	ent->r.inuse = qfalse;
	ent->r.svflags = SVF_NOCLIENT;
	ent->classname = NULL;
	ent->s.team = TEAM_SPECTATOR;
	ent->s.weapon = WEAP_NONE;
	// free scores
	match.scores[PLAYERNUM( ent )].score = 0;
	match.scores[PLAYERNUM( ent )].deaths = 0;
	match.scores[PLAYERNUM( ent )].kills = 0;
	match.scores[PLAYERNUM( ent )].suicides = 0;

	memset( ent->r.client, 0, sizeof( *ent->r.client ) );

	trap_ConfigString( CS_PLAYERINFOS+PLAYERNUM( ent ), "" );
	GClip_UnlinkEntity( ent );

	G_Teams_UpdateMembersList();
	G_Match_CheckReadys();
}


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

void G_PredictedEvent( int entNum, int ev, int parm )
{
	edict_t	*ent;

	ent = &game.edicts[entNum];
	switch( ev )
	{
	case EV_FALL:
	{
		vec3_t dir = { 0.0f, 0.0f, 1.0f };
		int dflags, damage;
		dflags = 0;
		damage = parm;

		if( ent->fall_fatal )
		{                       // it will correct the predicted one, but doesn't matter, cause he's dead
			damage = ceil( ent->health ) - GIB_HEALTH + 1;
			dflags = DAMAGE_NO_PROTECTION;
		}
		if( damage && ( g_allow_falldamage->integer || ent->fall_fatal ) )
			T_Damage( ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, dflags, MOD_FALLING );
		G_AddEvent( ent, ev, damage, qtrue );
	}
		break;
	case EV_JUMP_PAD:
		G_AddEvent( ent, ev, parm, qfalse );
		break;
	default:
		G_AddEvent( ent, ev, parm, qtrue );
		break;
	}
}

static void G_ClientPMoveFromEntity( edict_t *ent, pmove_t *pm )
{
	gclient_t *client = ent->r.client;

	memset( pm, 0, sizeof( pmove_t ) );
	pm->s = &client->ps.pmove;

	//update from entity state
	VectorCopy( ent->s.origin, pm->s->origin );
	VectorCopy( ent->velocity, pm->s->velocity );
	VectorCopy( ent->r.mins, pm->mins );
	VectorCopy( ent->r.maxs, pm->maxs );

	pm->viewheight = ent->viewheight;

	// in QFusion this was applied to both ps.pmove and pm.s, so it never activated snap initial
	{
		if( match.state >= MATCH_STATE_POSTMATCH )
		{
			pm->s->pm_type = PM_FREEZE;
			pm->s->pm_flags |= PMF_NO_PREDICTION;
		}
		else if( ent->r.client->chase.active )
		{
			pm->s->pm_type = PM_CHASECAM;
			pm->s->pm_flags |= PMF_NO_PREDICTION;
		}
		else if( ent->r.solid == SOLID_NOT && ( ent->r.svflags & SVF_NOCLIENT ) )
		{
			pm->s->pm_type = PM_SPECTATOR;
			pm->s->pm_flags &= ~PMF_NO_PREDICTION;
		}
		else if( gtimeout.active )
		{
			pm->s->pm_type = PM_FREEZE;
			pm->s->pm_flags |= PMF_NO_PREDICTION;
		}
		else if( ent->s.type == ET_GIB )
		{
			pm->s->pm_type = PM_GIB;
			pm->s->pm_flags |= PMF_NO_PREDICTION;
		}
		else if( G_IsDead( ent ) && !( ent->r.svflags & SVF_NOCLIENT ) )
		{                                                      //ent->deadflag ) {
			pm->s->pm_type = PM_FREEZE; // dead is a 3rd person cam now
			pm->s->pm_flags |= PMF_NO_PREDICTION;
		}
		else if( ent->movetype == MOVETYPE_NOCLIP )
		{
			pm->s->pm_type = PM_SPECTATOR;
			pm->s->pm_flags &= ~PMF_NO_PREDICTION;
		}
		else
		{
			pm->s->pm_type = PM_NORMAL;
			pm->s->pm_flags &= ~PMF_NO_PREDICTION;
		}

		pm->s->gravity = g_gravity->value;
	}
}

static void G_ClientUpdateEntityFromPMove( edict_t *ent, pmove_t *pm )
{
	gclient_t *client = ent->r.client;
	int i, j;

	VectorCopy( pm->s->origin, ent->s.origin );
	VectorCopy( pm->s->velocity, ent->velocity );
	VectorCopy( pm->mins, ent->r.mins );
	VectorCopy( pm->maxs, ent->r.maxs );

	ent->viewheight = client->ps.viewheight = pm->viewheight;
	ent->waterlevel = pm->waterlevel;
	ent->watertype = pm->watertype;
	if( pm->groundentity != -1 )
	{
		G_AwardResetPlayerComboStats( ent );

		ent->groundentity = &game.edicts[pm->groundentity];
		ent->groundentity_linkcount = ent->groundentity->r.linkcount;
	}
	else
	{
		ent->groundentity = NULL;
	}

	// angles
	if( pm->s->pm_type < PM_DEAD )
	{
		VectorCopy( pm->viewangles, client->ps.viewangles );
		VectorCopy( client->ps.viewangles, ent->s.angles );
	}

	if( game.gametype == GAMETYPE_RACE )
	{
		if( ent->r.client->resp.race_active )
		{
			ent->r.client->resp.race_time += pm->cmd.msec;
		}
		else
		{
			ent->r.client->resp.race_time = pm->cmd.msec; // in case it starts
		}
	}

	GClip_LinkEntity( ent );

	// touch the world
	if( ent->movetype != MOVETYPE_NOCLIP )
	{
		edict_t *other;

		// touch other objects
		for( i = 0; i < pm->numtouch; i++ )
		{
			other = &game.edicts[pm->touchents[i]];
			for( j = 0; j < i; j++ )
			{
				if( &game.edicts[pm->touchents[j]] == other )
					break;
			}
			if( j != i )
				continue; // duplicated
			if( !other->touch )
				continue;
			// player can't touch projectiles, only projectiles can touch the player
			//if( other->r.svflags & SVF_PROJECTILE )
			//	continue;
			other->touch( other, ent, NULL, 0 );
		}
	}
}

static void ClientMakePlrkeys( gclient_t *client, usercmd_t *ucmd )
{
	if( !client )
		return;

	client->plrkeys = 0; // clear it first

	if( ucmd->forwardmove > 0 )
		client->plrkeys |= ( 1 << KEYICON_FORWARD );
	if( ucmd->forwardmove < 0 )
		client->plrkeys |= ( 1 << KEYICON_BACKWARD );
	if( ucmd->sidemove > 0 )
		client->plrkeys |= ( 1 << KEYICON_RIGHT );
	if( ucmd->sidemove < 0 )
		client->plrkeys |= ( 1 << KEYICON_LEFT );
	if( ucmd->upmove > 0 )
		client->plrkeys |= ( 1 << KEYICON_JUMP );
	if( ucmd->upmove < 0 )
		client->plrkeys |= ( 1 << KEYICON_CROUCH );
	if( ucmd->buttons & BUTTON_ATTACK )
		client->plrkeys |= ( 1 << KEYICON_FIRE );
	if( ucmd->buttons & BUTTON_SPECIAL )
		client->plrkeys |= ( 1 << KEYICON_SPECIAL );
}

//==============
//ClientMultiviewChanged
//
//This will be called when client tries to change multiview mode
//Mode change can be disallowed by returning qfalse
//==============
qboolean ClientMultiviewChanged( edict_t *ent, qboolean multiview )
{
	ent->r.client->pers.multiview = multiview;
	return qtrue;
}

//==============
//ClientThink
//
//This will be called once for each client frame, which will
//usually be a couple times for each server frame.
//==============
void ClientThink( edict_t *ent, usercmd_t *ucmd, int timeDelta )
{
	gclient_t *client;
	int i;
	static pmove_t pm;
	pmove_state_t oldpms;
	int delta, count;

	client = ent->r.client;
	client->buttons = 0;
	VectorCopy( ucmd->angles, client->pers.cmd_angles );

	// anti-lag
	if( ent->r.svflags & SVF_FAKECLIENT )
	{
		client->timeDelta = 0;
	}
	else
	{
		int nudge;

		// add smoothing to timeDelta between the last few ucmds and a small fine-tuning nudge.
		// fixme: find where this nudge comes from. BTW, it seems to be the same amount in which
		// the pings are too big.
		nudge = 30 + g_antilag_timenudge->integer;
		timeDelta += nudge;
		clamp( timeDelta, -g_antilag_maxtimedelta->integer, 0 );
		client->timeDeltas[client->timeDeltasHead & G_MAX_TIME_DELTAS_MASK] = timeDelta;
		client->timeDeltasHead++;

		// smooth it
		i = client->timeDeltasHead - 6;
		if( i < 0 ) i = 0;
		for( count = 0, delta = 0; i < client->timeDeltasHead; i++, count++ )
		{
			delta += client->timeDeltas[i & G_MAX_TIME_DELTAS_MASK];
		}

		delta /= count;

		client->timeDelta = ( delta + timeDelta ) * 0.5;

#ifdef UCMDTIMENUDGE
		client->timeDelta += client->pers.ucmdTimeNudge;
#endif
	}

	clamp( client->timeDelta, -g_antilag_maxtimedelta->integer, 0 );

	oldpms = client->ps.pmove;

	// set up for pmove
	G_ClientPMoveFromEntity( ent, &pm );

	if( memcmp( &client->old_pmove, pm.s, sizeof( pmove_state_t ) ) )
	{
		pm.snapinitial = qtrue;
		//G_Printf("pmove changed!\n");
	}

	pm.cmd = *ucmd;
	pm.max_walljumps = GS_GameType_MaxWallJumps( game.gametype );
	pm.movestyle = client->pers.movestyle;
	pm.passent = ENTNUM( ent );
	pm.contentmask = MASK_PLAYERSOLID;
	// modify the mask under special conditions
	if( G_IsDead( ent ) )
		pm.contentmask = MASK_DEADSOLID;
	if( game.gametype == GAMETYPE_RACE )
		pm.contentmask = MASK_DEADSOLID;
	if( ent->movetype == MOVETYPE_NOCLIP )
		pm.contentmask = 0;

	// check for special cases where pmove is disabled
	if( match.state >= MATCH_STATE_POSTMATCH || pm.s->pm_type >= PM_FREEZE )
	{
		for( i = 0; i < 3; i++ )  // force the delta angle
			pm.s->delta_angles[i] = ANGLE2SHORT( ent->s.angles[i] ) - ucmd->angles[i];

		if( match.state == MATCH_STATE_WAITEXIT && ( ucmd->buttons & BUTTON_ATTACK ) )
		{
			// can exit intermission after five seconds
			level.exitNow = qtrue;
		}
		else if( G_IsDead( ent ) )
		{
			if( ent->deathTimeStamp + g_respawn_delay_min->integer <= level.time )
			{
				client->buttons = ucmd->buttons;
			}
		}
		client->old_pmove = oldpms;
		return;
	}

	// perform a pmove
	Pmove( &pm );

	// save results of pmove
	client->old_pmove = *pm.s;

	G_ClientUpdateEntityFromPMove( ent, &pm );

	if( client->ps.pmove.stats[PM_STAT_NOUSERCONTROL] <= 0 )
		client->buttons = ucmd->buttons;

	// generating plrkeys (optimized for net communication)
	ClientMakePlrkeys( client, ucmd );

	if( client->buttons & BUTTON_ATTACK )
		Think_Weapon( ent, 0 );
}

//==============
//G_CheckClientRespawnClick
//==============
void G_CheckClientRespawnClick( edict_t *ent )
{
	if( !ent->r.inuse || !ent->r.client || !G_IsDead( ent ) )
		return;

	if( match.state >= MATCH_STATE_POSTMATCH )
		return;

	if( trap_GetClientState( PLAYERNUM( ent ) ) >= CS_SPAWNED )
	{
		// clicked
		if( ent->snap.buttons & BUTTON_ATTACK )
		{
			if( level.time > ent->deathTimeStamp + g_respawn_delay_min->integer )
			{
				if( game.gametype == GAMETYPE_CA && ( match.state == MATCH_STATE_PLAYTIME || match.state == MATCH_STATE_COUNTDOWN ) )
				{
					// cannot respawn, but go into chasecam mode
					ent->health = 0;
					ent->s.weapon = 0;
					if( ent->ai.type != AI_ISBOT )
					{
						if( !ent->r.client->chase.target || game.edicts[ent->r.client->chase.target].s.team != ent->s.team )
						{
							G_ChasePlayer( ent, NULL, qtrue, 0 );
						}
					}
#ifdef DUEL_ARENA
				}
				else if( game.gametype == GAMETYPE_DA && ( match.state == MATCH_STATE_PLAYTIME || match.state == MATCH_STATE_COUNTDOWN ) )
				{
					// cannot respawn, but go into chasecam mode
					ent->health = 0;
					ent->s.weapon = 0;
					if( ent->ai.type != AI_ISBOT )
					{
						if( !ent->r.client->chase.target || game.edicts[ent->r.client->chase.target].s.team != ent->s.team )
						{
							G_ChasePlayer( ent, NULL, qtrue, 0 );
						}
					}
#endif
				}
				else
				{
					G_Gametype_ClientRespawn( ent );
				}
			}
		}
		// didn't click, but too much time passed
		else if( g_respawn_delay_max->integer && ( level.time > ent->deathTimeStamp + g_respawn_delay_max->integer ) )
		{
			if( game.gametype == GAMETYPE_CA && ( match.state == MATCH_STATE_PLAYTIME || match.state == MATCH_STATE_COUNTDOWN ) )
			{
				// cannot respawn
				ent->health = 0;
				ent->s.weapon = 0;
				if( ent->ai.type != AI_ISBOT )
				{
					if( !ent->r.client->chase.target || game.edicts[ent->r.client->chase.target].s.team != ent->s.team )
					{
						G_ChasePlayer( ent, NULL, qtrue, 0 );
					}
				}
#ifdef DUEL_ARENA
			}
			else if( game.gametype == GAMETYPE_DA && ( match.state == MATCH_STATE_PLAYTIME || match.state == MATCH_STATE_COUNTDOWN ) )
			{
				// cannot respawn
				ent->health = 0;
				ent->s.weapon = 0;
				if( ent->ai.type != AI_ISBOT )
				{
					if( !ent->r.client->chase.target || game.edicts[ent->r.client->chase.target].s.team != ent->s.team )
					{
						G_ChasePlayer( ent, NULL, qtrue, 0 );
					}
				}
#endif
			}
			else
			{
				G_Gametype_ClientRespawn( ent );
			}
		}
		// in race, just respawn (just wait one snap)
		else if( ( game.gametype == GAMETYPE_RACE ) && ( level.time > ent->deathTimeStamp + game.snapFrameTime ) )
		{
			G_Gametype_ClientRespawn( ent );
		}
	}
}
