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

//==================
//LookAtKiller
//==================
void LookAtKiller( edict_t *self, edict_t *inflictor, edict_t *attacker )
{
	vec3_t		dir;

	if( attacker && attacker != world && attacker != self )
	{
		VectorSubtract( attacker->s.origin, self->s.origin, dir );
	}
	else if( inflictor && inflictor != world && inflictor != self )
	{
		VectorSubtract( inflictor->s.origin, self->s.origin, dir );
	}
	else
	{
		self->r.client->killer_yaw = self->s.angles[YAW];
		return;
	}

	if( dir[0] ) {
		self->r.client->killer_yaw = RAD2DEG( atan2(dir[1], dir[0]) );
	} else {
		self->r.client->killer_yaw = 0;
		if( dir[1] > 0 )
			self->r.client->killer_yaw = 90;
		else if( dir[1] < 0 )
			self->r.client->killer_yaw = -90;
	}
	if( self->r.client->killer_yaw < 0 )
		self->r.client->killer_yaw += 360;
}

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

//==================
//G_DeadBody_ThirdPersonView
//==================
void G_DeadBody_ThirdPersonView( vec3_t vieworg, vec3_t viewangles, edict_t *passent )
{
	float	thirdPersonRange = 60;
	float	thirdPersonAngle = 0;
	float	dist, f, r;
	vec3_t	dest, stop;
	vec3_t	chase_dest;
	trace_t	trace;
	vec3_t	mins = { -4, -4, -4 };
	vec3_t	maxs = { 4, 4, 4 };
	vec3_t v_forward, v_right, v_up;

	AngleVectors( viewangles, v_forward, v_right, v_up );

	// calc exact destination
	VectorCopy( vieworg, chase_dest );
	r = DEG2RAD( thirdPersonAngle );
	f = -cos( r );
	r = -sin( r );
	VectorMA( chase_dest, thirdPersonRange * f, v_forward, chase_dest );
	VectorMA( chase_dest, thirdPersonRange * r, v_right, chase_dest );
	chase_dest[2] += 8;

	// find the spot the player is looking at
	VectorMA( vieworg, 512, v_forward, dest );
	trap_Trace( &trace, vieworg, mins, maxs, dest, passent, MASK_SOLID );

	// calculate pitch to look at the same spot from camera
	VectorSubtract( trace.endpos, vieworg, stop );
	dist = sqrt( stop[0] * stop[0] + stop[1] * stop[1] );
	if( dist < 1 )
		dist = 1;
	viewangles[PITCH] = RAD2DEG( -atan2(stop[2], dist) );
	viewangles[YAW] -= thirdPersonAngle;
	AngleVectors( viewangles, v_forward, v_right, v_up );

	// move towards destination
	trap_Trace( &trace, vieworg, mins, maxs, chase_dest, passent, MASK_SOLID );

	if( trace.fraction != 1.0 ) {
		VectorCopy( trace.endpos, stop );
		stop[2] += ( 1.0 - trace.fraction ) * 32;
		trap_Trace( &trace, vieworg, mins, maxs, stop, passent, MASK_SOLID );
		VectorCopy( trace.endpos, chase_dest );
	}

	VectorCopy( chase_dest, vieworg );
}

//=============
//G_Client_DeadView
//=============
void G_Client_DeadView( edict_t *ent )
{
	edict_t	*body;
	trace_t	trace;

	if( !ent->deadflag )
		return;

	// find the body
	for( body = game.edicts + game.maxclients; ENTNUM(body) < game.maxclients + BODY_QUEUE_SIZE + 1; body++ ) {
		if( !body->r.inuse || body->r.svflags & SVF_NOCLIENT )
			continue;
		if( body->activator == ent ) // this is our body
			break;
	}

	if( body->activator != ent ) { // ran all the list and didn't find our body
		return;
	}

	// move us to body position
	VectorCopy( body->s.origin, ent->s.origin );

	// see if our killer is still in view
	if( body->enemy && (body->enemy != ent) ) {
		trap_Trace( &trace, ent->s.origin, vec3_origin, vec3_origin, body->enemy->s.origin, body, MASK_OPAQUE );
		if( trace.fraction != 1.0f ) {
			body->enemy = NULL;
		} else {
			LookAtKiller( ent, NULL, body->enemy );
		}
	} else { // nobody killed us, so just circle around the body ?

	}

	ent->r.client->ps.viewangles[ROLL] = 0;
	ent->r.client->ps.viewangles[PITCH] = 0;
	ent->r.client->ps.viewangles[YAW] = ent->r.client->killer_yaw;
	G_DeadBody_ThirdPersonView( ent->s.origin, ent->r.client->ps.viewangles, body );
}

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

	// find bodies linked to us
	for( body = game.edicts + game.maxclients; ENTNUM(body) < game.maxclients + BODY_QUEUE_SIZE + 1; body++ ) 
	{
		if( !body->r.inuse || body->r.svflags & SVF_NOCLIENT )
			continue;

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

//==================
//InitBodyQue 
//==================
void InitBodyQue( 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 
//==================
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, 3, damage );
	self->s.origin[2] -= 48;
	ThrowClientHead( self, damage );
}

