/*
 * 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 "m_player.h"

void		ClientUserinfoChanged(edict_t * ent, char *userinfo);

void		SP_misc_teleporter_dest(edict_t * ent);

//
/* Gross, ugly, disgustuing hack section */
//

/* this function is an ugly as hell hack to fix some map flaws */
//
/* the coop spawn spots on some maps are SNAFU.  There are coop spots */
/* with the wrong targetname as well as spots with no name at all */
//

/*
 * we use carnal knowledge of the maps to fix the coop spot targetnames to
 * match
 */
/* that of the nearest named single player spot */

static void
SP_FixCoopSpots(edict_t * self)
{
	edict_t        *spot;
	vec3_t		d;

	spot = NULL;

	while (1) {
		spot = G_Find(spot, FOFS(classname), "info_player_start");
		if (!spot)
			return;
		if (!spot->targetname)
			continue;
		VectorSubtract(self->s.origin, spot->s.origin, d);
		if (VectorLength(d) < 384) {
			if ((!self->targetname) || Q_stricmp(self->targetname, spot->targetname) != 0) {

				/*
				 * gi.dprintf("FixCoopSpots changed %s at %s
				 * targetname from %s to %s\n",
				 * self->classname, vtos(self->s.origin),
				 * self->targetname, spot->targetname);
				 */
				self->targetname = spot->targetname;
			}
			return;
		}
	}
}

/* now if that one wasn't ugly enough for you then try this one on for size */
/* some maps don't have any coop spots at all, so we need to create them */
/* where they should have been */

static void
SP_CreateCoopSpots(edict_t * self)
{
	edict_t        *spot;

	if (Q_stricmp(level.mapname, "security") == 0) {
		spot = G_Spawn();
		spot->classname = "info_player_coop";
		spot->s.origin[0] = 188 - 64;
		spot->s.origin[1] = -164;
		spot->s.origin[2] = 80;
		spot->targetname = "jail3";
		spot->s.angles[1] = 90;

		spot = G_Spawn();
		spot->classname = "info_player_coop";
		spot->s.origin[0] = 188 + 64;
		spot->s.origin[1] = -164;
		spot->s.origin[2] = 80;
		spot->targetname = "jail3";
		spot->s.angles[1] = 90;

		spot = G_Spawn();
		spot->classname = "info_player_coop";
		spot->s.origin[0] = 188 + 128;
		spot->s.origin[1] = -164;
		spot->s.origin[2] = 80;
		spot->targetname = "jail3";
		spot->s.angles[1] = 90;

		return;
	}
}


/*
 * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) The normal
 * starting point for a level.
 */
void
SP_info_player_start(edict_t * self)
{
	if (!coop->value)
		return;
	if (Q_stricmp(level.mapname, "security") == 0) {
		/* invoke one of our gross, ugly, disgusting hacks */
		self->think = SP_CreateCoopSpots;
		self->nextthink = level.time + FRAMETIME;
	}
}

/*
 * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) potential
 * spawning position for deathmatch games
 */
void
SP_info_player_deathmatch(edict_t * self)
{
	if (!deathmatch->value) {
		G_FreeEdict(self);
		return;
	}
	SP_misc_teleporter_dest(self);
}

/*
 * QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) potential
 * spawning position for coop games
 */

void
SP_info_player_coop(edict_t * self)
{
	if (!coop->value) {
		G_FreeEdict(self);
		return;
	}
	if ((Q_stricmp(level.mapname, "jail2") == 0) ||
	    (Q_stricmp(level.mapname, "jail4") == 0) ||
	    (Q_stricmp(level.mapname, "mine1") == 0) ||
	    (Q_stricmp(level.mapname, "mine2") == 0) ||
	    (Q_stricmp(level.mapname, "mine3") == 0) ||
	    (Q_stricmp(level.mapname, "mine4") == 0) ||
	    (Q_stricmp(level.mapname, "lab") == 0) ||
	    (Q_stricmp(level.mapname, "boss1") == 0) ||
	    (Q_stricmp(level.mapname, "fact3") == 0) ||
	    (Q_stricmp(level.mapname, "biggun") == 0) ||
	    (Q_stricmp(level.mapname, "space") == 0) ||
	    (Q_stricmp(level.mapname, "command") == 0) ||
	    (Q_stricmp(level.mapname, "power2") == 0) ||
	    (Q_stricmp(level.mapname, "strike") == 0)) {
		/* invoke one of our gross, ugly, disgusting hacks */
		self->think = SP_FixCoopSpots;
		self->nextthink = level.time + FRAMETIME;
	}
}


/*
 * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) The
 * deathmatch intermission point will be at one of these Use 'angles' instead
 * of 'angle', so you can set pitch or roll as well as yaw.  'pitch yaw roll'
 */
void
SP_info_player_intermission(void)
{
}


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


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


qboolean
IsFemale(edict_t * ent)
{
	char           *info;

	if (!ent->client)
		return false;

	info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
	if (info[0] == 'f' || info[0] == 'F')
		return true;
	return false;
}

qboolean
IsNeutral(edict_t * ent)
{
	char           *info;

	if (!ent->client)
		return false;

	info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
	if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
		return true;
	return false;
}

