#include "g_local.h"
#include "m_player.h"

void Drop_Armor (edict_t *ent, gitem_t *item);
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) || 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(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(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
*/
static int spawnpoints = 0;
void SP_info_player_deathmatch(edict_t *self)
{
	edict_t *ent;
	//FILE *logfile = fopen("log.txt", "a");
	//fprintf(logfile, "SP_info_player_deathmatch(%s)\n", self->classname);
	//fclose(logfile);

	
	SP_misc_teleporter_dest (self);	
	
	spawnpoints++;
	ent = G_Spawn();
	if(spawnpoints & 2)
	{
		vec3_t forward;
		AngleVectors(self->s.angles, forward, NULL, NULL);
		ent->classname = "weapon_blaster";
		ent->s = self->s;
		//ent->s.origin[2] += 72;
		VectorMA(self->s.origin, 40, forward, ent->s.origin);
		ED_CallSpawn(ent);

		ent = G_Spawn();
		ent->classname = "ammo_cells";
		ent->s = self->s;
		VectorMA(self->s.origin, 64, forward, ent->s.origin);
		ED_CallSpawn(ent);
	}
	else if(spawnpoints & 1)
	{
		vec3_t forward;
		AngleVectors(self->s.angles, forward, NULL, NULL);
		ent->classname = "weapon_chainfist";
		ent->s = self->s;
		//ent->s.origin[2] += 72;
		VectorMA(self->s.origin, 64, forward, ent->s.origin);
		ED_CallSpawn(ent);
	}
	else
	{
		vec3_t forward;
		AngleVectors(self->s.angles, forward, NULL, NULL);
		ent->classname = "weapon_flamethrower";
		ent->s = self->s;
		//ent->s.origin[2] += 72;
		VectorMA(self->s.origin, 40, forward, ent->s.origin);
		ED_CallSpawn(ent);

		ent = G_Spawn();
		ent->classname = "ammo_napalm";
		ent->s = self->s;
		VectorMA(self->s.origin, 64, forward, ent->s.origin);
		ED_CallSpawn(ent);
	}
	
	
}

/*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((stricmp(level.mapname, "jail2") == 0)   ||
	   (stricmp(level.mapname, "jail4") == 0)   ||
	   (stricmp(level.mapname, "mine1") == 0)   ||
	   (stricmp(level.mapname, "mine2") == 0)   ||
	   (stricmp(level.mapname, "mine3") == 0)   ||
	   (stricmp(level.mapname, "mine4") == 0)   ||
	   (stricmp(level.mapname, "lab") == 0)     ||
	   (stricmp(level.mapname, "boss1") == 0)   ||
	   (stricmp(level.mapname, "fact3") == 0)   ||
	   (stricmp(level.mapname, "biggun") == 0)  ||
	   (stricmp(level.mapname, "space") == 0)   ||
	   (stricmp(level.mapname, "command") == 0) ||
	   (stricmp(level.mapname, "power2") == 0) ||
	   (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
}



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


	if (coop->value && attacker->client)
		meansOfDeath |= MOD_FRIENDLY_FIRE;
	else if(ctf->value && attacker->client && self->client->resp.ctf_team == attacker->client->resp.ctf_team)
		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_BLEEDING:
			if(attacker == world)
				message = "dies of a sucking chest wound";
			break;
		case MOD_SUICIDE:
			message = "suicides";
			break;
		case MOD_IMPACT:
		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:
		case MOD_LIGHTNING:
			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 (self->client->sex == SEX_FEMALE)
					message = "tripped on her own grenade";
				else
					message = "tripped on his own grenade";
				break;
			case MOD_R_SPLASH:
				if (self->client->sex == SEX_FEMALE)
					message = "blew herself up";
				else
					message = "blew himself up";
				break;
			case MOD_LIGHTNING:
			case MOD_BFG_BLAST:
				message = "should have used a smaller gun";
				break;
			default:
				if (self->client->sex == SEX_FEMALE)
					message = "killed herself";
				else
					message = "killed himself";
				break;
			}
		}
		if (message)
		{
			gi.bprintf (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_BLEEDING:
				message = "finally bled to death from";
				message2 = "'s attack";
				break;
			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_HIT:
				message = "was bitch-slapped by";
				break;
			case MOD_G_HIT:
				message = "was beaned by";
				message2 = "'s grenade";
				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;
//ZOID
			case MOD_GRAPPLE:
				message = "was caught by";
				message2 = "'s grapple";
				break;
//ZOID

//===============
//ROGUE
			case MOD_CHAINFIST:
				message = "was shredded by";
				message2 = "'s ripsaw";
				break;
			case MOD_DISINTEGRATOR:
				message = "lost his grip courtesy of";
				message2 = "'s disintegrator";
				break;
			case MOD_ETF_RIFLE:
				message = "was perforated by";
				break;
			case MOD_HEATBEAM:
				message = "was scorched by";
				message2 = "'s plasma beam";
				break;
			case MOD_TESLA:
				message = "was enlightened by";
				message2 = "'s tesla mine";
				break;
			case MOD_PROX:
				message = "got too close to";
				message2 = "'s proximity mine";
				break;
			case MOD_NUKE:
				message = "was nuked by";
				message2 = "'s antimatter bomb";
				break;
			case MOD_VENGEANCE_SPHERE:
				message = "was purged by";
				message2 = "'s vengeance sphere";
				break;
			case MOD_DEFENDER_SPHERE:
				message = "had a blast with";
				message2 = "'s defender sphere";
				break;
			case MOD_HUNTER_SPHERE:
				message = "was killed like a dog by";
				message2 = "'s hunter sphere";
				break;
			case MOD_TRACKER:
				message = "was annihilated by";
				message2 = "'s disruptor";
				break;
			case MOD_DOPPLE_EXPLODE:
				message = "was blown up by";
				message2 = "'s doppleganger";
				break;
			case MOD_DOPPLE_VENGEANCE:
				message = "was purged by";
				message2 = "'s doppleganger";
				break;
			case MOD_DOPPLE_HUNTER:
				message = "was hunted down by";
				message2 = "'s doppleganger";
				break;
//ROGUE
//===============
			case MOD_LIGHTNING:
				message = "was electrocuted";
				message2 = "";
				break;
			case MOD_FIRE:
				message = "was char-broiled by";
				message2 = "'s flamethrower";
				break;
			case MOD_MAGICK:
				message = "ate too much of ";
				message2 = "'s pixie dust";
				break;
			}
			if (message)
			{
				gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
				if (deathmatch->value)
				{
					if (ff)
					{
						attacker->client->resp.score--;
						gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, " was mistakenly fragged by ", attacker->client->pers.netname, message2);
					}
					else
					{
						attacker->client->resp.score++;
						gi.bprintf (PRINT_MEDIUM,"%s score: %d\n", attacker->client->pers.netname, attacker->client->resp.score);
					}
				}
				return;
			}
		}
	}

	gi.bprintf (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 Drop_Ammo (edict_t *ent, gitem_t *item);

qboolean TossClientStuff (edict_t *self)
{
	gitem_t		*item;
//	edict_t		*drop;
//	qboolean	quad;
	float		vspread, hspread;
	int i;
	//if (!deathmatch->value || !self->client->can_get_weapons )
	//	return false;

	
	if (ArmorIndex(self))
	{
		
		if(!self->client->wears_armor)
			self->client->pers.inventory[ArmorIndex(self)] = 0;
		else
		{
			item = GetItemByIndex(ArmorIndex(self));
			Drop_Armor(self, item);			
		}
		
	}
	for(i = 0; i < MAX_ITEMS; i++)
	{
		item = GetItemByIndex(i);//self->client->pers.weapon;
		if(self->client->pers.inventory[i] > 0 && (item->drop != NULL))
		{
			hspread = crandom() * 30.0;
			vspread = random() * 30.0;
			self->client->v_angle[YAW] += hspread;
			self->client->v_angle[PITCH] += vspread;
			self->s.angles[YAW] += hspread;
			self->s.angles[PITCH] += vspread;
			item->drop(self, item);
			self->client->v_angle[YAW] -= hspread;
			self->client->v_angle[PITCH] -= vspread;
			self->s.angles[YAW] += hspread;
			self->s.angles[PITCH] += vspread;
			return true;
		}		
		else if(self->client->pers.inventory[i])
		{
			// set to zero here
		}

	}		
	return false;
}

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

	self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
}

/*
==================
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
//ZOID
	self->s.modelindex3 = 0;	// remove linked ctf flag
//ZOID
    self->s.modelindex4 = 0;	// remove linked backpack / armor 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);
//ZOID
		CTFFragBonuses(self, inflictor, attacker);
//ZOID
		//don't do this here: TossClientStuff (self);
		
//ZOID
		CTFPlayerResetGrapple(self);
		CTFDeadDropFlag(self);
//ZOID
		if (deathmatch->value && !self->client->showscores)
			Cmd_Help_f (self);		// show scores
	}

	
	
	// DON'T clear inventory - TossClientStuff() does that
	//memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory));

	if (self->health < -40)
	{	// gib
		
		if(self->client->canbleed)
		{
			for (n= 0; n < 4; n++) ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
			gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
		}
		else			
		{
			for (n= 0; n < 4; n++) ThrowGib (self, "models/objects/debris1/tris.md2", damage, GIB_METALLIC);				
			gi.sound (self, CHAN_BODY, gi.soundindex ("world/explod1.wav"), 1, ATTN_NORM, 0);
		}
//ZOID
		self->client->anim_priority = ANIM_DEATH;
		self->s.frame = 0;
		self->client->anim_end = 0;
//ZOID
		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));
	//client->pers.weapon = NULL;
	/*
	item = FindItem("Cells");
	client->pers.selected_item = ITEM_INDEX(item);
	client->pers.inventory[client->pers.selected_item] = 50;
	item = FindItem("Blaster");
	client->pers.selected_item = ITEM_INDEX(item);
	client->pers.inventory[client->pers.selected_item] = 1;
	
	client->pers.weapon = item;
//ZOID
	client->pers.lastweapon = item;
//ZOID

//ZOID
	item = FindItem("Grapple");
	client->pers.inventory[ITEM_INDEX(item)] = 1;
//ZOID
	*/

	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)
{
//ZOID
	int ctf_team = client->resp.ctf_team;
//ZOID

	memset (&client->resp, 0, sizeof(client->resp));
	
//ZOID
	client->resp.ctf_team = ctf_team;
//ZOID

	client->resp.enterframe = level.framenum;
	client->resp.coop_respawn = client->pers;
 
//ZOID
	if (ctf->value && client->resp.ctf_team < CTF_TEAM1)
		CTFAssignTeam(client);
//ZOID
}

/*
==================
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.powerArmorActive = (ent->flags & 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;
	if (ent->client->pers.powerArmorActive)
		ent->flags |= FL_POWER_ARMOR;
	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)
//ZOID
		if (ctf->value)
			spot = SelectCTFSpawnPoint(ent);
		else
//ZOID
			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->s.frame = 0;
		self->takedamage = DAMAGE_NO;
	}
}

void body2_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/debris1/tris.md2", damage, GIB_METALLIC);
		for (n= 0; n < 4; n++) ThrowGib (self, "models/objects/debris2/tris.md2", damage, GIB_METALLIC);
		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->mass = ent->mass;
	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;

	if(ent->client->canbleed)
		body->die = body_die;
	else
		body->die = body2_die;
	body->takedamage = DAMAGE_YES;

	gi.linkentity (body);
}


void respawn (edict_t *self)
{
	if (deathmatch->value || coop->value)
	{
		if(TossClientStuff (self)) // something was dropped - there might be more stuff
			return;

		CopyToBodyQue (self);
		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");
}

void PlayerTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
	vec3_t vel, point;
	float speed, delta, dot, add;
	int damage;
	if(surf && (surf->flags & SURF_SKY))
		return;
	if(other == self->groundentity)
	{
		return;
	}
	VectorSubtract(other->velocity, self->velocity, vel);
	if(plane)
	{
		VectorMA(self->s.origin, -VectorLength(self->size) / VectorLength(vel), vel, point);
		speed = DotProduct(vel, plane->normal);
		dot = speed / VectorLength(vel);
	}
	else
	{
		VectorCopy(other->s.origin, point);
		speed = VectorLength(vel);
		dot = 1;
	}
	
	delta = speed * speed * self->mass * 0.000001;
	if ((self->client->ps.pmove.pm_flags & PMF_DUCKED) && plane && (plane->normal[2] > 0))
		delta -= 30 * plane->normal[2];
	if(plane && plane->normal[2] > 0)
		add = self->client->leg_damage * plane->normal[2];
	else
		add = 0;
		
	if (delta < 10)
	{
		self->s.event = EV_FOOTSTEP;
		//self->velocity[0] = 0;
		//self->velocity[1] = 0;
		//self->velocity[2] = 0;
		return;
	}

	self->client->fall_value = delta*0.5;
	if (self->client->fall_value > 40)
		self->client->fall_value = 40;
	self->client->fall_time = level.time + FALL_TIME;

	if (delta + add > 30)
	{
		//gi.dprintf("boink\n");
		if (self->health > 0)
		{
			if (delta >= 50)
				self->s.event = EV_FALLFAR;
			else
				self->s.event = EV_FALL;
		}
		self->pain_debounce_time = level.time;	// no normal pain sound
		damage = ((delta * 100 / self->client->basemass - 15) + add);
		//gi.dprintf("damage: %d", damage);
		gi.dprintf("falling damage: %d\n", damage);
		if(damage < 1)
		{
		}
		else if(plane)
		{
			//point[0] = self->s.origin[0];
			//point[1] = self->s.origin[1];
			//point[2] = self->s.origin[2] - 24;
			T_Damage (self, other, other, plane->normal, point, plane->normal, damage, 0, DAMAGE_NO_BLEED, MOD_FALLING);
			//gi.dprintf("falling\n");
		}
		else
		{
			T_Damage (self, other, other, vel, point, NULL, damage, 0, DAMAGE_NO_BLEED, MOD_IMPACT);
			//gi.dprintf("smack!\n");
		}
	}
	else
	{
		self->s.event = EV_FALLSHORT;		
	}
	//VectorMA(self->velocity, -speed * 2, plane->normal, self->velocity);
	//self->velocity[0] = 0;
	//self->velocity[1] = 0;
	//self->velocity[2] = 0;

	

}

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

void SpawnGive (edict_t *ent, char *name);
/*
===========
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;
	char *filename, model[64], *skin;
	FILE *classinfo;
	// 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);
		//classinfo = fopen("log.txt", "a");
		//fprintf(classinfo, "PutClientInServer(%s)", ent->client->pers.netname);
		//fclose(classinfo);
	
	}
	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 < MAX_ITEMS; n++)
		{
			if (itemlist[n].flags & IT_KEY)
				resp.coop_respawn.inventory[n] = client->pers.inventory[n];
		}
		client->pers = resp.coop_respawn;
		client->playermodel[0] = 0;
		ClientUserinfoChanged (ent, userinfo);
		if (resp.score > client->pers.score)
			client->pers.score = resp.score;
	}
	else
	{
		memset (&resp, 0, sizeof(resp));
	}
	stuffcmd(ent, "-attack\n");
	// 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;
	client->pers.shotgun_ammo = 0;
	client->pers.sshotgun_ammo = 0;
	client->pers.grenade_ammo = 0;
	client->pers.rocket_ammo = 0;
	client->pers.railgun_ammo = 0;
	
	// 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->client->canfly = false;
	ent->client->canbleed = true;
	ent->client->max_weapons = 4;
	ent->client->basemass = 100;
	ent->client->basespeed = 200;
	ent->client->basejump = 100;
	ent->client->max_carry = 60;
	ent->client->sex = SEX_MALE;
	ent->client->wears_armor = true;
	ent->client->base_fov = 90;
	
	//ent->client->playermodel[0] = 0;
	CTFAssignSkin(ent, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
	ent->client->width = 16;
	ent->client->height = 56;
	ent->client->bleeding = 0;
	ent->client->blinded = 0;
	ent->client->weapons_carried = 0;
	ent->client->using_ir = false;
	ent->client->can_cheat = false;
	ent->client->ctf_regentime = 0;
	ent->client->has_hands = true;
	if(classinfo = fopen(ent->client->classfile, "r"))
	{
		while(!feof(classinfo))
		{
			char token[64], *endline;					
			fgets(token, 60, classinfo);
			if((endline = strchr(token, '\n')) != NULL)
				*endline = '\0'; // strip newline
			//gi.dprintf("%s\n", token);
			if(strncmp(token, "mass=", 5) == 0)
			{
				ent->client->basemass = atoi(token + 5);
				if(ent->client->basemass < 1)
					ent->client->basemass = 100;
			}
			if(strncmp(token, "maxhealth=", 10) == 0)
			{
				ent->max_health = atoi(token + 10);
				ent->client->pers.max_health = ent->max_health;
				ent->health = atoi(token + 10);				
			}
			else if(strncmp(token, "sex=female", 10) == 0)
			{
				ent->client->sex = SEX_FEMALE;
			}
			else if(strncmp(token, "sex=male", 8) == 0)
			{
				ent->client->sex = SEX_MALE;
			}
			else if(strncmp(token, "basespeed=", 10) == 0)
			{
				ent->client->basespeed = atof(token + 10);
			}
			else if(strncmp(token, "height=", 7) == 0)
			{
				ent->client->height = atof(token + 7);
				maxs[2] = atof(token + 7) - 24;
				ent->viewheight = (atof(token + 7) * 0.85) - 24;
				//gi.dprintf("height: %f viewheight: %d\n", atof(token + 7) - 24, ent->viewheight);
			}
			else if(strncmp(token, "width=", 6) == 0)
			{
				ent->client->width = atof(token + 6);
				mins[0] = atof(token + 6);
				mins[1] = atof(token + 6);
				maxs[0] = atof(token + 6);
				maxs[1] = atof(token + 6);
				
			}
			
			else if(strncmp(token, "jumpheight=", 11) == 0)
			{
				ent->client->basejump = atof(token + 11);
			}
			else if(strncmp(token, "max_carry=", 10) == 0)
			{
				ent->client->max_carry = atof(token + 10);
			}
			else if(strncmp(token, "max_weapons=", 12) == 0)			
			{
				ent->client->max_weapons = atoi(token + 12);
			}
			else if(strncmp(token, "fov=", 4) == 0)
			{
				ent->client->base_fov = atoi(token + 4);
			}
			else if(strncmp(token, "item=", 5) == 0)
			{
				SpawnGive (ent, token + 5);
			}
			else if(strncmp(token, "regenerates=", 12) == 0)
			{
				ent->client->ctf_regentime = atof(token + 12);
				ent->client->ctf_lasttechmsg = level.time + ent->client->ctf_regentime;
			}
			else if(strcmp(token, "option=robotic") == 0)
			{
				ent->client->canbleed = false;
			}
			else if(strcmp(token, "option=nohands") == 0)
			{
				ent->client->has_hands = false;
			}
			else if(strcmp(token, "option=cheat") == 0)
				ent->client->can_cheat = true;
			else if(strcmp(token, "option=flying {") == 0)
			{
				ent->client->canfly = true;
				ent->client->special_anim_frames[ANIM_SPECIAL_FLY] = 0;
				fgets(token, 60, classinfo);
				while(token[0] != '}')
				{
					ent->client->special_anim[ent->client->special_anim_frames[ANIM_SPECIAL_FLY]++][ANIM_SPECIAL_FLY] = atoi(token);
					if(feof(classinfo))
						break;
					fgets(token, 60, classinfo);
					
				}
			}
			else if(strcmp(token, "option=throwing_anim {") == 0)
			{
				ent->client->special_anim_frames[ANIM_SPECIAL_THROW] = 0;
				fgets(token, 60, classinfo);
				while(token[0] != '}')
				{
					ent->client->special_anim[ent->client->special_anim_frames[ANIM_SPECIAL_THROW]++][ANIM_SPECIAL_THROW] = atoi(token);
					if(feof(classinfo))
						break;
					fgets(token, 60, classinfo);
					
				}
			}
			else if(strcmp(token, "option=organic") == 0)
			{
				ent->client->canbleed = true;
			}
			else if(strcmp(token, "option=noweapons") == 0)
			{
				ent->client->max_weapons = 0;
			}
			else if(strcmp(token, "option=noarmor") == 0)
			{
				ent->client->wears_armor = false;
			}
		}
		fclose(classinfo);
	}
	else
	{
		gi.bprintf(PRINT_HIGH, "no class info for %s - error openning file %s", ent->client->playermodel, ent->client->classfile);
	}
	if(ent->client->basejump <= 0)
		i = 1;	
	else
		i = ent->client->basejump;	
	stuffcmd(ent, "cl_forwardspeed 200;cl_sidespeed 180;cl_upspeed 100");
	
	ent->client->encumbrance = CalcBulk(ent);
	ent->mass = ent->client->basemass + ent->client->encumbrance;
	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;
	
	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;
//HENTAI
	client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
//HENTAI
	client->ps.fov = client->base_fov;
	if(client->pers.weapon)
		client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
	else
		client->ps.gunindex = 0;
	// clear entity state values
	ent->s.effects = 0;
	ent->s.skinnum = ent - g_edicts - 1;
	ent->s.modelindex = 255;		// will use the skin specified model
	ent->s.modelindex2 = 255;		// custom gun model
	ent->s.frame = 0;
	VectorCopy (spawn_origin, ent->s.origin);
	ent->s.origin[2] += 1;	// make sure off ground
	VectorCopy (ent->s.origin, ent->s.old_origin);
	ent->touch = PlayerTouch;
	// 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);
	
//ZOID
	if (CTFStartClient(ent))
		return;
//ZOID

	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)
{
	G_InitEdict (ent);

	strcpy(ent->client->playermodel, "");

	InitClientResp (ent->client);

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

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

	gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);

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

			gi.bprintf (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 skin
	s = Info_ValueForKey (userinfo, "skin");

	playernum = ent-g_edicts-1;

	// combine name and skin into a configstring
//ZOID

	CTFAssignSkin(ent, s);
	
//ZOID
	//gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );

	if (ent->client->pers.inventory[ITEM_INDEX(FindItem("Imaging Goggles"))] == 0 && ent->client->pers.inventory[ITEM_INDEX(FindItem("builtin Scanner"))])	
		ent->client->ps.fov = ent->client->base_fov;	
	else
	{
		ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
		if (ent->client->ps.fov < 1 || ent->client->ps.fov > ent->client->base_fov)
			ent->client->ps.fov = ent->client->base_fov;		
	}
	
	// handedness
	s = Info_ValueForKey (userinfo, "hand");
	if (strlen(s) && atoi(s) != CENTER_HANDED && atoi(s) != ent->client->pers.hand)
	{
		ent->client->newhand = atoi(s);
		ent->client->newweapon = ent->client->pers.weapon;
		ChangeWeapon(ent);
		//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");

	// check for a password
	value = Info_ValueForKey (userinfo, "password");
	if (strcmp(password->string, value) != 0)
		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
//ZOID -- force team join
		ent->client->resp.ctf_team = -1;
//ZOID
		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->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;

	gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);

//ZOID
	CTFDeadDropFlag(ent);
//ZOID

	// 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, pm_passent->mins, pm_passent->maxs, end, pm_passent, MASK_PLAYERSOLID);
	else
		return gi.trace (start, pm_passent->mins, pm_passent->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, speedfactor;
	pmove_t	pm;
	float speedscale;
	
	
	if(ent->mass > ent->client->basemass && ent->movetype == MOVETYPE_WALK)
		speedscale = ent->client->basemass * ent->client->basespeed / (ent->mass * 200.0);
	else
		speedscale = ent->client->basespeed / 200.0;
	if(ent->client->leg_damage > 0 && (ucmd->forwardmove || ucmd->sidemove || (ucmd->upmove > 0)))
	{
		speedscale = speedscale * 50 / (ent->client->leg_damage * ent->client->leg_damage + 50);
		ucmd->sidemove += crandom() * ent->client->leg_damage * (fabs(ucmd->forwardmove) + 1);
		ucmd->forwardmove += crandom() * ent->client->leg_damage * 12 * (fabs(ucmd->sidemove) + 1);
		ucmd->upmove -= random() * ent->client->leg_damage / 2;
		if(random() < (ent->client->leg_damage * ent->speed / (ent->client->basespeed * 100)))
		{
			vec3_t dir, point, normal;
			dir[0] = 0;
			dir[1] = 0;
			dir[2] = 1;
			normal[0] = 0;
			normal[1] = 0;
			normal[2] = 1;
			point[0] = ent->s.origin[0];
			point[1] = ent->s.origin[1];
			point[2] = ent->s.origin[2] - 20;
			ent->pain (ent, ent, 0, ent->client->leg_damage);
			T_Damage (ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 1, 0, DAMAGE_NO_BLEED | DAMAGE_NO_PROTECTION, MOD_HIT);
			//gi.dprintf("oof ");
		}
	}
	else if(ent->client->leg_damage > random() * 10)
		ucmd->upmove -= random() * ent->client->leg_damage / 2;
	ent->runspeed = 200 * speedscale;
	if(speedscale > 0 && ent->groundentity)
	{
		speedfactor = floor(speedscale);
		speedscale -= speedfactor;
	}
	else
	{
		speedfactor = 1;
		speedscale = 0;
	}
	//if(speed > 1)
	//	speed = 1;
	//gi.bprintf (PRINT_MEDIUM, "%f\n", speed);
	level.current_entity = ent;
	client = ent->client;
	
	
	/*if(speed != ent->runspeed) 
	{
		char s[128];
		int jspeed = ent->client->basejump;
		if(jspeed < 1)
			jspeed = 1;
		ent->runspeed = speed;
		
		sprintf(s, "cl_forwardspeed %d; cl_sidespeed %d;cl_upspeed %d", speed, 8 * speed / 10, jspeed);		
		stuffcmd(ent, s);
		
	}*/

	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;

//ZOID
	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]);
		return;
	}