//==================
//body_ready 
//autodestruct the body 
//==================
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_PLAYER ) {
		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->s.modelindex = 0;
	trap_LinkEntity( self );
}

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

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

	trap_UnlinkEntity(ent);

	contents = trap_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;

	// FIXME: send an effect on the removed body
	if( body->s.modelindex && body->s.type == ET_PLAYER )
		ThrowSmallPileOfGibs( body, 3, 10 );

	trap_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->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_PLAYER and bodies, but not in ET_GENERIC
	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.origin2, body->s.origin2 );
	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
		memset( &body->pmAnim, 0, sizeof(body->pmAnim) );
		body->pmAnim.anim_priority[LOWER] = ANIM_DEATH;
		body->pmAnim.anim_priority[UPPER] = ANIM_DEATH;
		body->pmAnim.anim_priority[HEAD] = ANIM_DEATH;
		body->s.frame = 0;

		body->nextthink = level.timemsec + 5000 + random()*10000;
	}
	else if( ent->s.type == ET_PLAYER ) 
	{
		// copy the model
		body->s.modelindex = ent->s.modelindex;
		body->s.bodyOwner = ent->s.number; // bodyOwner is the same as modelindex2
		body->s.skinnum = ent->s.skinnum;

		// launch the death animation on the body
		{
			static int i;
			i = (i+1)%3;
			switch (i)
			{
			case 0:
				body->pmAnim.anim[LOWER] = BOTH_DEAD1;
				body->pmAnim.anim[UPPER] = BOTH_DEAD1;
				body->pmAnim.anim[HEAD] = 0;
				break;
			case 1:
				body->pmAnim.anim[LOWER] = BOTH_DEAD2;
				body->pmAnim.anim[UPPER] = BOTH_DEAD2;
				body->pmAnim.anim[HEAD] = 0;
				break;
			case 2:
				body->pmAnim.anim[LOWER] = BOTH_DEAD3;
				body->pmAnim.anim[UPPER] = BOTH_DEAD3;
				body->pmAnim.anim[HEAD] = 0;
				break;
			}

			//send the death style (1, 2 or 3) inside parameters
			G_AddEvent( body, EV_DIE, i, qtrue );
			body->pmAnim.anim_priority[LOWER] = ANIM_DEATH;
			body->pmAnim.anim_priority[UPPER] = ANIM_DEATH;
			body->pmAnim.anim_priority[HEAD] = ANIM_DEATH;
			body->s.frame = ((body->pmAnim.anim[LOWER] &0x3F)|(body->pmAnim.anim[UPPER] &0x3F)<<6|(body->pmAnim.anim[HEAD] &0xF)<<12);
		}

		body->think = body_ready;
		body->takedamage = DAMAGE_NO;
		body->nextthink = level.timemsec + 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.timemsec + 5000 + random()*10000;
	}

	trap_LinkEntity( body );
	return body;
}

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