void
ClientObituary(edict_t * self, edict_t * inflictor, edict_t * attacker)
{
	int		mod;
	char           *message;
	char           *message2;
	qboolean	ff;

	if (coop->value && attacker->client)
		meansOfDeath |= MOD_FRIENDLY_FIRE;

	if (deathmatch->value || coop->value) {
		ff = meansOfDeath & MOD_FRIENDLY_FIRE;
		mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
		message = NULL;
		message2 = "";

		switch (mod) {
		case MOD_SUICIDE:
			message = "suicides";
			break;
		case MOD_FALLING:
			message = "cratered";
			break;
		case MOD_CRUSH:
			message = "was squished";
			break;
		case MOD_WATER:
			message = "sank like a rock";
			break;
		case MOD_SLIME:
			message = "melted";
			break;
		case MOD_LAVA:
			message = "does a back flip into the lava";
			break;
		case MOD_EXPLOSIVE:
		case MOD_BARREL:
			message = "blew up";
			break;
		case MOD_EXIT:
			message = "found a way out";
			break;
		case MOD_TARGET_LASER:
			message = "saw the light";
			break;
		case MOD_TARGET_BLASTER:
			message = "got blasted";
			break;
		case MOD_BOMB:
		case MOD_SPLASH:
		case MOD_TRIGGER_HURT:
			message = "was in the wrong place";
			break;
		}
		if (attacker == self) {
			switch (mod) {
			case MOD_HELD_GRENADE:
				message = "tried to put the pin back in";
				break;
			case MOD_HG_SPLASH:
			case MOD_G_SPLASH:
				if (IsNeutral(self))
					message = "tripped on its own grenade";
				else if (IsFemale(self))
					message = "tripped on her own grenade";
				else
					message = "tripped on his own grenade";
				break;
			case MOD_R_SPLASH:
				if (IsNeutral(self))
					message = "blew itself up";
				else if (IsFemale(self))
					message = "blew herself up";
				else
					message = "blew himself up";
				break;
			case MOD_BFG_BLAST:
				message = "should have used a smaller gun";
				break;
			default:
				if (IsNeutral(self))
					message = "killed itself";
				else if (IsFemale(self))
					message = "killed herself";
				else
					message = "killed himself";
				break;
			}
		}
		if (message) {
#ifdef WITH_ACEBOT
			safe_bprintf
#else
			gi.bprintf
#endif
			    (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
			if (deathmatch->value)
				self->client->resp.score--;
			self->enemy = NULL;
			return;
		}
		self->enemy = attacker;
		if (attacker && attacker->client) {
			switch (mod) {
			case MOD_BLASTER:
				message = "was blasted by";
				break;
			case MOD_SHOTGUN:
				message = "was gunned down by";
				break;
			case MOD_SSHOTGUN:
				message = "was blown away by";
				message2 = "'s super shotgun";
				break;
			case MOD_MACHINEGUN:
				message = "was machinegunned by";
				break;
			case MOD_CHAINGUN:
				message = "was cut in half by";
				message2 = "'s chaingun";
				break;
			case MOD_GRENADE:
				message = "was popped by";
				message2 = "'s grenade";
				break;
			case MOD_G_SPLASH:
				message = "was shredded by";
				message2 = "'s shrapnel";
				break;
			case MOD_ROCKET:
				message = "ate";
				message2 = "'s rocket";
				break;
			case MOD_R_SPLASH:
				message = "almost dodged";
				message2 = "'s rocket";
				break;
			case MOD_HYPERBLASTER:
				message = "was melted by";
				message2 = "'s hyperblaster";
				break;
			case MOD_RAILGUN:
				message = "was railed by";
				break;
			case MOD_BFG_LASER:
				message = "saw the pretty lights from";
				message2 = "'s BFG";
				break;
			case MOD_BFG_BLAST:
				message = "was disintegrated by";
				message2 = "'s BFG blast";
				break;
			case MOD_BFG_EFFECT:
				message = "couldn't hide from";
				message2 = "'s BFG";
				break;
			case MOD_HANDGRENADE:
				message = "caught";
				message2 = "'s handgrenade";
				break;
			case MOD_HG_SPLASH:
				message = "didn't see";
				message2 = "'s handgrenade";
				break;
			case MOD_HELD_GRENADE:
				message = "feels";
				message2 = "'s pain";
				break;
			case MOD_TELEFRAG:
				message = "tried to invade";
				message2 = "'s personal space";
				break;
			}
			if (message) {
#ifdef WITH_ACEBOT
				safe_bprintf
#else
				gi.bprintf
#endif
				    (PRINT_MEDIUM, "%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
				if (deathmatch->value) {
#ifdef WITH_ACEBOT
					/* botchat> */
					if (botchat->value && attacker->client && attacker != self) {
						bTaunt(attacker, self);
						bInsult(attacker, self);
					}
					/* <botchat */
#endif
					if (ff)
						attacker->client->resp.score--;
					else
						attacker->client->resp.score++;
				}
				return;
			}
		}
	}
#ifdef WITH_ACEBOT
	safe_bprintf
#else
	gi.bprintf
#endif
	    (PRINT_MEDIUM, "%s died.\n", self->client->pers.netname);
	if (deathmatch->value)
		self->client->resp.score--;
}


void		Touch_Item(edict_t * ent, edict_t * other, cplane_t * plane, csurface_t * surf);

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

	if (!deathmatch->value)
		return;

	item = self->client->pers.weapon;
	if (!self->client->pers.inventory[self->client->ammo_index])
		item = NULL;
	if (item && (strcmp(item->pickup_name, "Blaster") == 0))
		item = NULL;

	if (!((int)(dmflags->value) & DF_QUAD_DROP))
		quad = false;
	else
		quad = (self->client->quad_framenum > (level.framenum + 10));

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

	if (item) {
		self->client->v_angle[YAW] -= spread;
		drop = Drop_Item(self, item);
		self->client->v_angle[YAW] += spread;
		drop->spawnflags = DROPPED_PLAYER_ITEM;
	}
	if (quad) {
		self->client->v_angle[YAW] += spread;
		drop = Drop_Item(self, FindItemByClassname("item_quad"));
		self->client->v_angle[YAW] -= spread;
		drop->spawnflags |= DROPPED_PLAYER_ITEM;

		drop->touch = Touch_Item;
		drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
		drop->think = G_FreeEdict;
	}
}


/*
 * ================== 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->client->killer_yaw = self->s.angles[YAW];
		return;
	}

	if (dir[0])
		self->client->killer_yaw = 180 / M_PI * atan2(dir[1], dir[0]);
	else {
		self->client->killer_yaw = 0;
		if (dir[1] > 0)
			self->client->killer_yaw = 90;
		else if (dir[1] < 0)
			self->client->killer_yaw = -90;
	}
	if (self->client->killer_yaw < 0)
		self->client->killer_yaw += 360;


}

/*
 * ================== player_die ==================
 */
void
player_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	int		n;

	VectorClear(self->avelocity);

	self->takedamage = DAMAGE_YES;
	self->movetype = MOVETYPE_TOSS;

	self->s.modelindex2 = 0;/* remove linked weapon model */

	self->s.angles[0] = 0;
	self->s.angles[2] = 0;

	self->s.sound = 0;
	self->client->weapon_sound = 0;

	self->maxs[2] = -8;

	/* self->solid = SOLID_NOT; */
	self->svflags |= SVF_DEADMONSTER;

	if (!self->deadflag) {
		self->client->respawn_time = level.time + 1.0;
		LookAtKiller(self, inflictor, attacker);
		self->client->ps.pmove.pm_type = PM_DEAD;
		ClientObituary(self, inflictor, attacker);
		TossClientWeapon(self);
		if (deathmatch->value)
			Cmd_Help_f(self);	/* show scores */

		/* clear inventory */

		/*
		 * this is kind of ugly, but it's how we want to handle keys
		 * in coop
		 */
		for (n = 0; n < game.num_items; n++) {
			if (coop->value && itemlist[n].flags & IT_KEY)
				self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n];
			self->client->pers.inventory[n] = 0;
		}
	}
	/* remove powerups */
	self->client->quad_framenum = 0;
	self->client->invincible_framenum = 0;
	self->client->breather_framenum = 0;
	self->client->enviro_framenum = 0;
	self->flags &= ~FL_POWER_ARMOR;

	if (self->health < -40) {	/* gib */
		gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
		for (n = 0; n < 4; n++)
			ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
		ThrowClientHead(self, damage);

		self->takedamage = DAMAGE_NO;
	} else {		/* normal death */
		if (!self->deadflag) {
			static int	i;

			i = (i + 1) % 3;
			/* start a death animation */
			self->client->anim_priority = ANIM_DEATH;
			if (self->client->ps.pmove.pm_flags & PMF_DUCKED) {
				self->s.frame = FRAME_crdeath1 - 1;
				self->client->anim_end = FRAME_crdeath5;
			} else
				switch (i) {
				case 0:
					self->s.frame = FRAME_death101 - 1;
					self->client->anim_end = FRAME_death106;
					break;
				case 1:
					self->s.frame = FRAME_death201 - 1;
					self->client->anim_end = FRAME_death206;
					break;
				case 2:
					self->s.frame = FRAME_death301 - 1;
					self->client->anim_end = FRAME_death308;
					break;
				}
			gi.sound(self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand() % 4) + 1)), 1, ATTN_NORM, 0);
		}
	}

	self->deadflag = DEAD_DEAD;

	gi.linkentity(self);
}

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

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

	memset(&client->pers, 0, sizeof(client->pers));

	item = FindItem("Blaster");
	client->pers.selected_item = ITEM_INDEX(item);
	client->pers.inventory[client->pers.selected_item] = 1;

	client->pers.weapon = item;

	client->pers.health = 100;
	client->pers.max_health = 100;

	client->pers.max_bullets = 200;
	client->pers.max_shells = 100;
	client->pers.max_rockets = 50;
	client->pers.max_grenades = 50;
	client->pers.max_cells = 200;
	client->pers.max_slugs = 50;

	client->pers.connected = true;
}