//ZOID
	// 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;
	if(!ent->deadflag && !ent->groundentity && client->canfly)// && (ucmd->forwardmove || ucmd->sidemove || ucmd->upmove))
	{
		
		//client->ps.pmove.gravity = 0;
		if(ucmd->forwardmove || ucmd->sidemove || ucmd->upmove)
		{
			vec3_t forward, right, up;
			AngleVectors (ent->client->v_angle, forward, right, up);
			VectorScale(ent->velocity, 0.1, ent->velocity);
			VectorMA(ent->velocity, (speedfactor + speedscale) * ucmd->forwardmove, forward, ent->velocity);
			VectorMA(ent->velocity, (speedfactor + speedscale) * 0.5 * ucmd->sidemove, right, ent->velocity);
			VectorMA(ent->velocity, (speedfactor + speedscale) * 0.75 * ucmd->upmove, up, ent->velocity);			
		}
		//client->ps.pmove.gravity = 0;
		ent->client->anim_priority = ANIM_SPECIAL + ANIM_SPECIAL_FLY;
		//ent->movetype = MOVETYPE_FLY;
	}
	else
	{
		if(ent->client->anim_priority == ANIM_SPECIAL + ANIM_SPECIAL_FLY)
			ent->client->anim_priority = ANIM_BASIC;
		if(client->basemass > 400 && !client->canbleed && ent->waterlevel > 2 && ent->groundentity == NULL)
			ucmd->upmove = -client->basemass / 10;
	}
	pm.s = client->ps.pmove;
	pm.s.pm_flags |= PMF_NO_PREDICTION;
	if(!ent->groundentity)
	{
		vec3_t endpos;
		trace_t tr;
		
		VectorAdd(ent->s.origin, ent->velocity, endpos);
		tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, endpos, ent, MASK_PLAYERSOLID);
		
		//void PlayerTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
		if(tr.fraction > FRAMETIME)
			ent->lasttouch = NULL;
		else if(ent->lasttouch != tr.ent)
		{
			ent->touch(ent, tr.ent, &tr.plane, tr.surface);
			ent->lasttouch = tr.ent;
		}
	}

	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");
	}
	
	if(ucmd->upmove > 0 && ent->client->basejump < 0)
	{
		ucmd->upmove = 0;
	}

	pm.cmd = *ucmd;

	if(pm.cmd.forwardmove || pm.cmd.sidemove)
	{
		short fwd = pm.cmd.forwardmove, side = pm.cmd.sidemove;
		float spd = sqrt(fwd * 1.0 * fwd + side * 1.0 * side);
		if(abs(fwd) > abs(side))
		{
			fwd = fwd * fabs(fwd) / spd;
			side = side * fabs(fwd) / spd;
		}
		else
		{
			fwd = fwd * fabs(side) / spd;
			side = side * fabs(side) / spd;
		}
		pm.cmd.forwardmove = fwd;
		pm.cmd.sidemove = side;
	}
	pm.trace = PM_trace;	// adds default parms
	pm.pointcontents = gi.pointcontents;

	// perform a pmove
	while(speedfactor > 0)
	{
		gi.Pmove (&pm);
		//pm.cmd.upmove = 0;
		speedfactor--;
	}
	if(speedscale > 0)
	{
		pm.cmd.forwardmove *= speedscale;
		pm.cmd.sidemove *= speedscale;
		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;
	}
	//ent->velocity[0] *= speed;
	//ent->velocity[1] *= speed;

	VectorCopy (pm.mins, ent->mins);
	VectorCopy (pm.maxs, ent->maxs);
	/*if(!ent->groundentity && fabs(ent->client->v_angle[PITCH]) > 45)
	{		
		ent->mins[0] = -ent->client->height / 2;
		ent->mins[1] = -ent->client->height / 2;
		ent->maxs[0] = ent->client->height / 2;
		ent->maxs[1] = ent->client->height / 2;
		ent->maxs[2] = ent->client->width * 2;
	}
	else*/
	{	
		ent->mins[0] = -ent->client->width;
		ent->mins[1] = -ent->client->width;
		ent->maxs[0] = ent->client->width;
		ent->maxs[1] = ent->client->width;
		ent->maxs[2] = ent->client->height * (ent->maxs[2] - ent->mins[2]) / 56 + ent->mins[2];
	}
	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))
	{
		
		ent->velocity[2] *= ent->client->basejump / (100.0 + ent->client->leg_damage * ent->client->leg_damage * 10);
		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->viewheight = ent->client->height * (pm.viewheight + 24) /  56 - 24;
	//gi.dprintf("%f %f [%f]\n", ent->viewheight - ent->mins[2], pm.viewheight - ent->mins[2], ent->maxs[2] - ent->mins[2]);
	if(ent->viewheight < -18)
		ent->viewheight = -18;
	ent->waterlevel = pm.waterlevel * (56 / ent->client->height);
	if(ent->waterlevel > 3)
		ent->waterlevel = 3;
	ent->watertype = pm.watertype;
	ent->groundentity = pm.groundentity;
	if (pm.groundentity)
		ent->groundentity_linkcount = pm.groundentity->linkcount;

	if (ent->deadflag)
	{
		//client->ps.viewangles[ROLL] = 0;//40;
		if(ent->deadflag && fabs(ent->client->ps.viewangles[ROLL]) < 75)	
		{
			ent->client->ps.viewangles[ROLL] += crandom() * (5 / (fabs(ent->client->ps.viewangles[ROLL] / 5) + 1));
			ent->client->ps.viewangles[ROLL] += ent->client->ps.viewangles[ROLL] / 10;				
		}
	
		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);
	}

//ZOID
	if (client->ctf_grapple)
		CTFGrapplePull(client->ctf_grapple);
//ZOID

	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
		
		//ent->touch(ent, other, NULL, NULL);
		if (!other->touch || other->solid == SOLID_TRIGGER)
			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
//ZOID
		&& ent->movetype != MOVETYPE_NOCLIP
//ZOID
		)
	{
		if (!client->weapon_thunk)
		{
			client->weapon_thunk = true;
			Think_Weapon (ent);
		}
	}

//ZOID
	for (i = 1; i <= maxclients->value; i++) {
		other = g_edicts + i;
		if (other->inuse && other->client->chase_target == ent)
			UpdateChaseCam(other);
	}
//ZOID
}


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

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

	if (ent->deadflag)
	{
		// wait for any button just going down
		if(TossClientStuff(ent))
		{
		}
		else 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;
}