//==================
//TossClientWeapon
//==================
void TossClientWeapon( edict_t *self )
{
	gitem_t		*item;
	edict_t		*drop;
	qboolean	quad;
	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.timemsec + 1000) );

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

	if( item )
	{
		self->r.client->v_angle[YAW] -= spread;
		drop = Drop_Item( self, item );
		self->r.client->v_angle[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->v_angle[YAW] += spread;
		drop = Drop_Item( self, game.items[POWERUP_QUAD] );
		self->r.client->v_angle[YAW] -= spread;
		if( drop ) {
			drop->spawnflags |= DROPPED_PLAYER_ITEM;
			drop->touch = Touch_Item;
			drop->nextthink = level.timemsec + (self->r.client->quad_timeout - level.timemsec);	
			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) )
		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( !self->r.client->inventory[AMMO_CELLS] &&
		(!active_ammo_tag || self->r.client->inventory[active_ammo_tag]) )
		return;

	//create the entity
	yawoffset = random() * self->r.client->v_angle[YAW] * 0.5;
	self->r.client->v_angle[YAW] -= yawoffset;
	pack = Drop_Item( self, item );
	self->r.client->v_angle[YAW] += yawoffset;
	if( pack ) {
		pack->spawnflags |= DROPPED_PLAYER_ITEM;

		pack->invpak[AMMO_CELLS] = self->r.client->inventory[AMMO_CELLS];
		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;

	//trap_UnlinkEntity( ent );
	ent->r.solid = SOLID_NOT;

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

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

	// 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->grenade_blew_up = qfalse;
	ent->r.client->grenade_time = 0;
	ent->r.client->weapon_sound = 0;
	ent->r.client->weapon_powered = 0;

	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_NOCLIP;
	ent->deathtimestamp = level.timemsec;

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

	ent->deadflag = DEAD_DEAD;
	trap_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));
}
/*
=======================================================================

  SelectSpawnPoint

=======================================================================
*/

//jal: moved spawnpoints to their own file, so this one is a little smaller

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

//newgametypes[start]
int ClientRespawn( edict_t *self )
{
	weapon_info_t *weapon;

	self->r.svflags &= ~SVF_NOCLIENT;
	PutClientInServer( self );
	G_AddEvent( self, EV_TELEPORT, 0, qtrue );
	
	// add a teleportation effect
	G_SpawnTeleportEffect( self );
	
	// hold in place briefly
	self->r.client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
	self->r.client->ps.pmove.pm_time = 14;

	G_FakeClientRespawn( self );	//MbotGame

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

	//give default items
	memset( &self->r.client->inventory, 0, sizeof(self->r.client->inventory) );
	if( g_instagib->integer ) {
		self->r.client->inventory[WEAP_ELECTROBOLT] = 1;
		self->r.client->inventory[AMMO_BOLTS] = 1;
		self->r.client->inventory[AMMO_WEAK_BOLTS] = 1;
		self->r.client->latched_weapon = WEAP_ELECTROBOLT;
	}
	else {
		if( match.state == MATCH_STATE_WARMUP ) {
			int	i;

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

				weapon = G_FiredefForWeapon( i );
				if( weapon ) {
					self->r.client->inventory[i] = 1;
					if( weapon->firedef_weak->ammo_id )
						self->r.client->inventory[weapon->firedef_weak->ammo_id] = weapon->firedef_weak->ammo_max;
					if( weapon->firedef->ammo_id )
						self->r.client->inventory[weapon->firedef->ammo_id] = weapon->firedef->ammo_max;
				}
			}

			self->r.client->armortag = ARMOR_YA;
			self->r.client->armor = 100;

		} else {
			self->r.client->inventory[WEAP_GUNBLADE] = 1;
			if( G_FiredefForAmmo(AMMO_CELLS) )
				self->r.client->inventory[AMMO_CELLS] = G_FiredefForAmmo( AMMO_CELLS )->ammo_max;
			else
				self->r.client->inventory[AMMO_CELLS] = 0;
			self->r.client->inventory[AMMO_WEAK_GUNBLADE] = 0;
		}
		self->r.client->latched_weapon = WEAP_GUNBLADE;
	}
	ChangeWeapon( self );
	self->r.client->weaponstate = WEAPON_ACTIVATING;
	self->r.client->weapon_nexttime = level.timemsec + 200;

	return qtrue;
}