void
InitClientResp(gclient_t * client)
{
	memset(&client->resp, 0, sizeof(client->resp));
	client->resp.enterframe = level.framenum;
	client->resp.coop_respawn = client->pers;
}

/*
 * ================== SaveClientData
 *
 * Some information that should be persistant, like health, is still stored in
 * the edict structure, so it needs to be mirrored out to the client
 * structure before all the edicts are wiped. ==================
 */
void
SaveClientData(void)
{
	int		i;
	edict_t        *ent;

	for (i = 0; i < game.maxclients; i++) {
		ent = &g_edicts[1 + i];
		if (!ent->inuse)
			continue;
		game.clients[i].pers.health = ent->health;
		game.clients[i].pers.max_health = ent->max_health;
		game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR));
		if (coop->value)
			game.clients[i].pers.score = ent->client->resp.score;
	}
}

void
FetchClientEntData(edict_t * ent)
{
	ent->health = ent->client->pers.health;
	ent->max_health = ent->client->pers.max_health;
	ent->flags |= ent->client->pers.savedFlags;
	if (coop->value)
		ent->client->resp.score = ent->client->pers.score;
}



/*
 * =======================================================================
 *
 * SelectSpawnPoint
 *
 * =======================================================================
 */

/*
 * ================ PlayersRangeFromSpot
 *
 * Returns the distance to the nearest player from the given spot
 * ================
 */
float
PlayersRangeFromSpot(edict_t * spot)
{
	edict_t        *player;
	float		bestplayerdistance;
	vec3_t		v;
	int		n;
	float		playerdistance;


	bestplayerdistance = 9999999;

	for (n = 1; n <= maxclients->value; n++) {
		player = &g_edicts[n];

		if (!player->inuse)
			continue;

		if (player->health <= 0)
			continue;

		VectorSubtract(spot->s.origin, player->s.origin, v);
		playerdistance = VectorLength(v);

		if (playerdistance < bestplayerdistance)
			bestplayerdistance = playerdistance;
	}

	return bestplayerdistance;
}

/*
 * ================ SelectRandomDeathmatchSpawnPoint
 *
 * go to a random point, but NOT the two points closest to other players
 * ================
 */
edict_t        *
SelectRandomDeathmatchSpawnPoint(void)
{
	edict_t        *spot, *spot1, *spot2;
	int		count = 0;
	int		selection;
	float		range   , range1, range2;

	spot = NULL;
	range1 = range2 = 99999;
	spot1 = spot2 = NULL;

	while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
		count++;
		range = PlayersRangeFromSpot(spot);
		if (range < range1) {
			range1 = range;
			spot1 = spot;
		} else if (range < range2) {
			range2 = range;
			spot2 = spot;
		}
	}

	if (!count)
		return NULL;

	if (count <= 2) {
		spot1 = spot2 = NULL;
	} else
		count -= 2;

	selection = rand() % count;

	spot = NULL;
	do {
		spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
		if (spot == spot1 || spot == spot2)
			selection++;
	} while (selection--);

	return spot;
}

/*
 * ================ SelectFarthestDeathmatchSpawnPoint
 *
 * ================
 */
edict_t        *
SelectFarthestDeathmatchSpawnPoint(void)
{
	edict_t        *bestspot;
	float		bestdistance, bestplayerdistance;
	edict_t        *spot;


	spot = NULL;
	bestspot = NULL;
	bestdistance = 0;
	while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
		bestplayerdistance = PlayersRangeFromSpot(spot);

		if (bestplayerdistance > bestdistance) {
			bestspot = spot;
			bestdistance = bestplayerdistance;
		}
	}

	if (bestspot) {
		return bestspot;
	}
	/* if there is a player just spawned on each and every start spot */
	/* we have no choice to turn one into a telefrag meltdown */
	spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");

	return spot;
}

edict_t        *
SelectDeathmatchSpawnPoint(void)
{
	if ((int)(dmflags->value) & DF_SPAWN_FARTHEST)
		return SelectFarthestDeathmatchSpawnPoint();
	else
		return SelectRandomDeathmatchSpawnPoint();
}


edict_t        *
SelectCoopSpawnPoint(edict_t * ent)
{
	int		index;
	edict_t        *spot = NULL;
	char           *target;

	index = ent->client - game.clients;

	/* player 0 starts in normal player spawn point */
	if (!index)
		return NULL;

	spot = NULL;

	/* assume there are four coop spots at each spawnpoint */
	while (1) {
		spot = G_Find(spot, FOFS(classname), "info_player_coop");
		if (!spot)
			return NULL;	/* we didn't have enough... */

		target = spot->targetname;
		if (!target)
			target = "";
		if (Q_stricmp(game.spawnpoint, target) == 0) {	/* this is a coop spawn
								 * point for one of the
								 * clients here */
			index--;
			if (!index)
				return spot;	/* this is it */
		}
	}


	return spot;
}


/*
 * =========== SelectSpawnPoint
 *
 * Chooses a player start, deathmatch start, coop start, etc ============
 */