void respawn( edict_t *self )
{
	self->r.client->respawn_timestamp = level.timemsec;

	if( G_Gametype_ClientRespawn(self) ) {
		self->r.client->resp.respawnCount++;
		//G_Printf( "%s respawn count %i\n", self->r.client->pers.netname, self->r.client->resp.respawnCount );
		return;
	}

	//MbotGame[start]
	if (self->r.svflags & SVF_FAKECLIENT)//should never happen
		BOT_RemoveBot( self->r.client->pers.netname );
	//[end]

	// restart the entire server
	trap_AddCommandString( "menu_loadgame\n" );
}
//newgametypes[end]
//==============================================================


/*
===========
PutClientInServer

Called when a player connects to a server or respawns in
a deathmatch.
============
*/
void PutClientInServer( edict_t *ent )
{
	int		index;
	vec3_t	spawn_origin, spawn_angles;
	gclient_t	*client;
	int		i;
	client_respawn_t		resp;
	client_teamchange_t		teamchange;
	client_persistant_t		pers;
	char	userinfo[MAX_INFO_STRING];

	trap_UnlinkEntity( ent );

	// find a spawn point
	// do it before setting health back up, so farthest
	// ranging doesn't count this client // wsw: Medar: Uh?
	SelectSpawnPoint( ent, spawn_origin, spawn_angles );

	index = ent-game.edicts-1;
	client = ent->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;
	ClientUserinfoChanged( ent, userinfo );

	// clear entity values
	ent->groundentity = NULL;
	ent->r.client = &game.clients[index];
	ent->takedamage = DAMAGE_AIM;
	ent->movetype = MOVETYPE_WALK;
	ent->viewheight = playerbox_stand_viewheight;
	ent->r.inuse = qtrue;

	//MbotGame[start]
	if( ent->ai.type == AI_ISBOT )
		ent->classname = "bot";
	else if( ent->r.svflags & SVF_FAKECLIENT )
		ent->classname = "fakeclient";
	else//[end]
		ent->classname = "player";
	
	ent->mass = 200;
	ent->r.solid = SOLID_BBOX;
	ent->deadflag = DEAD_NO;
	ent->air_finished = level.time + 12;
	ent->r.clipmask = MASK_PLAYERSOLID;
	ent->pain = player_pain;
	ent->die = player_die;
	ent->waterlevel = 0;
	ent->watertype = 0;
	ent->flags &= ~FL_NO_KNOCKBACK;
	ent->r.svflags &= ~SVF_CORPSE;

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

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

	client->ps.pmove.origin[0] = spawn_origin[0]*16;
	client->ps.pmove.origin[1] = spawn_origin[1]*16;
	client->ps.pmove.origin[2] = spawn_origin[2]*16;
	client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;

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

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

	// modelindex and skinnum are set at calling to update userinfo
	ent->s.modelindex2 = 0;

	//splitmodels (PutClientInServer): clean up animations
	ent->pmAnim.anim_priority[LOWER] = ANIM_BASIC;
	ent->pmAnim.anim_priority[UPPER] = ANIM_BASIC;
	ent->pmAnim.anim_priority[HEAD] = ANIM_BASIC;

	ent->pmAnim.anim[LOWER] = LEGS_STAND;
	ent->pmAnim.anim[UPPER] = TORSO_STAND;
	ent->pmAnim.anim[HEAD] = ANIM_NONE;

	ent->s.frame = 0;
	VectorCopy (spawn_origin, ent->s.origin);
	VectorCopy (ent->s.origin, ent->s.old_origin);

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

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

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

	//don't put spectators in the game
	if( ent->s.team == TEAM_SPECTATOR ) {
		ent->deadflag = DEAD_NO;
		ent->movetype = MOVETYPE_NOCLIP;
		ent->r.solid = SOLID_NOT;
		ent->r.svflags |= SVF_NOCLIENT;
		trap_LinkEntity( ent );
		return;
	}

	if( !KillBox(ent) )
	{	// could't spawn in?
	}

	trap_LinkEntity(ent);

	// weapon is set properly in respawn
	client->latched_weapon = WEAP_NONE;
	ChangeWeapon(ent);
}

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

	// locate ent at a spawn point
	PutClientInServer(ent);

	if( match.state >= MATCH_STATE_POSTMATCH ) {
		G_MoveClientToPostMatchScoreBoards( ent, G_SelectIntermissionSpawnPoint() );
	} else if( match.state >= MATCH_STATE_WARMUP && ent->s.team != TEAM_SPECTATOR ) {
		respawn( ent );
	}

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


/*
===========
ClientBeginSinglePlayerGame

called when a client has finished connecting in singleplayer mode, and is ready
to be placed into the game.  This will happen every level load.
============
*/
void ClientBeginSinglePlayerGame( edict_t *ent )
{
	int		i;

	// if there is already a body waiting for us (a loadgame), just
	// take it, otherwise spawn one from scratch
	if (ent->r.inuse == qtrue)
	{
		// the client has cleared the client side viewangles upon
		// connecting to the server, which is different than the
		// state when the game is saved, so we need to compensate
		// with deltaangles
		for (i=0 ; i<3 ; i++)
			ent->r.client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->r.client->ps.viewangles[i]);
	}
	else
	{
		// a spawn point will completely reinitialize the entity
		// except for the persistant data that was initialized at
		// ClientConnect() time
		G_InitEdict (ent);
		ent->classname = "player";
		InitClientResp (ent->r.client);
		InitClientTeamChange(ent->r.client);
		//newgametypes: in singleplayer join neutral team
		G_Teams_JoinTeam( ent, TEAM_PLAYERS ); //already calls putclientinserver
	}

	if( match.state >= MATCH_STATE_POSTMATCH )
	{
		G_MoveClientToPostMatchScoreBoards( ent, G_SelectIntermissionSpawnPoint() );
	}
	else
	{
		// send effect if in a multiplayer game
		if (game.maxclients > 1)
		{
			G_SpawnTeleportEffect(ent);//newgametypes
			G_PrintMsg( NULL, "%s %sentered 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;

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

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

/*
===========
G_SetName

Validate and change client's name.
============
*/
void G_SetName( edict_t *ent, char *orginal_name )
{
	static char *invalid_names[] = { "console", NULL };
	edict_t *other;
	char name[MAX_INFO_VALUE];
	char colorless[MAX_INFO_VALUE];
	int i, try;

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

	if( !orginal_name || !strlen(orginal_name) )
		Q_strncpyz( name, "Player", sizeof(name) );
	else
		Q_strncpyz( name, orginal_name, sizeof(name) );

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

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

	if( strlen(colorless) > MAX_NAME_CHARS ) {
		char *in = name;
		int len = 0;
		while( *in && len < MAX_NAME_CHARS ) {
			if( Q_IsColorString( in ) ) {
				in += 2;
			} else {
				len++;
				in++;
			}
		}
		*(in+1) = 0;
		Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) );
	}

	for( i = 0; invalid_names[i] != NULL; i++ ) {
		if( !Q_stricmp(colorless, invalid_names[i]) ) {
			Q_strncpyz( name, "Player", sizeof(name) );
			Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) );
		}
	}

	try = 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)) ) {
				// remove enough characters, so that us adding ^7(1) won't matter
				if( try == 1 ) {
					name[sizeof(name)-6] = 0;
					Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) );

					if( strlen(colorless) > MAX_NAME_CHARS - 3 ) {
						int remove = strlen(colorless) - (MAX_NAME_CHARS - 3);
						while( remove > 0 && strlen(name) >= 2 ) {
							if( Q_IsColorString(name + (strlen(name)-2)) ) {
								name[strlen(name)-2] = 0;
							} else {
								name[strlen(name)-1] = 0;
								remove--;
							}
						}
					}
				} else {
					name[strlen(name)-5] = 0;
				}

				Q_strncatz( name, va("%s(%i)", S_COLOR_WHITE, try), sizeof(name) );
				Q_strncpyz( colorless, COM_RemoveColorTokens(name), sizeof(colorless) );
				try++;
				break;
			}
		}
	} while( i != game.maxclients && try < 10 );

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

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

	cl = ent->r.client;

	// check for malformed or illegal info strings
	if( !Info_Validate(userinfo) )
	{
		//use default_playermodel
		Q_snprintfz( userinfo, sizeof(userinfo), 
			"\\name\\badinfo\\hand\\0\\model\\%s\\skin\\%s",
			DEFAULT_PLAYERMODEL, DEFAULT_PLAYERSKIN );
	}

	// color
	{
		int rgbcolor;
		
		rgbcolor = COM_ReadColorRGBString( Info_ValueForKey( userinfo, "color" ) );
		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( Q_stricmp( oldname, cl->pers.netname ) && cl->pers.connected ) {
		G_PrintMsg( NULL, "%s%s is now known as %s%s\n", oldname, S_COLOR_WHITE, cl->pers.netname,
			S_COLOR_WHITE );
	}

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

	// 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, "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
	cl->pers.fov = atoi( Info_ValueForKey(userinfo, "fov") );
	if( cl->pers.fov < 1 )
		cl->pers.fov = 90;
	else if( cl->pers.fov > 160 )
		cl->pers.fov = 160;

	// 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 )
{
	char	*value;
	char	message[MAX_STRING_CHARS];

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

	//MbotGame[start]
	if( fakeClient && !G_FakeClientBeginConnection(ent) )
		return qfalse;
	//[end]

	// check for a password
	value = Info_ValueForKey( userinfo, "password" );
	if( !fakeClient && (*password->string && strcmp(password->string, value)) ) {
		Info_SetValueForKey( userinfo, "rejtype", va("%i", DROP_TYPE_PASSWORD) );
		Info_SetValueForKey( userinfo, "rejflag", va("%i", 0) );
		if( value && strlen(value) > 0 )
			Info_SetValueForKey( userinfo, "rejmsg", "Password incorrect" );
		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;
#ifdef BATTLEYE
	if( sv_battleye->integer )
		ent->r.client->pers.battleye = (atoi(Info_ValueForKey( userinfo, "cl_battleye" )) != 0);
	else
		ent->r.client->pers.battleye = 0;
#endif

	Q_snprintfz( message, sizeof(message), "%s%s connected", ent->r.client->pers.netname, S_COLOR_WHITE );
#ifdef BATTLEYE
	if( sv_battleye->integer == 1 ) {
		if( ent->r.client->pers.battleye )
			Q_strncatz( message, " (BE enabled)", sizeof(message) );
		else
			Q_strncatz( message, " (BE disabled)", sizeof(message) );
	}
#endif
	G_PrintMsg( NULL, "%s\n", message );
	G_Printf( "%s%s connected from %s\n", ent->r.client->pers.netname, S_COLOR_WHITE,
			Info_ValueForKey (userinfo, "ip") );

	return qtrue;
}

/*
===========
ClientDisconnect

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

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

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

	G_PrintMsg( NULL, "%s %sdisconnected\n", ent->r.client->pers.netname, 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 = "disconnected";
	ent->s.team = TEAM_SPECTATOR;
	ent->s.weapon = WEAP_NONE;

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

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

	G_Teams_UpdateMembersList();
	G_Match_CheckReadys();
}


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


edict_t	*pm_passent;

// pmove doesn't need to know about passent and contentmask
void PM_trace( trace_t *tr, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end )
{
	if( !G_IsDead(pm_passent) )
	{
		// wsw: pb disable player collision in race mode (thanks Medar)
		if(game.gametype==GAMETYPE_RACE)
			trap_Trace( tr, start, mins, maxs, end, pm_passent, MASK_DEADSOLID );
		else 
			trap_Trace( tr, start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID );
	}
	else
		trap_Trace( tr, start, mins, maxs, end, pm_passent, MASK_DEADSOLID );
}

/*
==============
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 )
{
	gclient_t	*client;
	edict_t	*other;
	int		i, j, xyspeedcheck;
	pmove_t	pm;

	level.current_entity = ent;
	client = ent->r.client;

	// clear events here, because ClientThink can be called
	// several times during one server frame (G_RunFrame hasn't advanced yet)
	if( !ent->s.events[0] ) {
		ent->numEvents = 0;
		ent->eventPriority[0] = ent->eventPriority[1] = qfalse;
	}

	VectorCopy( ucmd->angles, client->pers.cmd_angles );

	if( match.state >= MATCH_STATE_POSTMATCH )
	{
		// set the delta angle
		for( i = 0 ; i < 3 ; i++ )
			client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - ucmd->angles[i];

		client->ps.pmove.pm_type = PM_FREEZE;
		// can exit intermission after five seconds
		if( match.state == MATCH_STATE_WAITEXIT 
			&& (ucmd->buttons & BUTTON_ATTACK) )
			level.exitnow = qtrue;
		return;
	}

	pm_passent = ent;

//ZOID
	if( ent->r.client->chase.active ) {
		// set the delta angle
		for( i = 0 ; i < 3 ; i++ )
			client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - ucmd->angles[i];

		client->ps.pmove.pm_type = PM_CHASECAM;
		//CHASEHACKv2 & splitmodels [start]
		if( ucmd->upmove && game.realtime > client->chase.keytime ){
			client->chase.keytime = game.realtime + 1000;
			client->chase.keyNext = qtrue;
		}//[end]
		return;
	}
//ZOID

	// set up for pmove
	memset( &pm, 0, sizeof(pm) );

	if( ent->r.svflags & SVF_NOCLIENT )
		client->ps.pmove.pm_type = PM_SPECTATOR;
	else if( ent->s.type == ET_GIB )
		client->ps.pmove.pm_type = PM_GIB;
	else if( ent->deadflag )
		client->ps.pmove.pm_type = PM_FREEZE;
	else
		client->ps.pmove.pm_type = PM_NORMAL;

	if( gtimeout.active && client->ps.pmove.pm_type != PM_SPECTATOR )
	{
		// only set angles the last few seconds of the timeout, to save bandwidth
		if( (gtimeout.endtime - gtimeout.time) < TIMEIN_TIME ) {
			// set the delta angle
			for( i = 0 ; i < 3 ; i++ )
				client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->s.angles[i]) - ucmd->angles[i];
		}

		client->ps.pmove.pm_type = PM_FREEZE;
		return;
	}

	client->ps.pmove.gravity = g_gravity->value;
	pm.s = client->ps.pmove;

	for( i = 0; i < 3; i++ )
	{
		pm.s.origin[i] = ent->s.origin[i]*16;
		pm.s.velocity[i] = ent->velocity[i]*16;
	}

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

	pm.cmd = *ucmd;

	pm.trace = PM_trace;	// adds default parms
	pm.pointcontents = trap_PointContents;

	// wsw : jal : set max_walljumps for gametype
	pm.max_walljumps = GS_GameType_MaxWallJumps( game.gametype );

	// perform a pmove
	Pmove( &pm );

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

	for( i=0; i<3; i++ ) {
		ent->s.origin[i] = pm.s.origin[i]*(1.0/16.0);
		ent->velocity[i] = pm.s.velocity[i]*(1.0/16.0);
	}

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

	//splitmodels jal[start]
	xyspeedcheck = sqrt( ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1] );

	// send jumping events
	if( client->walljumped )
	{
		if( !(pm.s.pm_flags & PMF_WALLJUMPING) )
			client->walljumped = qfalse;
	}
	else
	{
		if( pm.s.pm_flags & PMF_WALLJUMPING )
		{
			G_AddEvent( ent, EV_JUMP, 1, qtrue );
			client->walljumped = qtrue;
		}
	}

	if( pm.s.pm_flags & PMF_DASHING && ent->velocity[2] > 0.0f 
		&& ent->groundentity && (pm.groundentity == -1) )
		G_AddEvent( ent, EV_JUMP, 2, qtrue );

	if( ent->groundentity && (pm.groundentity == -1) && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0) )
	{
		if(ent->velocity[2] > 40.0f) 
		{ // typhontaur fix
			G_AddEvent( ent, EV_JUMP, 0, qtrue );

			//LAUNCH JUMP animation

			//vertical velocity:
			//a simple strafe jump gives a velocity of 4179
			//double jumps & ramp jumps are over 5500 (and over 5600...)
			//
			//note: this checks may not work with altered gravity
			//Com_Printf ("%i\n",pm.s.velocity[2]);

			if( (pm.s.velocity[2] < 5500) || !ent->pmAnim.anim_jump )//is not a double jump
			{
				if( ent->pmAnim.anim_jump == qtrue && ent->pmAnim.anim_jump_thunk == qtrue )
				{	//REBOUNCE
					if( xyspeedcheck > 50 ){

						if( ent->pmAnim.anim_jump_style < 2 ) {
							ent->pmAnim.anim_jump_style = 2;
						}
						else
							ent->pmAnim.anim_jump_style = 1;
					} else
						ent->pmAnim.anim_jump_style = 0;

				} else {// is a simple jump

					if( pm.cmd.forwardmove >= 20 && xyspeedcheck > 50 )
					{
						if( ent->pmAnim.anim_jump_style < 2 )
							ent->pmAnim.anim_jump_style = 2;
						else
							ent->pmAnim.anim_jump_style = 1;
					} else
						ent->pmAnim.anim_jump_style = 0;
				}

				//set the jump as active and launch the animation change process.
				ent->pmAnim.anim_jump_thunk = qfalse;
				ent->pmAnim.anim_jump = qtrue;
			}
		}
	}
	//jal [end]

	ent->viewheight = pm.viewheight;
	ent->waterlevel = pm.waterlevel;
	ent->watertype = pm.watertype;
	if( pm.groundentity != -1 ) {
		ent->groundentity = &game.edicts[pm.groundentity];
		ent->groundentity_linkcount = ent->groundentity->r.linkcount;
	}
	else {
		ent->groundentity = NULL;
	}

	if( ent->deadflag ) {
		G_Client_DeadView(ent);
	} else {
		VectorCopy( pm.viewangles, client->v_angle );
		VectorCopy( pm.viewangles, client->ps.viewangles );
	}

	trap_LinkEntity(ent);

	if( ent->movetype != MOVETYPE_NOCLIP )
		G_TouchTriggers(ent);

	// 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;
		other->touch( other, ent, NULL, 0 );
	}

	// during the min respawn time, clear all buttons
	if( ent->deathtimestamp + g_respawn_delay_min->integer > level.timemsec ) {
	// during the first 100 msecs after a death, clear up buttons.
	//if( ent->deathtimestamp + 100 > level.timemsec ) {
		client->latched_buttons = client->buttons = 0;
	}
	else {
		client->oldbuttons = client->buttons;
		client->buttons = ucmd->buttons;
		client->latched_buttons |= client->buttons & ~client->oldbuttons;
	}

	//splitmodels: jal[begin]: set movement flags for animations 

	ent->pmAnim.anim_moveflags = 0;//start from 0

	if( ucmd->forwardmove < -1 )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_BACK;
	else if( ucmd->forwardmove > 1 )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_FRONT;

	if( ucmd->sidemove < -1 )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_LEFT;
	else if( ucmd->sidemove > 1 )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_RIGHT;

	if( !(client->buttons & BUTTON_WALK) && xyspeedcheck )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_RUN;
	else if( xyspeedcheck )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_WALK;

	if( client->ps.pmove.pm_flags & PMF_DUCKED )
		ent->pmAnim.anim_moveflags |= ANIMMOVE_DUCK;
	//jal[end]

	// fire weapon from final position if needed
	if( client->latched_buttons & BUTTON_ATTACK && ent->movetype != MOVETYPE_NOCLIP )
	{
		if( !client->weapon_thunk ) {
			client->weapon_thunk = qtrue;
			Think_Weapon(ent);
		}
	}

	//MBotGame[start]
	AITools_DropNodes(ent);
	//MBotGame[end]
}


/*
==============
ClientBeginServerFrame

This will be called once for each server frame, before running
any other entities in the world.
==============
*/
void ClientBeginServerFrame( edict_t *ent )
{
	gclient_t	*client;
	int			buttonMask;

	if( match.state >= MATCH_STATE_POSTMATCH )
		return;

	client = ent->r.client;

	// run weapon animations if it hasn't been done by a ucmd_t
	if( !gtimeout.active && !client->weapon_thunk && ent->movetype != MOVETYPE_NOCLIP )
		Think_Weapon (ent);
	else
		client->weapon_thunk = qfalse;

	if( ent->deadflag )
	{
		// in deathmatch, only wait for attack button
		buttonMask = BUTTON_ATTACK;

		// wait for any button just going down
		if( level.timemsec > ent->deathtimestamp + g_respawn_delay_min->integer ) {
			if( client->latched_buttons & buttonMask ) {
				respawn(ent);
				client->latched_buttons = client->buttons = 0;
			}
		}

		// too much time passed
		if( g_respawn_delay_max->integer && (level.timemsec > ent->deathtimestamp + g_respawn_delay_max->integer) )
		{
			respawn(ent);
		}

		client->latched_buttons = client->buttons = 0;
		return;
	}

	client->latched_buttons = 0;
}