void
SelectSpawnPoint(edict_t * ent, vec3_t origin, vec3_t angles)
{
	edict_t        *spot = NULL;

	if (deathmatch->value)
		spot = SelectDeathmatchSpawnPoint();
	else if (coop->value)
		spot = SelectCoopSpawnPoint(ent);

	/* find a single player start spot */
	if (!spot) {
		while ((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL) {
			if (!game.spawnpoint[0] && !spot->targetname)
				break;

			if (!game.spawnpoint[0] || !spot->targetname)
				continue;

			if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
				break;
		}

		if (!spot) {
			if (!game.spawnpoint[0]) {	/* there wasn't a
							 * spawnpoint without a
							 * target, so use any */
				spot = G_Find(spot, FOFS(classname), "info_player_start");
			}
			if (!spot)
				gi.error("Couldn't find spawn point %s\n", game.spawnpoint);
		}
	}
	VectorCopy(spot->s.origin, origin);
	origin[2] += 9;
	VectorCopy(spot->s.angles, angles);
}

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


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

void
body_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
{
	int		n;

	if (self->health < -40) {
		gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
		for (n = 0; n < 4; n++)
			ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
		self->s.origin[2] -= 48;
		ThrowClientHead(self, damage);
		self->takedamage = DAMAGE_NO;
	}
}

void
CopyToBodyQue(edict_t * ent)
{
	edict_t        *body;

	/* grab a body que and cycle to the next one */
	body = &g_edicts[(int)maxclients->value + level.body_que + 1];
	level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;

	/* FIXME: send an effect on the removed body */

	gi.unlinkentity(ent);

	gi.unlinkentity(body);
	body->s = ent->s;
	body->s.number = body - g_edicts;

	body->svflags = ent->svflags;
	VectorCopy(ent->mins, body->mins);
	VectorCopy(ent->maxs, body->maxs);
	VectorCopy(ent->absmin, body->absmin);
	VectorCopy(ent->absmax, body->absmax);
	VectorCopy(ent->size, body->size);
	body->solid = ent->solid;
	body->clipmask = ent->clipmask;
	body->owner = ent->owner;
	body->movetype = ent->movetype;

	body->die = body_die;
	body->takedamage = DAMAGE_YES;

	gi.linkentity(body);
}


void
respawn(edict_t * self)
{
	if (deathmatch->value || coop->value) {
#ifdef WITH_ACEBOT
		/* ACEBOT_ADD special respawning code */
		if (self->is_bot) {
			ACESP_Respawn(self);
			return;
		}
		/* ACEBOT_END */
#endif
		/* spectator's don't leave bodies */
		if (self->movetype != MOVETYPE_NOCLIP)
			CopyToBodyQue(self);
		self->svflags &= ~SVF_NOCLIENT;
		PutClientInServer(self);

		/* add a teleportation effect */
		self->s.event = EV_PLAYER_TELEPORT;

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

		self->client->respawn_time = level.time;

		return;
	}
	/* restart the entire server */
	gi.AddCommandString("menu_loadgame\n");
}

/*
 * only called when pers.spectator changes note that resp.spectator should be
 * the opposite of pers.spectator here
 */
void
spectator_respawn(edict_t * ent)
{
	int		i         , numspec;

	/* if the user wants to become a spectator, make sure he doesn't */
	/* exceed max_spectators */

	if (ent->client->pers.spectator) {
		char           *value = Info_ValueForKey(ent->client->pers.userinfo, "spectator");

		if (*spectator_password->string &&
		    strcmp(spectator_password->string, "none") &&
		    strcmp(spectator_password->string, value)) {
#ifdef WITH_ACEBOT
			safe_cprintf
#else
			gi.cprintf
#endif
			    (ent, PRINT_HIGH, "Spectator password incorrect.\n");
			ent->client->pers.spectator = false;
			gi.WriteByte(svc_stufftext);
			gi.WriteString("spectator 0\n");
			gi.unicast(ent, true);
			return;
		}
		/* count spectators */
		for (i = 1, numspec = 0; i <= maxclients->value; i++)
			if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
				numspec++;

		if (numspec >= maxspectators->value) {
#ifdef WITH_ACEBOT
			safe_cprintf
#else
			gi.cprintf
#endif
			    (ent, PRINT_HIGH, "Server spectator limit is full.");
			ent->client->pers.spectator = false;
			/* reset his spectator var */
			gi.WriteByte(svc_stufftext);
			gi.WriteString("spectator 0\n");
			gi.unicast(ent, true);
			return;
		}
	} else {
		/* he was a spectator and wants to join the game */
		/* he must have the right password */
		char           *value = Info_ValueForKey(ent->client->pers.userinfo, "password");

		if (*password->string && strcmp(password->string, "none") &&
		    strcmp(password->string, value)) {
#ifdef WITH_ACEBOT
			safe_cprintf
#else
			gi.cprintf
#endif
			    (ent, PRINT_HIGH, "Password incorrect.\n");
			ent->client->pers.spectator = true;
			gi.WriteByte(svc_stufftext);
			gi.WriteString("spectator 1\n");
			gi.unicast(ent, true);
			return;
		}
	}

	/* clear client on respawn */
	ent->client->resp.score = ent->client->pers.score = 0;

	ent->svflags &= ~SVF_NOCLIENT;
	PutClientInServer(ent);

	/* add a teleportation effect */
	if (!ent->client->pers.spectator) {
		/* send effect */
		gi.WriteByte(svc_muzzleflash);
		gi.WriteShort(ent - g_edicts);
		gi.WriteByte(MZ_LOGIN);
		gi.multicast(ent->s.origin, MULTICAST_PVS);

		/* hold in place briefly */
		ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
		ent->client->ps.pmove.pm_time = 14;
	}
	ent->client->respawn_time = level.time;

	if (ent->client->pers.spectator)
#ifdef WITH_ACEBOT
		safe_bprintf(PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
	else
		safe_bprintf(PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
#else
		gi.bprintf(PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
	else
		gi.bprintf(PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
#endif
}

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


/*
 * =========== PutClientInServer
 *
 * Called when a player connects to a server or respawns in a deathmatch.
 * ============
 */
void
PutClientInServer(edict_t * ent)
{
	vec3_t		mins = {-16, -16, -24};
	vec3_t		maxs = {16, 16, 32};
	int		index;
	vec3_t		spawn_origin, spawn_angles;
	gclient_t      *client;
	int		i;
	client_persistant_t saved;
	client_respawn_t resp;

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

	index = ent - g_edicts - 1;
	client = ent->client;

	/* deathmatch wipes most client data every spawn */
	if (deathmatch->value) {
		char		userinfo  [MAX_INFO_STRING];

		resp = client->resp;
		memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
		InitClientPersistant(client);
		ClientUserinfoChanged(ent, userinfo);
	} else if (coop->value) {
		/* int			n; */
		char		userinfo  [MAX_INFO_STRING];

		resp = client->resp;
		memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));

		/*
		 * this is kind of ugly, but it's how we want to handle keys
		 * in coop
		 */
		/* for (n = 0; n < game.num_items; n++) */
		/* { */
		/* if (itemlist[n].flags & IT_KEY) */

		/*
		 * resp.coop_respawn.inventory[n] =
		 * client->pers.inventory[n];
		 */
		/* } */
		resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
		resp.coop_respawn.helpchanged = client->pers.helpchanged;
		client->pers = resp.coop_respawn;
		ClientUserinfoChanged(ent, userinfo);
		if (resp.score > client->pers.score)
			client->pers.score = resp.score;
	} else {
		memset(&resp, 0, sizeof(resp));
	}

	/* clear everything but the persistant data */
	saved = client->pers;
	memset(client, 0, sizeof(*client));
	client->pers = saved;
	if (client->pers.health <= 0)
		InitClientPersistant(client);
	client->resp = resp;

	/* copy some data from the client to the entity */
	FetchClientEntData(ent);

	/* clear entity values */
	ent->groundentity = NULL;
	ent->client = &game.clients[index];
	ent->takedamage = DAMAGE_AIM;
	ent->movetype = MOVETYPE_WALK;
	ent->viewheight = 22;
	ent->inuse = true;
	ent->classname = "player";
	ent->mass = 200;
	ent->solid = SOLID_BBOX;
	ent->deadflag = DEAD_NO;
	ent->air_finished = level.time + 12;
	ent->clipmask = MASK_PLAYERSOLID;
	ent->model = "players/male/tris.md2";
	ent->pain = player_pain;
	ent->die = player_die;
	ent->waterlevel = 0;
	ent->watertype = 0;
	ent->flags &= ~FL_NO_KNOCKBACK;
	ent->svflags &= ~SVF_DEADMONSTER;
#ifdef WITH_ACEBOT
	/* ACEBOT_ADD */
	ent->is_bot = false;
	ent->last_node = -1;
	ent->is_jumping = false;
	/* ACEBOT_END */
#endif

	VectorCopy(mins, ent->mins);
	VectorCopy(maxs, ent->maxs);
	VectorClear(ent->velocity);

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

	client->ps.pmove.origin[0] = spawn_origin[0] * 8;
	client->ps.pmove.origin[1] = spawn_origin[1] * 8;
	client->ps.pmove.origin[2] = spawn_origin[2] * 8;

	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) {
		client->ps.fov = 90;
	} else {
		client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
		if (client->ps.fov < 1)
			client->ps.fov = 90;
		else if (client->ps.fov > 160)
			client->ps.fov = 160;
	}

	client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);

	/* clear entity state values */
	ent->s.effects = 0;
	ent->s.modelindex = 255;/* will use the skin specified model */
	ent->s.modelindex2 = 255;	/* custom gun model */
	/* sknum is player num and weapon number */
	/* weapon number will be added in changeweapon */
	ent->s.skinnum = ent - g_edicts - 1;

	ent->s.frame = 0;
#ifdef WITH_ACEBOT
	/* botchat> */
	ent->last_insult = level.time;
	ent->last_taunt = level.time;
	ent->last_chat = level.time;
	/* <botchat */
#endif
	VectorCopy(spawn_origin, ent->s.origin);
	ent->s.origin[2] += 1;	/* make sure off ground */
	VectorCopy(ent->s.origin, ent->s.old_origin);

	/* set the delta angle */
	for (i = 0; i < 3; i++) {
		client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
	}

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

	/* spawn a spectator */
	if (client->pers.spectator) {
		client->chase_target = NULL;

		client->resp.spectator = true;

		ent->movetype = MOVETYPE_NOCLIP;
		ent->solid = SOLID_NOT;
		ent->svflags |= SVF_NOCLIENT;
		ent->client->ps.gunindex = 0;
		gi.linkentity(ent);
		return;
	} else
		client->resp.spectator = false;

	if (!KillBox(ent)) {	/* could't spawn in? */
	}
	gi.linkentity(ent);

	/* force the current weapon up */
	client->newweapon = client->pers.weapon;
	ChangeWeapon(ent);
}

/*
 * ===================== ClientBeginDeathmatch
 *
 * A client has just connected to the server in deathmatch mode, so clear
 * everything out before starting them. =====================
 */
void
ClientBeginDeathmatch(edict_t * ent)
{
#ifdef WITH_ACEBOT
	/* ACEBOT_ADD */
	static char	current_map[55];

	/* ACEBOT_END */
#endif
	G_InitEdict(ent);
#ifdef WITH_ACEBOT
	/* ACEBOT_ADD */
	ACEIT_PlayerAdded(ent);
	/* ACEBOT_END */
#endif

	InitClientResp(ent->client);

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

	if (level.intermissiontime) {
		MoveClientToIntermission(ent);
	} else {
		/* send effect */
		gi.WriteByte(svc_muzzleflash);
		gi.WriteShort(ent - g_edicts);
		gi.WriteByte(MZ_LOGIN);
		gi.multicast(ent->s.origin, MULTICAST_PVS);
	}
#ifdef WITH_ACEBOT
	safe_bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
	safe_centerprintf(ent, "\nQ2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2\n\nQuake2 Ace Bots\n\n\n'sv addbot ' to add a new bot.\n\n'sv removebot <name>' to remove bot.\n\n\nhttp://qudos.quakedev.com\n\n\nQ2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2\n\n");
	/* If the map changes on us, init and reload the nodes */
	    if (strcmp(level.mapname, current_map)) {

		ACEND_InitNodes();
		ACEND_LoadNodes();
		if (botauto_respawn->value) {
			ACESP_LoadBots();
		}
		strcpy(current_map, level.mapname);
	}
	safe_bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
	/* ACEBOT_END */
#else
	gi.bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
#endif

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


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

	ent->client = game.clients + (ent - g_edicts - 1);
	
	if (deathmatch->value) {
		ClientBeginDeathmatch(ent);
		return;
	}
	/* if there is already a body waiting for us (a loadgame), just */
	/* take it, otherwise spawn one from scratch */
	if (ent->inuse == true) {
		/* 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->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->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->client);
		PutClientInServer(ent);
	}

	if (level.intermissiontime) {
		MoveClientToIntermission(ent);
	} else {
		/* send effect if in a multiplayer game */
		if (game.maxclients > 1) {
			gi.WriteByte(svc_muzzleflash);
			gi.WriteShort(ent - g_edicts);
			gi.WriteByte(MZ_LOGIN);
			gi.multicast(ent->s.origin, MULTICAST_PVS);

#ifdef WITH_ACEBOT
			safe_bprintf
#else
			gi.bprintf
#endif
			    (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
		}
	}

	/* make sure all view stuff is valid */
	ClientEndServerFrame(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;
	int		playernum;

	/* check for malformed or illegal info strings */
	if (!Info_Validate(userinfo)) {
		strcpy(userinfo, "\\name\\badinfo\\skin\\male/grunt");
	}
	/* set name */
	s = Info_ValueForKey(userinfo, "name");
	strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname) - 1);

	/* set spectator */
	s = Info_ValueForKey(userinfo, "spectator");
	/* spectators are only supported in deathmatch */
	if (deathmatch->value && *s && strcmp(s, "0"))
		ent->client->pers.spectator = true;
	else
		ent->client->pers.spectator = false;

	/* set skin */
	s = Info_ValueForKey(userinfo, "skin");

	playernum = ent - g_edicts - 1;

	/* combine name and skin into a configstring */
	gi.configstring(CS_PLAYERSKINS + playernum, va("%s\\%s", ent->client->pers.netname, s));

	/* fov */
	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) {
		ent->client->ps.fov = 90;
	} else {
		ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
		if (ent->client->ps.fov < 1)
			ent->client->ps.fov = 90;
		else if (ent->client->ps.fov > 160)
			ent->client->ps.fov = 160;
	}

	/* handedness */
	s = Info_ValueForKey(userinfo, "hand");
	if (strlen(s)) {
		ent->client->pers.hand = atoi(s);
	}
	/* save off the userinfo in case we want to check something later */
	strncpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo) - 1);
}


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

	/* check to see if they are on the banned IP list */
	value = Info_ValueForKey(userinfo, "ip");
	if (SV_FilterPacket(value)) {
		Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
		return false;
	}
	/* check for a spectator */
	value = Info_ValueForKey(userinfo, "spectator");
	if (deathmatch->value && *value && strcmp(value, "0")) {
		int		i         , numspec;

		if (*spectator_password->string &&
		    strcmp(spectator_password->string, "none") &&
		    strcmp(spectator_password->string, value)) {
			Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
			return false;
		}
		/* count spectators */
		for (i = numspec = 0; i < maxclients->value; i++)
			if (g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator)
				numspec++;

		if (numspec >= maxspectators->value) {
			Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
			return false;
		}
	} else {
		/* check for a password */
		value = Info_ValueForKey(userinfo, "password");
		if (*password->string && strcmp(password->string, "none") &&
		    strcmp(password->string, value)) {
			Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
			return false;
		}
	}


	/* they can connect */
	ent->client = game.clients + (ent - g_edicts - 1);

	/* if there is already a body waiting for us (a loadgame), just */
	/* take it, otherwise spawn one from scratch */
	if (ent->inuse == false) {
		/* clear the respawning variables */
		InitClientResp(ent->client);
		if (!game.autosaved || !ent->client->pers.weapon)
			InitClientPersistant(ent->client);
	}
	ClientUserinfoChanged(ent, userinfo);

	if (game.maxclients > 1)
		gi.dprintf("%s connected\n", ent->client->pers.netname);

	ent->svflags = 0;	/* make sure we start with known default */
	ent->client->pers.connected = true;
	return true;
}

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

	if (!ent->client)
		return;
#ifdef GAME_MOD
	Blinky_OnClientTerminate(ent);
#endif
#ifdef WITH_ACEBOT
	safe_bprintf
#else
	gi.bprintf
#endif
	    (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
#ifdef WITH_ACEBOT
	/* ACEBOT_ADD */
	ACEIT_PlayerRemoved(ent);
	/* ACEBOT_END */
#endif

	/* send effect */
	gi.WriteByte(svc_muzzleflash);
	gi.WriteShort(ent - g_edicts);
	gi.WriteByte(MZ_LOGOUT);
	gi.multicast(ent->s.origin, MULTICAST_PVS);

	gi.unlinkentity(ent);
	ent->s.modelindex = 0;
	ent->solid = SOLID_NOT;
	ent->inuse = false;
	ent->classname = "disconnected";
	ent->client->pers.connected = false;

	playernum = ent - g_edicts - 1;
	gi.configstring(CS_PLAYERSKINS + playernum, "");
}


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


edict_t        *pm_passent;

/* pmove doesn't need to know about passent and contentmask */
trace_t
PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
{
	if (pm_passent->health > 0)
		return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
	else
		return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
}

unsigned
CheckBlock(void *b, int c)
{
	int		v         , i;

	v = 0;
	for (i = 0; i < c; i++)
		v += ((byte *) b)[i];
	return v;
}
void
PrintPmove(pmove_t * pm)
{
	unsigned	c1    , c2;

	c1 = CheckBlock(&pm->s, sizeof(pm->s));
	c2 = CheckBlock(&pm->cmd, sizeof(pm->cmd));
	Com_Printf("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
}

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

	level.current_entity = ent;
	client = ent->client;
#ifdef GAME_MOD
	Blinky_BeginClientThink(ent, ucmd);
#endif
	if (level.intermissiontime) {
		client->ps.pmove.pm_type = PM_FREEZE;
		/* can exit intermission after five seconds */
		if (level.time > level.intermissiontime + 5.0
		    && (ucmd->buttons & BUTTON_ANY))
			level.exitintermission = true;
		return;
	}
	pm_passent = ent;

	if (ent->client->chase_target) {

		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);

	} else {

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

		if (ent->movetype == MOVETYPE_NOCLIP)
			client->ps.pmove.pm_type = PM_SPECTATOR;
		else if (ent->s.modelindex != 255)
			client->ps.pmove.pm_type = PM_GIB;
		else if (ent->deadflag)
			client->ps.pmove.pm_type = PM_DEAD;
		else
			client->ps.pmove.pm_type = PM_NORMAL;

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

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

		if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) {
			pm.snapinitial = true;
			/* gi.dprintf ("pmove changed!\n"); */
		}
		pm.cmd = *ucmd;

		pm.trace = PM_trace;	/* adds default parms */
		pm.pointcontents = gi.pointcontents;

		/* perform a pmove */
		gi.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] * 0.125;
			ent->velocity[i] = pm.s.velocity[i] * 0.125;
		}

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

		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);

		if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) {
			gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
			PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
		}
		ent->viewheight = pm.viewheight;
		ent->waterlevel = pm.waterlevel;
		ent->watertype = pm.watertype;
		ent->groundentity = pm.groundentity;
		if (pm.groundentity)
			ent->groundentity_linkcount = pm.groundentity->linkcount;

		if (ent->deadflag) {
			client->ps.viewangles[ROLL] = 40;
			client->ps.viewangles[PITCH] = -15;
			client->ps.viewangles[YAW] = client->killer_yaw;
		} else {
			VectorCopy(pm.viewangles, client->v_angle);
			VectorCopy(pm.viewangles, client->ps.viewangles);
		}

		gi.linkentity(ent);

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

		/* touch other objects */
		for (i = 0; i < pm.numtouch; i++) {
			other = pm.touchents[i];
			for (j = 0; j < i; j++)
				if (pm.touchents[j] == other)
					break;
			if (j != i)
				continue;	/* duplicated */
			if (!other->touch)
				continue;
			other->touch(other, ent, NULL, NULL);
		}

	}

	client->oldbuttons = client->buttons;
	client->buttons = ucmd->buttons;
	client->latched_buttons |= client->buttons & ~client->oldbuttons;

	/* save light level the player is standing on for */
	/* monster sighting AI */
	ent->light_level = ucmd->lightlevel;

	/* fire weapon from final position if needed */
	if (client->latched_buttons & BUTTON_ATTACK) {
		if (client->resp.spectator) {

			client->latched_buttons = 0;

			if (client->chase_target) {
				client->chase_target = NULL;
				client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
			} else
				GetChaseTarget(ent);

		} else if (!client->weapon_thunk) {
			client->weapon_thunk = true;
			Think_Weapon(ent);
		}
	}
#ifdef WITH_ACEBOT
	/* ACEBOT_ADD */
	if (!ent->is_bot && !ent->deadflag && !ent->client->resp.spectator)
		ACEND_PathMap(ent);
	/* ACEBOT_END */
#endif

	if (client->resp.spectator) {
		if (ucmd->upmove >= 10) {
			if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) {
				client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
				if (client->chase_target)
					ChaseNext(ent);
				else
					GetChaseTarget(ent);
			}
		} else
			client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
	}
	/* update chase cam if being followed */
	for (i = 1; i <= maxclients->value; i++) {
		other = g_edicts + i;
		if (other->inuse && other->client->chase_target == ent)
			UpdateChaseCam(other);
	}
}


/*
 * ============== 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 (level.intermissiontime)
		return;

	client = ent->client;

	if (deathmatch->value &&
	    client->pers.spectator != client->resp.spectator &&
	    (level.time - client->respawn_time) >= 5) {
		spectator_respawn(ent);
		return;
	}
	/* run weapon animations if it hasn't been done by a ucmd_t */
	if (!client->weapon_thunk && !client->resp.spectator)
		Think_Weapon(ent);
	else
		client->weapon_thunk = false;

	if (ent->deadflag) {
		/* wait for any button just going down */
		if (level.time > client->respawn_time) {
			/* in deathmatch, only wait for attack button */
			if (deathmatch->value)
				buttonMask = BUTTON_ATTACK;
			else
				buttonMask = -1;

			if ((client->latched_buttons & buttonMask) ||
			    (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN))) {
				respawn(ent);
				client->latched_buttons = 0;
			}
		}
		return;
	}
	/* add player trail so monsters can follow */
	if (!deathmatch->value)
		if (!visible(ent, PlayerTrail_LastSpot()))
			PlayerTrail_Add(ent->s.old_origin);

	client->latched_buttons = 0;
}
